2013-03-25 43 views
26

Próbuję napisać procedurę składowaną, która zwraca listę obiektów z porządkiem sortowania i kierunkiem sortowania wybranym przez użytkownika i przekazanych jako parametry sql.Warunkowe zamówienie T-SQL:

Powiedzmy mam tabelę produktów z następującymi kolumnami: PRODUCT_ID (int), nazwa (varchar), wartość (int), CREATED_DATE (datetime) i parametry @sortDir i @sortOrder

chcę wykonać coś takiego, jak

Próbowałem zrobić to z instrukcjami case, ale miałem problemy, ponieważ typy danych były różne. Czy ktoś ma jakieś sugestie?

+0

Byliście na właściwej drodze z oświadczeniem o sprawie. Czy możesz opublikować to, co miałeś i powiedzieć nam, jaki był błąd? –

+0

Wystąpił następujący błąd "Konwersja nie powiodła się podczas przekształcania daty i/lub czasu z ciągu znaków." ponieważ najwyraźniej twoje typy danych muszą być takie same, jeśli używasz instrukcji case. – RiceRiceBaby

+0

Czy możesz opublikować swój kod? –

Odpowiedz

47

CASE to wyrażenie, które zwraca wartość. Nie służy do kontroli przepływu, np. IF. I nie można użyć IF w zapytaniu.

Niestety, istnieją pewne ograniczenia z wyrażeniami CASE, które sprawiają, że wykonywanie tego, co chcesz, jest uciążliwe. Na przykład wszystkie gałęzie w wyrażeniu CASE muszą zwracać ten sam typ lub być niejawnie wymienialne na ten sam typ. Nie próbowałem tego z ciągami i datami. Nie można również użyć CASE, aby określić kierunek sortowania.

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; 

Prawdopodobnie prostszym rozwiązaniem (szczególnie, jeśli robi się to bardziej skomplikowane) jest użycie dynamicznego SQL. Udaremnić SQL injection można przetestować wartości:

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; 

Kolejny plus dla dynamicznego SQL, mimo wszystko strach zaopatrywaniem który rozprzestrzenia się o tym: możesz uzyskać najlepszy plan dla każdej zmiany sortowania, zamiast jeden plan, który zoptymalizuje każdą odmianę, której używałeś jako pierwszą. Wykonywana jest również najlepiej powszechnie w niedawnym porównania wydajności wpadłem:

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

+0

+1 Działa dobrze - po prostu ciekawi, dlaczego nie łączyłbyś instrukcji dotyczących spraw? Masz 8 - wydaje się, że możesz uprościć do 4? czego mi brakuje? – sgeddes

+0

Zobacz odpowiedź @ GordonLinoffa - chodzi mi o połączenie kryteriów AND - nie zwracanie typów danych różnicowych ... – sgeddes

+0

@sgeddes O tak, dobrze. Zazwyczaj je dzielę, ponieważ mogą wystąpić przypadki, w których później dodają nazwisko, pośredni adres, itp. –

15

co potrzeba oświadczenie przypadku, chociaż chciałbym skorzystać z wielu instrukcji postępowania:

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 

Mając cztery różne klauzule eliminuje problem konwersji między typami.

+0

Co jeśli chcesz zamówić przez dwie kolumny? Czy jest to możliwe w przypadku instrukcji CASE? https://stackoverflow.com/questions/47388230/order-by-twoi-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

Istnieje wiele sposobów, aby to zrobić. Jednym ze sposobów byłoby:

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 

Można również zrobić to za pomocą dynamicznego SQL.