Правильный способ обработки «необязательных», где предложение фильтров в SQL? - PullRequest
6 голосов
/ 10 ноября 2009

Допустим, у вас есть хранимая процедура, и она принимает необязательный параметр. Вы хотите использовать этот необязательный параметр в запросе SQL. Как правило, вот как я это сделал:

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

Кажется, это работает хорошо, однако вызывает большое количество логических чтений, если вы выполняете запрос с STATISTICS IO ON. Я также попробовал следующий вариант:

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

И это дает такое же количество высоких чтений. Если мы преобразуем SQL в строку, затем вызовем sp_ExecuteSQL для нее, чтение будет почти равно нулю:

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

Я сумасшедший? Почему факультативно, когда пункты так трудно получить правильно?

Обновление: Я просто спрашиваю, есть ли способ сохранить стандартный синтаксис внутри хранимой процедуры и получить низкое логическое чтение, как это делает метод sp_ExecuteSql. Мне кажется совершенно безумным создание строки ... не говоря уже о том, что ее сложнее поддерживать, отлаживать, визуализировать ..

Ответы [ 5 ]

4 голосов
/ 10 ноября 2009

Если мы преобразуем SQL в строку, а затем вызовем sp_ExecuteSQL для нее, чтение будет почти нулевым ...

  1. Поскольку ваш запрос больше не оценивает ИЛИ, что, как вы можете видеть, убивает сарказм
  2. План запроса кэшируется при использовании sp_executesql; SQL Server не должен выполнять жесткий анализ ...

Отличный ресурс: Проклятие и благословение динамического SQL

Пока вы используете параметризованные запросы, вы должны защищаться от SQL-инъекций .

2 голосов
/ 10 ноября 2009

Это еще один вариант метода необязательных параметров:

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

Я почти уверен, что у него будет такая же проблема с производительностью. Если производительность # 1, то вы, вероятно, застрянете в логике разветвления и почти повторяющихся запросах или построении строк, что одинаково болезненно в TSQL.

1 голос
/ 10 ноября 2009

Вы используете предложение «ИЛИ» (неявно и явно) в первых двух операторах SQL. Последний является критерием «И». «ИЛИ» всегда дороже критериев «И». Нет, ты не сумасшедший, следует ожидать.

0 голосов
/ 10 ноября 2009

Если перейти от использования синтаксиса "или" к подходу с двумя запросами, вы увидите 2 разных плана, которые должны поддерживать как можно меньше логических значений чтения:

IF @MyOptionalParam is null
BEGIN

  SELECT *
  FROM dbo.MyTableName t1

END
ELSE
BEGIN

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

END

Вам нужно побороть желание вашего программиста уменьшить дублирование. Поймите, что вы запрашиваете два принципиально разных плана выполнения и вам требуется два запроса для создания двух планов.

0 голосов
/ 10 ноября 2009

РЕДАКТИРОВАТЬ: Добавление ссылки на аналогичный вопрос / ответ с контекстом о том, почему подход union / if ... else работает лучше, чем логика ИЛИ (FYI, Remus, ответчик в этой ссылке, использовал для работа в команде SQL Server, занимающейся разработкой сервисного брокера и другими технологиями)

Перейдя от использования синтаксиса "или" к подходу объединения, вы увидите 2 запроса, которые должны поддерживать как можно меньший счетчик логического чтения:

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

Если вы хотите дублировать результаты, используйте «union» вместо «union all».

РЕДАКТИРОВАТЬ: Демонстрация, показывающая, что оптимизатор достаточно умен, чтобы исключить сканирование со значением нулевой переменной в UNION:

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
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...