2009-12-30 11 views
12

OK, l'ennesima domanda di colonna condizionale:Come filtrare condizionalmente su una colonna in una clausola WHERE?

Sto scrivendo un processo memorizzato che accetta un parametro di input mappato su una delle colonne di flag diverse. Qual è il modo migliore per filtrare sulla colonna richiesta? Sono attualmente su SQL2000, ma sto per passare a SQL2008, quindi prenderò una soluzione contemporanea se disponibile.

La tabella interrogato nel sproc assomiglia

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 

e voglio fare qualcosa di simile

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

così se io chiamo lo sproc come exec uspMyProc @flag = 'foo' avrei avuto indietro le righe 1 e 4

So che non posso eseguire la parte in Paren direttamente in SQL. Per fare l'SQL dinamico, dovrò inserire l'intera query in una stringa, concatenare il parametro @flag nella clausola WHERE e quindi eseguire la stringa. A parte lo sporco sentimento che provo quando faccio SQL dinamico, la mia query è abbastanza ampia (seleziono una dozzina di campi, unisco 5 tavoli, chiamando un paio di funzioni), quindi è una grande stringa gigante a causa di una singola riga in un filtro WHERE a 3 righe.

In alternativa, potrei avere 4 copie della query e selezionarle in un'istruzione CASE. Questo lascia il codice SQL direttamente eseguibile (e soggetto alla sintassi hilighting, ecc.) Ma a costo di ripetere grossi blocchi di codice, dal momento che non posso usare il CASE solo sulla clausola WHERE.

Ci sono altre opzioni? Qualche joins difficile o operazioni logiche che possono essere applicate? O dovrei semplicemente superarlo ed eseguire l'SQL dinamico?

+1

"exec uspMyProc @flag = 'foo' Vorrei tornare indietro riga 1". Perché solo riga 1 e non anche riga 4? –

+0

@ Mark - d'oh, sì, corretto! – Val

risposta

18

ci sono alcuni modi per farlo:

si può fare questo con un'istruzione case.

select ID, name, description, ... 
from myTable 
where CASE 
    WHEN @flag = 'foo' then fooFlag 
    WHEN @flag = 'bar' then barFlag 
END = 1 

È possibile utilizzare IF.

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 

.... 

È possibile avere una clausola WHERE complicata con molte parentesi.

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

È possibile farlo con SQL dinamico:

DECLARE @SQL nvarchar(4000) 

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

EXECUTE sp_ExecuteSQL @SQL, N'' 

ci sono più, ma penso che uno di questi saranno farti andare.

+0

+1 - Personalmente userei l'approccio 'CASE' perché penso sia il più accurato, anche se molto probabilmente userò un caso semplice piuttosto che un caso cercato, come hai mostrato come la cosa da abbinare è costante (cioè 'CASE @flag QUANDO 'foo' THEN fooFlag QUANDO 'bar' poi barFlag END = 1') –

+0

@Greg: Naturalmente, è probabile che abbia le peggiori prestazioni di tutte le opzioni, dato che ora non si sta solo avvolgendo una colonna in una funzione, li stai avvolgendo tutti. Se si dispone di un indice diverso su ciascuna colonna, questa diventa una scansione della tabella. – Aaronaught

+0

+1 - a seconda della complessità della query. Sebbene nella mia esperienza, anche quando la query inizia in modo semplice, finisce sempre (durante la manutenzione, ecc.) Complessa e vorrei aver iniziato con SQL dinamico. – Russell

4

"In alternativa, potrei avere 4 copie della query e selezionarle tra loro in un'istruzione CASE."

Non è necessario copiare l'intera interrogazione 4 volte, basta aggiungere tutte le possibilità, nelle clausole dove nella vostra singola copia della query:

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

+1: questo sarebbe il mio approccio, sebbene utilizzerei comunque la dinamica in modo che tutte le OR non vengano trascinate ogni volta che viene eseguita la query. –

+0

sì, questa è la mia soluzione preferita. Ho scelto la risposta di Gabriel, anche se mi ha dato così tante opzioni, incl. questo. – Val

0

si potrebbe avere un parametro per ogni possibile bandiera colonna, quindi controlla se il parametro è nullo o il valore nella colonna è uguale al parametro. Quindi passi un 1 per le bandiere che vuoi controllare e lascia gli altri nulli.

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

Onestamente, però, questo sembra come un candidato ideale per la costruzione di una query LINQ dinamica e saltando lo SPROC una volta arrivati ​​a SQL2008.

+0

Questo è quello che farei se le prestazioni non rappresentassero un problema rilevante (ad esempio, le colonne flag non sono indicizzate). – Aaronaught

+0

No, mi piacerebbe davvero evitare più parametri, più manutenzione se espandessi a più flag. Con un singolo parametro, la manutenzione è limitata all'SP; se aggiungo params, devo cambiare anche la struttura del codice chiamante, piuttosto che passare semplicemente nuovi valori in un parametro esistente. Grazie, comunque! – Val

+0

Oh, sì - ho dimenticato di dire che, mentre sto spostando il DB in SQL2008, la mia app è ASP Classic e non avrò la possibilità di spostare questa parte dell'app su .NET in qualsiasi momento. Quindi LINQ è fuori ... – Val

3

Quello che vorrei fare è CASE alcune variabili all'inizio. Esempio:

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)) 

La cosa buona di questa query è che, poiché i valori possibili per "bandiere" sono delimitate, è possibile calcolare tutti i condizionali come prerequisiti invece di avvolgere le colonne in loro. Ciò garantisce una ricerca di indici ad alte prestazioni su qualsiasi colonna indicizzata e non richiede la scrittura di alcun SQL dinamico. Ed è meglio che scrivere 4 query separate per ovvi motivi.

+0

Questo non garantisce una ricerca di indice ad alte prestazioni in quanto rivaluterà barFlag> = ISNULL (@barFlag, 0) ogni volta. – Russell

+0

@barFlag è solo una variabile scalare, non una colonna di tabella.È sargabile: puoi espandere l'intera espressione in 8 flag costanti separati. Provalo e guarda – Aaronaught

+0

Mi scuso, Aaron, stavo usando la query con le espressioni IS NULL OR .. :) La valutazione delle espressioni numeriche non sembra farlo. Scusa – Russell

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

Aggiungere una spiegazione testuale al codice aiuterà il richiedente. – 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) 
       ) 

lavoro

quando la CompanyID è presente, allora filtrazione basato su di esso è fatto, l'altro saggio, filtrazione viene saltato.

Problemi correlati