2009-12-30 4 views
12

OK, die x-te bedingte Spalte Frage:Wie wird eine Spalte in einer WHERE-Klausel bedingt gefiltert?

Ich schreibe ein gespeichertes Proc, das einen Eingabeparameter verwendet, der einer von mehreren Flag-Spalten zugeordnet ist. Was ist der beste Weg, um die angeforderte Spalte zu filtern? Ich bin derzeit auf SQL2000, aber im Begriff, zu SQL2008 zu wechseln, also nehme ich eine zeitgenössische Lösung, wenn jemand verfügbar ist.

Die Tabelle im sproc abgefragt sieht aus wie

ID ... fooFlag barFlag bazFlag quuxFlag 
--  ------- ------- ------- -------- 
01   1  0  0   1 
02   0  1  0   0 
03   0  0  1   1 
04   1  0  0   0 

und ich so etwas wie

select ID, name, description, ... 
from myTable 
where (colname like @flag + 'Flag') = 1 

so tun, wenn ich die sproc wie exec uspMyProc @flag = 'foo' nenne ich Reihen zurück würde 1 und 4 .

Ich weiß, ich kann nicht den Teil in Parens direkt in SQL. Um dynamisches SQL zu erstellen, muss ich die gesamte Abfrage in eine Zeichenfolge stopfen, den @flag-Parameter in der WHERE-Klausel verketten und dann die Zeichenfolge ausführen. Abgesehen von dem dreckigen Gefühl, das ich beim Ausführen von dynamischem SQL erhalte, ist meine Abfrage ziemlich groß (ich wähle ein paar Dutzend Felder, verbinde 5 Tabellen, rufe ein paar Funktionen auf), also ist es eine große, riesige Zeichenkette wegen einer einzelnen Zeile in einem 3-Zeilen-WHERE-Filter.

Alternativ könnte ich 4 Kopien der Abfrage haben und unter ihnen in einer CASE-Anweisung auswählen. Dadurch bleibt der SQL-Code direkt ausführbar (und unterliegt einem Syntax-Highlighting usw.), aber auf Kosten der Wiederholung großer Codeabschnitte, da ich den CASE nicht nur für die WHERE-Klausel verwenden kann.

Gibt es noch andere Optionen? Irgendwelche kniffligen Joins oder logischen Operationen, die angewendet werden können? Oder sollte ich nur darüber hinwegkommen und das dynamische SQL ausführen?

+1

"exec uspMyProc @flag = 'foo' Ich würde Zeile 1 zurück erhalten." Warum nur Zeile 1 und nicht auch Zeile 4? –

+0

@Mark - oh, ja, behoben! – Val

Antwort

18

Es gibt ein paar Möglichkeiten, dies zu tun:

Sie dies mit einem Fall Aussage machen können.

Sie können IF verwenden.

IF (@flag = 'foo') BEGIN 
    select ID, name, description, ... 
    from myTable 
    where fooFlag = 1 
END ELSE IF (@flag = 'bar') BEGIN 
    select ID, name, description, ... 
    from myTable 
    where barFlag = 1 
END 

.... 

Sie können eine komplizierte Where-Klausel mit vielen Klammern haben.

select ID, name, description, ... 
from myTable 
where (@flag = 'foo' and fooFlag = 1) 
OR (@flag = 'bar' and barFlag = 1) OR ... 

Sie können dies mit dynamischem SQL:

DECLARE @SQL nvarchar(4000) 

