Условные соединения - динамический SQL

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

Вот пример моей сохраненной процедуры:

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

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

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

Спасибо

«Администратор базы данных здесь на работе пытается превратить мои простые хранимые процедуры в динамический sql monstrocity». - это смешно, у меня обычно бывает наоборот;)

D'Arcy Rittich 22.01.2009 18:28
ReactJs | Supabase | Добавление данных в базу данных
ReactJs | Supabase | Добавление данных в базу данных
Это и есть ваш редактор таблиц в supabase.👇
Понимание Python и переход к SQL
Понимание Python и переход к SQL
Перед нами лабораторная работа по BloodOath:
2
1
4 911
6

Ответы 6

Вы можете 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 в своем списке, разделенном запятыми.

Обновлено:

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

Но что, если @Filter имеет значение null и не передан? Разве это не приведет к тому, что сохраненная процедура не вернет данных.

Brian Hasden 22.01.2009 17:55

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

То есть, когда вы вызываете хранимую процедуру в обычном режиме, она выполняется с разрешениями владельца хранимой процедуры, ЗА ИСКЛЮЧЕНИЕМ при выполнении динамического SQL с командой execute, для контекста динамического SQL она возвращается обратно к разрешениям вызывающей стороны, что может быть нежелательным в зависимости от вашей модели безопасности.

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

Спасибо, Джон. Это хорошая информация. Есть идеи, как это сделать? По словам Эрланда из sommarskog.se/dyn-search-2005.html, он, кажется, предполагает, что динамический sql - это действительно путь, но в sommarskog.se/dynamic_sql.html#List он предлагает иное. Спасибо.

Brian Hasden 22.01.2009 18:14

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

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

Когда вы используете динамический sql, вы теряете все проверки "времени компиляции". Все становится ошибкой "времени выполнения". Это у меня большая проблема. Даже с «новой и улучшенной» динамической хранимой процедурой sql у администраторов баз данных есть ошибки «времени выполнения».

Brian Hasden 22.01.2009 19:12

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

HLGEM 22.01.2009 19:23

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

D'Arcy Rittich 22.01.2009 23:30

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

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

Мне сказали, что он работает недостаточно хорошо.

Brian Hasden 22.01.2009 19:13

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

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

Сначала я бы просто посмотрел на замену 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

Похоже, вы пытаетесь написать один запрос для двух сценариев:
1. @filter = "x, y, z"
2. @filter ЕСТЬ 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


UNION для моделирования 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

Преимущество таких конструкций состоит в том, что каждый случай прост, а определить, какой из них проще, проще простого. Однако объединение этих двух элементов в один запрос приводит к таким компромиссам, как LEFT JOINs, и, таким образом, приводит к значительной потере производительности для каждого из них.

Другие вопросы по теме