2009-11-10 8 views
6

Załóżmy, że masz procedurę składowaną i przyjmuje ona opcjonalny parametr. Chcesz użyć tego opcjonalnego parametru w zapytaniu SQL. Zazwyczaj jest to jak widziałem to zrobić:Właściwy sposób obsługi "opcjonalnego", gdzie filtry klauzuli w SQL?

SELECT * FROM dbo.MyTableName t1 
WHERE t1.ThisField = 'test' 
AND (@MyOptionalParam IS NULL OR t1.MyField = @MyOptionalParam) 

To wydaje się działać dobrze, jednak powoduje to dużą ilość logicznych odczytów, jeśli uruchomić kwerendę statystyki IO. Próbowałem również następujący wariant:

SELECT * FROM dbo.MyTableName t1 
WHERE t1.ThisField = 'test' 
AND t1.MyField = CASE WHEN @MyOptionalParam IS NULL THEN t1.MyField ELSE @MyOptionalParam END 

I daje taką samą liczbę wysokich odczytów. Jeśli będziemy konwertować SQL do łańcucha, a następnie zadzwonić sp_executesql na nim odczyty są prawie zerowe:

DECLARE @sql nvarchar(max) 

SELECT @sql = 'SELECT * FROM dbo.MyTableName t1 
WHERE t1.ThisField = ''test''' 

IF @MyOptionalParam IS NOT NULL 
BEGIN 
    SELECT @sql = @sql + ' AND t1.MyField = @MyOptionalParam ' 
END 

EXECUTE sp_ExecuteSQL @sql, N'@MyOptionalParam', @MyOptionalParam 

jestem szalony? Dlaczego opcjonalnie klauzule są tak trudne do uzyskania?

Aktualizacja: Ja w zasadzie pytaniem, czy istnieje sposób, aby zachować standardową składnię wewnątrz procedury przechowywanej i uzyskać niski logiczne brzmi jak metoda sp_executesql robi. Wydaje się całkowicie szalony mi zbudować ciąg ... nie wspominając to sprawia, że ​​trudniej utrzymać, debugowania, wizualizacji ..

+0

Nicholas, zobacz poniższe podejście do unii w celu zastosowania standardowej składni sql bez dynamicznego sql - Byłbym bardzo ciekawy, aby zobaczyć, jak publikujesz swoje wyniki w swoim scenariuszu ... – chadhoc

+0

@Nicholas: Konstruowanie zapytania jako ciąg przed wykonaniem jest ** dokładnie ** czym * dynamiczny * SQL jest. Jest to niewielki problem podczas debugowania - kopiowania/wklejania, pozbycia się składni połączenia ciągów. –

Odpowiedz

1

Używasz „LUB” klauzuli (pośrednio i bezpośrednio) na dwa pierwsze Instrukcje SQL. Ostatni to kryterium "ORAZ". "OR" jest zawsze droższe niż kryteria "ORAZ". Nie, nie jesteś szalony, należy się spodziewać.

+2

'EXEC sp_executesql' ** robi ** buforuje plan zapytania, ponieważ v2005: http://www.sommarskog.se/dynamic_sql.html#queryplans –

+0

Masz rację. Nie zauważyłem, że on używa parametru na sp_ExecuteSQL. – mevdiven

+0

Zmieniłem odpowiednio odpowiedź. Dzięki. – mevdiven

2

Jest to kolejna wariacja na opcjonalnej techniki parametru:

SELECT * FROM dbo.MyTableName t1 
WHERE t1.ThisField = 'test' 
AND t1.MyField = COALESCE(@MyOptionalParam, t1.MyField) 

Jestem całkiem pewien, że mają ten sam problem z wydajnością choć. Jeśli wydajność wynosi # 1, prawdopodobnie utkniesz z logiką rozwidlania i blisko duplikatów zapytań lub budowania ciągów, co jest równie bolesne w TSQL.

4

Jeśli konwertować SQL do łańcucha, a następnie zadzwonić sp_executesql na nim odczyty są prawie zerowe ...

  1. Ponieważ zapytanie jest już oceniając OR, który jak widzi zabójstwo sargability
  2. Plan zapytania jest buforowany przy użyciu sp_executesql; SQL Server nie trzeba robić twardy parse ...

doskonałe źródło: The Curse & Blessing of Dynamic SQL

Dopóki używasz parametryzowane kwerendy, należy bezpieczne od SQL Injection attacks.

0

EDIT: Dodawanie link to similar question/answer with context as to why the union/if...else approach works better than OR logic (FYI, Remus z odpowiadającego w ten link, używany do pracy w zespole SQL Server rozwój Service Broker oraz innych technologii)

Zmiana z pomocą „lub” składni do podejścia Unii zobaczysz 2 wnosi, że należy zachować liczyć Twój logiczny odczytu tak niskie, jak to możliwe:

SELECT * FROM dbo.MyTableName t1 
WHERE t1.ThisField = 'test' 
AND @MyOptionalParam IS NULL 
union all 
SELECT * FROM dbo.MyTableName t1 
WHERE t1.ThisField = 'test' 
AND t1.MyField = @MyOptionalParam 

Jeśli chcesz de-powielać wyniki, użyj „unia” zamiast „jedności wszystkich”.

EDIT: Demo pokazuje, że optymalizator jest wystarczająco inteligentny, aby wykluczyć ze skanowania null wartości zmiennej w jedności:

if object_id('tempdb..#data') > 0 
    drop table #data 
go 

-- Put in some data 
select top 1000000 
     cast(a.name as varchar(100)) as thisField, cast(newid() as varchar(50)) as myField 
into #data 
from sys.columns a 
cross join sys.columns b 
cross join sys.columns c; 
go 

-- Shwo count 
select count(*) from #data; 
go 

-- Index on thisField 
create clustered index ixc__blah__temp on #data (thisField); 
go 

set statistics io on; 
go 

-- Query with a null parameter value 
declare @MyOptionalParam varchar(50); 
select * 
from #data d 
where d.thisField = 'test' 
and  @MyOptionalParam is null; 
go 

-- Union query 
declare @MyOptionalParam varchar(50); 
select * 
from #data d 
where d.thisField = 'test' 
and  @MyOptionalParam is null 
union all 
select * 
from #data d 
where d.thisField = 'test' 
and  d.myField = '5D25E9F8-EA23-47EE-A954-9D290908EE3E'; 
go 

-- Union query with value 
declare @MyOptionalParam varchar(50); 
select @MyOptionalParam = '5D25E9F8-EA23-47EE-A954-9D290908EE3E' 
select * 
from #data d 
where d.thisField = 'test' 
and  @MyOptionalParam is null 
union all 
select * 
from #data d 
where d.thisField = 'test' 
and  d.myField = '5D25E9F8-EA23-47EE-A954-9D290908EE3E'; 
go 

if object_id('tempdb..#data') > 0 
    drop table #data 
go 
+0

Pierwsze zapytanie odczytuje całą tabelę. To nie jest dobry sposób na zminimalizowanie IO. –

+0

Jest to droższa metoda niż instrukcja SQL przedstawiona w pytaniu. – mevdiven

+0

Przykro mi, ale optymalizator na pewno NIE przeskanuje całej tabeli w pierwszym zapytaniu, jest na tyle sprytny, aby wykluczyć zapytanie oparte na zerowej wartości "AND" ze zmienną. Prosty przykład z wyjściami statystyki IO zademonstruje, uruchom lokalnie i sprawdź na własną rękę (zauważ, jeśli nie masz odpowiedniego indeksu na ThisField, zawsze dostaniesz skan ze względu na zapytanie przeciwko niemu, stąd zakładasz) - Edytowałem odpowiedź z próbką wykazać - Put w niektórych danych wybrać \t górną 1000000 \t \t obsady (a.name jako varchar (100)) jako thisField, wylewane (newid() jako varchar (50)) jako myField do \t #data od \t s – chadhoc

-1

Zmian z pomocą „lub” do składnia zapytań dwa podejścia, będziesz zobacz 2 różne plany, które powinny zachować swój logiczny licznika odczytu tak niskie, jak to możliwe:

IF @MyOptionalParam is null 
BEGIN 

    SELECT * 
    FROM dbo.MyTableName t1 

END 
ELSE 
BEGIN 

    SELECT * 
    FROM dbo.MyTableName t1 
    WHERE t1.MyField = @MyOptionalParam 

END 

trzeba walczyć chęć programatora do zmniejszenia powielania tutaj. Zdaj sobie sprawę, że prosisz o dwa fundamentalnie różne plany wykonania i potrzebujesz dwóch zapytań do wyprodukowania dwóch planów.

+0

Ale co, jeśli masz wiele opcjonalnych parametrów, które chcesz filtrować? Chyba nie rozumiem, dlaczego jest to "dwa zasadniczo różne plany wykonania". Jeśli jestem parserem, patrzę na zmienną, idę "hej, jest zerowa i nigdy nie będzie inaczej. Mogę przestać filtrować na niej." Ale przypuszczam, że to nie działa w ten sposób, przynajmniej w SQL 2005. –

+0

Jeśli masz wiele parametrów opcjonalnych, szanse są takie, że tylko kilka z nich ma znaczenie dla planu kwerend ... po prostu oddziałuj na te. Jeśli chodzi o sniffowanie parametrów: http://sqlblog.com/blogs/ben_nevarez/archive/2009/08/27/the-query-optimizer-and-parameter-sniffing.aspx Życzymy powodzenia w tym podejściu. –

+0

Do komentującego downwizera - rozumiem. Uważasz, że masz rację, a ty także jesteś w większości. Jednak jesteś w błędzie. –

Powiązane problemy