Условные объединения - Динамический SQL - PullRequest
2 голосов
/ 22 января 2009

Администратор базы данных здесь, на работе, пытается превратить мои простые хранимые процессы в динамическое чудовище sql. Следует признать, что моя хранимая процедура может быть не такой быстрой, как хотелось бы, но я не могу удержаться от мысли, что есть адекватный способ сделать то, что по сути является условным соединением.

Вот пример моего сохраненного процесса:

SELECT 
*
FROM
table
WHERE
(
    @Filter IS NULL OR table.FilterField IN 
    (SELECT Value FROM dbo.udfGetTableFromStringList(@Filter, ','))
)

UDF превращает список фильтров, разделенных запятыми (например, названия банков), в таблицу.

Очевидно, что наличие условия фильтра в предложении where не идеально. Любые предложения о лучшем способе условного объединения на основе сохраненного параметра proc приветствуются. Помимо этого, есть ли у кого-нибудь предложения за или против динамического подхода SQL?

Спасибо

Ответы [ 6 ]

4 голосов
/ 22 января 2009

Вы можете использовать INNER JOIN для таблицы, возвращенной из UDF, вместо использования ее в предложении IN

Ваш UDF может быть что-то вроде

CREATE FUNCTION [dbo].[csl_to_table] (@list varchar(8000) )
RETURNS @list_table TABLE ([id] INT)
AS
BEGIN
    DECLARE     @index INT,
            @start_index INT,
            @id INT

    SELECT @index = 1 
    SELECT @start_index = 1
    WHILE @index <= DATALENGTH(@list)
    BEGIN

        IF SUBSTRING(@list,@index,1) = ','
        BEGIN

            SELECT @id = CAST(SUBSTRING(@list, @start_index, @index - @start_index ) AS INT)
            INSERT @list_table ([id]) VALUES (@id)
            SELECT @start_index = @index + 1
        END
        SELECT @index  = @index + 1
    END
    SELECT @id = CAST(SUBSTRING(@list, @start_index, @index - @start_index ) AS INT)
    INSERT @list_table ([id]) VALUES (@id)
    RETURN
END

и затем INNER JOIN для идентификаторов в возвращаемой таблице. Этот UDF предполагает, что вы передаете INT в вашем списке через запятую

EDIT:

Чтобы обработать нулевое или нулевое значение, передаваемое для @filter, самый простой способ, который я вижу, - это выполнить другой запрос в sproc на основе значения @filter. Я не уверен, как это влияет на кэшированный план выполнения (будет обновляться, если кто-то может подтвердить), или если конечный результат будет быстрее, чем ваш исходный код, я думаю, что ответ здесь будет в тестировании.

2 голосов
/ 22 января 2009

Я не вижу ничего плохого в вашем подходе. Переписать его с использованием динамического SQL для выполнения двух разных запросов в зависимости от того, является ли @Filter нулевым, мне, честно говоря, глупо.

Единственный потенциальный недостаток, который я вижу в том, что у вас есть, это то, что это может вызвать некоторые трудности в определении хорошего плана выполнения. Но если производительность достаточно хорошая, то нет причин ее менять.

2 голосов
/ 22 января 2009

Я не уверен, что понимаю ваше неприятие динамического SQL. Возможно, дело в том, что ваш UDF удачно избавился от некоторой путаницы проблемы, и вы чувствуете, что динамический SQL вернет это обратно. Что ж, учтите, что большинство, если не все инструменты DAL или ORM будут в значительной степени полагаться на динамический SQL, и я думаю, что ваша проблема может быть сформулирована как «как я могу красиво абстрагироваться от грязи динамического SQL».

Со своей стороны, динамический SQL дает мне именно тот запрос, который я хочу, и впоследствии требуемую производительность и поведение.

2 голосов
/ 22 января 2009

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

То есть, когда вы обычно вызываете хранимую процедуру, она выполняется с разрешениями владельца хранимой процедуры, КРОМЕ при выполнении динамического SQL с помощью команды execute, для контекста динамического SQL она возвращается к разрешениям вызывающей стороны. , что может быть нежелательно в зависимости от вашей модели безопасности.

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

1 голос
/ 19 апреля 2009

Похоже, что вы пытаетесь написать один запрос для двух сценариев:
1. @filter = "x, y, z"
2. @filter IS NULL

Чтобы оптимизировать сценарий 2, я бы использовал INNER JOIN для UDF, а не использовал предложение IN ...

SELECT * FROM table
INNER JOIN dbo.udfGetTableFromStringList(@Filter, ',') AS filter
  ON table.FilterField = filter.Value

Чтобы оптимизировать сценарий 2, я НЕ пытался бы адаптировать существующий запрос, вместо этого я намеренно оставил бы эти случаи раздельными, либо оператор IF, либо UNION, и моделировал бы IF с помощью предложения WHERE ...

TSQL IF

IF (@filter IS NULL)
  SELECT * FROM table
ELSE
  SELECT * FROM table
  INNER JOIN dbo.udfGetTableFromStringList(@Filter, ',') AS filter
    ON table.FilterField = filter.Value


СОЮЗ для имитации IF

SELECT * FROM table
  INNER JOIN dbo.udfGetTableFromStringList(@Filter, ',') AS filter
    ON table.FilterField = filter.Value

UNION ALL

SELECT * FROM table WHERE @filter IS NULL

Преимущество таких конструкций в том, что каждый случай прост, и определение того, что просто, само по себе просто. Однако их объединение в один запрос приводит к таким компромиссам, как ЛЕВЫЕ СОЕДИНЕНИЯ, что приводит к значительным потерям производительности для каждого.

1 голос
/ 26 января 2009

Независимо от того, что вы делаете (и ответы здесь все имеют хорошие моменты), обязательно сравните производительность и планы выполнения каждого варианта.

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

Сначала я бы просто посмотрел на изменение IN на простой LEFT JOIN с проверкой NULL (это не избавляет от вашего udf, но его нужно вызвать только один раз):

SELECT *
FROM table
LEFT JOIN dbo.udfGetTableFromStringList(@Filter, ',') AS filter
    ON table.FilterField = filter.Value
WHERE @Filter IS NULL
    OR filter.Value IS NOT NULL
...