SELECT @SQL = N'select ID, name, description, ... 
from myTable 
where (colname like ''' + @flag + 'Flag'') = 1' 

EXECUTE sp_ExecuteSQL @SQL, N'' 

Es gibt mehr, aber ich denke, eine davon wird erhalten Sie gehen.

+0

+1 - Ich persönlich würde den "CASE" -Ansatz verwenden, da ich denke, dass es der sauberste ist, obwohl ich eher einen einfachen Fall anstelle eines gesuchten Falls verwenden würde, wie Sie gezeigt haben, da die zu synchronisierende Sache konstant ist (dh 'CASE @flag WHEN 'foo' DANN fooFlag WANN 'bar' dann barFlag END = 1') –

+0

@Greg: Natürlich hat das wahrscheinlich die schlechteste Performance aller Optionen, da Sie jetzt nicht nur eine Spalte umbrechen In einer Funktion verpacken Sie alle von ihnen. Wenn Sie für jede Spalte einen anderen Index haben, wird dies zu einem Tabellenscan. – Aaronaught

+0

+1 - abhängig von der Komplexität der Abfrage. Obwohl in meiner Erfahrung, selbst wenn die Abfrage einfach beginnt, endet es immer (während der Wartung etc.) komplex, und ich wünschte, ich hätte mit dynamic sql begonnen. – Russell

4

"Alternativ könnte ich 4 Kopien der Abfrage haben und unter ihnen in einer CASE-Anweisung auswählen."

Sie brauchen nicht Ihre gesamte Abfrage 4 mal zu kopieren, nur alle Möglichkeiten in die where-Klauseln in Ihre einzige Kopie der Abfrage hinzufügen:

select ID, name, description, ... 
from myTable 
where (@flag = 'foo' and fooFlag = 1) OR (@flag = 'bar' and barFlag = 1) OR ... 
+1

+1: Das wäre mein Ansatz, obwohl ich immer noch dynamische verwenden würde, so dass alle ORs nicht jedes Mal mitgeschleppt werden, wenn die Abfrage ausgeführt wird. –

+0

Ja, das ist meine bevorzugte Lösung. Ich habe Gabriels Antwort gewählt, da er mir so viele Möglichkeiten gegeben hat, inkl. dieses. – Val

0

Sie können für jede mögliche Flagge einen Parameter haben Spalte, dann überprüfen Sie, ob der Parameter null ist oder der Wert in der Spalte gleich dem Parameter ist. Dann geben Sie eine 1 für die Flaggen ein, die Sie überprüfen möchten, und lassen Sie die anderen null.

select id, name, description, ... 
from myTable 
where (@fooFlag is null or fooFlag = @fooFlag) AND 
     (@barFlag is null or barFlag = @barFlag) AND 
     ... 

Ehrlich gesagt, obwohl, scheint dies ein idealer Kandidat eine dynamische LINQ-Abfrage für den Aufbau und das Überspringen der SPROC, sobald Sie auf SQL2008 bekommen.

+0

Das würde ich tun, wenn die Leistung keine große Rolle spielen würde (d. H. Die Flag-Spalten werden nicht indiziert). – Aaronaught

+0

Nein, ich möchte wirklich mehrere Params vermeiden - mehr Wartung wenn ich zu mehr Flags expandiere. Mit einem einzigen Parameter ist die Wartung auf den SP beschränkt; Wenn ich Parameter hinzufüge, muss ich auch die Struktur des aufrufenden Codes ändern, anstatt nur neue Werte in einem vorhandenen Parameter zu übergeben. Danke, tho! – Val

+0

Oh, ja - vergessen zu erwähnen, dass ich die DB nach SQL2008 verschiebe, meine App ist ASP Classic, und ich werde keine Möglichkeit haben, diesen Teil der App in nächster Zeit auf .NET zu verschieben. Also LINQ ist out ... – Val

3

Was ich tun würde, ist CASE einige Variablen zu Beginn. Beispiel:

DECLARE 
    @fooFlag int, 
    @barFlag int, 
    @bazFlag int, 
    @quuxFlag int 

SET @fooFlag = CASE WHEN @flag = 'foo' THEN 1 ELSE NULL END 
SET @barFlag = CASE WHEN @flag = 'bar' THEN 1 ELSE NULL END 
SET @bazFlag = CASE WHEN @flag = 'baz' THEN 1 ELSE NULL END 
SET @quuxFlag = CASE WHEN @flag = 'quux' THEN 1 ELSE NULL END 

SELECT ID, name, description, ... 
FROM myTable 
WHERE (fooFlag >= ISNULL(@fooFlag, 0) AND fooFlag <= ISNULL(@fooFlag, 1)) 
AND (barFlag >= ISNULL(@barFlag, 0) AND barFlag <= ISNULL(@barFlag, 1)) 
AND (bazFlag >= ISNULL(@bazFlag, 0) AND bazFlag <= ISNULL(@bazFlag, 1)) 
AND (quuxFlag >= ISNULL(@quuxFlag, 0) AND quuxFlag <= ISNULL(@quuxFlag, 1)) 

Die gute Sache über diese Frage ist, dass, weil die möglichen Werte für „Flags“ begrenzt sind, können Sie alle Ihre conditionals als Voraussetzungen statt Einwickeln Spalten in ihnen berechnen kann. Dies garantiert eine hoch leistungsfähige Indexsuche für die Spalten, die indiziert werden, und erfordert kein dynamisches SQL. Und es ist besser als 4 separate Abfragen aus offensichtlichen Gründen zu schreiben.

+0

Dies garantiert nicht eine Hochleistungs-Index-Suche, da es barFlag> = ISNULL (@barFlag, 0) jedes Mal neu bewerten wird. – Russell

+0

@barFlag ist nur eine skalare Variable, keine Tabellenspalte.Es ist sargable - Sie könnten den gesamten Ausdruck in 8 separate konstante Flags erweitern. Probieren Sie es aus und sehen Sie. – Aaronaught

+0

Entschuldigung, Aaron, ich benutzte die Abfrage mit dem IS NULL ODER .. Ausdrücke :) Auswerten von numerischen Ausdrücken scheint dies nicht zu tun. Sorry – Russell

0
where 
    case when @value<>0 then Field else 1 end 
    = 
    case when @value<>0 then @value else 1 end 
+1

Das Hinzufügen einiger Text-Erklärungen zu Ihrem Code hilft dem Fragesteller. – RBT

0
declare @CompanyID as varchar(10) = '' -- or anyother value 

select * from EmployeeChatTbl chat 

    where chat.ConversationDetails like '%'[email protected]+'%' 

       and 
       (
        (0 = CASE WHEN (@CompanyID = '') THEN 0 ELSE 1 END) 
           or 
        (chat.CompanyID = @CompanyID) 
       ) 

zu arbeiten, wenn die CompanyID vorhanden ist, Filtration auf es dann basierend getan wird, wird andere weise, Filtration übersprungen.