2013-03-25 38 views
26

Sto provando a scrivere una stored procedure che restituisce un elenco di oggetti con l'ordinamento e la direzione di ordinamento selezionati dall'utente e passati come parametri sql.Ordine condizionale T-SQL di

Diciamo che ho una tabella di prodotti con le seguenti colonne: product_id (int), nome (varchar), il valore (int), CREATED_DATE (datetime) e parametri @sortDir e @sortOrder

voglio fare qualcosa di simile

select * 
from Product 
    if (@sortOrder = 'name' and @sortDir = 'asc') 
    then order by name asc 
    if (@sortOrder = 'created_date' and @sortDir = 'asc') 
    then order by created_date asc 
    if (@sortOrder = 'name' and @sortDir = 'desc') 
    then order by name desc 
    if (@sortOrder = 'created_date' and @sortDir = 'desc') 
    then order by created_date desc 

ho provato farlo con istruzioni case, ma avuto problemi dal momento che i tipi di dati erano diverse. Qualcuno ha qualche suggerimento?

+0

Eri sulla strada giusta con la dichiarazione del caso. Puoi pubblicare quello che hai avuto e dirci qual è stato l'errore? –

+0

Viene visualizzato il seguente errore "Conversione non riuscita durante la conversione di data e/o ora dalla stringa di caratteri." perché apparentemente i tuoi tipi di dati devono essere gli stessi se stai usando le dichiarazioni dei casi. – RiceRiceBaby

+0

Puoi pubblicare il tuo codice? –

risposta

47

CASE è un'espressione che restituisce un valore. Non è per il controllo del flusso, come IF. E non è possibile utilizzare IF all'interno di una query.

Sfortunatamente, esistono alcune limitazioni con le espressioni CASE che rendono complicato fare ciò che si desidera. Ad esempio, tutte le diramazioni in un'espressione CASE devono restituire lo stesso tipo o essere implicitamente convertibili nello stesso tipo. Non lo proverei con stringhe e date. Non è inoltre possibile utilizzare CASE per specificare la direzione di ordinamento.

SELECT column_list_please 
FROM dbo.Product -- dbo prefix please 
ORDER BY 
    CASE WHEN @sortDir = 'asc' AND @sortOrder = 'name' THEN name END, 
    CASE WHEN @sortDir = 'asc' AND @sortOrder = 'created_date' THEN created_date END, 
    CASE WHEN @sortDir = 'desc' AND @sortOrder = 'name' THEN name END DESC, 
    CASE WHEN @sortDir = 'desc' AND @sortOrder = 'created_date' THEN created_date END DESC; 

Una soluzione senza dubbio più facile (soprattutto se questo diventa più complesso) è quello di utilizzare SQL dinamico. Per contrastare SQL injection è possibile verificare i valori:

IF @sortDir NOT IN ('asc', 'desc') 
    OR @sortOrder NOT IN ('name', 'created_date') 
BEGIN 
    RAISERROR('Invalid params', 11, 1); 
    RETURN; 
END 

DECLARE @sql NVARCHAR(MAX) = N'SELECT column_list_please 
    FROM dbo.Product ORDER BY ' + @sortOrder + ' ' + @sortDir; 

EXEC sp_executesql @sql; 

Un altro vantaggio per SQL dinamico, a dispetto di tutta la paura-mongering che si sviluppa su di esso: è possibile ottenere il miglior piano per ogni variazione sorta, invece di un solo piano che ottimizzerà in base a qualsiasi variazione di tipo che hai usato per prima. E 'anche esibito meglio universalmente in un confronto delle prestazioni di recente mi sono imbattuto:

http://sqlperformance.com/conditional-order-by

+0

+1 Funziona bene - solo curioso perché non si combinano le dichiarazioni dei casi? Hai 8 - sembra che puoi semplificare a 4? Cosa mi manca? – sgeddes

+0

Vedere la risposta di @ GordonLinoff - Intendevo combinare i criteri AND - non restituire tipi di dati diff ... – sgeddes

+0

@sgeddes Oh sì, ok. Di solito li divido perché potrebbero esserci dei casi in cui in un secondo momento aggiungeremo il cognome, il nome di dominio, ecc. –

15

Hai bisogno di un istruzione case, anche se avrei utilizzare più istruzioni case:

order by (case when @sortOrder = 'name' and @sortDir = 'asc' then name end) asc, 
     (case when @sortOrder = 'name' and @sortDir = 'desc' then name end) desc, 
     (case when @sortOrder = 'created_date' and @sortDir = 'asc' then created_date end) asc, 
     (case when @sortOrder = 'created_date' and @sortDir = 'desc' then created_date end) desc 

Avendo quattro clausole diverse elimina il problema di conversione tra tipi.

+0

Cosa succede se si desidera ordinare da due colonne? Questo è possibile con le dichiarazioni CASE? https://stackoverflow.com/questions/47388230/order-by-two-columns-with-usage-of-case-when/47388327#47388327 – FrenkyB

1
declare @str varchar(max) 
set @str = 'select * from Product order by ' + @sortOrder + ' ' + @sortDir 
exec(@str) 
2

Ci sono molti modi per farlo. Un modo potrebbe essere:

SELECT * 
FROM 
(
    SELECT 
    ROW_NUMBER() OVER (ORDER BY 
    CASE WHEN @sortOrder = 'name' and @sortDir = 'asc' then name 
    END ASC, 
    CASE WHEN @sortOrder = 'name' and @sortDir = 'desc' THEN name 
    END DESC, 
    CASE WHEN i(@sortOrder = 'created_date' and @sortDir = 'asc' THEN created_date 
    END ASC, 
    CASE WHEN i(@sortOrder = 'created_date' and @sortDir = 'desc' THEN created_date 
    END ASC) RowNum 
    * 
) 
order by 
RowNum 

È anche possibile farlo utilizzando SQL dinamico.