Как я могу параметризовать запрос, содержащий IN
предложение с переменным числом аргументов, как этот?
SELECT * FROM Tags
WHERE Name IN ('ruby','rails','scruffy','rubyonrails')
ORDER BY Count DESC
В этом запросе число аргументов может быть от 1 до 5.
Я бы предпочел не использовать выделенную хранимую процедуру для этого (или XML), но если есть какой-то элегантный способ, специфичный для SQL Server 2008 , я открыт для этого.
sql
sql-server
parameters
Джефф Этвуд
источник
источник
Ответы:
Вот быстрый и грязный метод, который я использовал:
Итак, вот код C #:
Два предостережения:
LIKE "%...%"
запросы не индексируются.|
пустых или пустых тегов, иначе это не сработаетЕсть и другие способы сделать это, что некоторые люди могут считать чище, поэтому, пожалуйста, продолжайте читать.
источник
Вы можете параметризовать каждое значение, так что-то вроде:
Который даст вам:
Нет, это не открыто для внедрения SQL . Единственный введенный текст в CommandText не основан на вводе пользователем. Он основан исключительно на жестко заданном префиксе "@tag" и индексе массива. Индекс всегда будет целым числом, не генерируется пользователем и является безопасным.
Введенные пользователем значения по-прежнему вставляются в параметры, поэтому там нет уязвимости.
Редактировать:
Не то, чтобы планы кешированных запросов не были ценными, но IMO этот запрос не настолько сложен, чтобы увидеть большую пользу от него. Хотя затраты на компиляцию могут приближаться (или даже превышать) к затратам на выполнение, вы все еще говорите миллисекунды.
Если у вас достаточно оперативной памяти, я ожидаю, что SQL Server, вероятно, также кеширует план для общего количества параметров. Я полагаю, что вы всегда можете добавить пять параметров и позволить неопределенным тегам быть NULL - план запроса должен быть таким же, но он кажется мне довольно уродливым, и я не уверен, что это стоило бы микрооптимизации (хотя, на переполнении стека - это вполне может стоить).
Кроме того, SQL Server 7 и более поздние версии будут автоматически параметризировать запросы , поэтому использование параметров на самом деле не является необходимым с точки зрения производительности - однако это важно с точки зрения безопасности - особенно с такими данными, которые вводятся пользователем.
источник
Для SQL Server 2008 вы можете использовать табличный параметр . Это немного работы, но это, возможно, чище, чем мой другой метод .
Во-первых, вы должны создать тип
Затем ваш код ADO.NET выглядит следующим образом:
источник
SELECT * FROM tags WHERE tags.name IN (SELECT name from @tvp);
? Теоретически, это действительно должен быть самый быстрый подход. Вы можете использовать соответствующие индексы (например, индекс по имени тега, которыйINCLUDE
считается идеальным), и SQL Server должен сделать несколько попыток, чтобы получить все теги и их количество. Как выглядит план?Первоначальный вопрос был «Как я могу параметризовать запрос ...»
Позвольте мне заявить прямо здесь, что это не ответ на первоначальный вопрос. Уже есть некоторые доказательства этого в других хороших ответах.
С учетом сказанного, пометьте этот ответ, опустите его, отметьте его как не ответ ... делайте все, что считаете правильным.
См. Ответ от Марка Брэкетта для предпочтительного ответа, который я (и 231 другой) проголосовал. Подход, приведенный в его ответе, позволяет: 1) эффективно использовать переменные связывания и 2) использовать предикаты, которые можно использовать.
Выбранный ответ
Здесь я хочу остановиться на подходе, представленном в ответе Джоэла Спольски, ответ «выбран» как правильный ответ.
Подход Джоэля Спольски умный. И он работает разумно, он будет демонстрировать предсказуемое поведение и предсказуемую производительность при заданных «нормальных» значениях и с нормативными крайними случаями, такими как NULL и пустая строка. И этого может быть достаточно для конкретного применения.
Но в терминах, обобщающих этот подход, давайте также рассмотрим более неясные угловые случаи, например, когда
Name
столбец содержит подстановочный знак (как распознается предикатом LIKE.) Подстановочный знак, который я вижу наиболее часто используемым, - это%
(знак процента). Итак, давайте разберемся с этим здесь и сейчас, а позже перейдем к другим случаям.Некоторые проблемы с характером%
Рассмотрим значение имени
'pe%ter'
. (Для примеров здесь я использую буквальное строковое значение вместо имени столбца.) Строка со значением Name `pe pe ter будет возвращена запросом формы:Но эта же строка не будет возвращена, если порядок поисковых терминов обратный:
Поведение, которое мы наблюдаем, немного странно. Изменение порядка поисковых терминов в списке приводит к изменению набора результатов.
Почти само собой разумеется, что мы можем не хотеть
pe%ter
соответствовать арахисовому маслу, независимо от того, насколько он любит это.Мрачный угловой чехол
(Да, я согласен, что это неясный случай. Вероятно, тот, который вряд ли будет проверен. Мы не ожидаем подстановочного знака в значении столбца. Мы можем предположить, что приложение предотвращает сохранение такого значения. Но по моему опыту, я редко видел ограничение базы данных, которое специально запрещало символы или шаблоны, которые считались бы подстановочными знаками справа от
LIKE
оператора сравнения.Ямочный ремонт
Один из подходов к исправлению этой дыры - избежать
%
символа подстановки. (Для тех, кто не знаком с оператором escape, здесь есть ссылка на документацию по SQL Server .Теперь мы можем сопоставить буквальный%. Конечно, когда у нас есть имя столбца, нам нужно динамически экранировать шаблон. Мы можем использовать
REPLACE
функцию, чтобы найти вхождения%
символа и вставить символ обратной косой черты перед каждым, например так:Так что это решает проблему с подстановочным знаком%. Почти.
Избежать побега
Мы понимаем, что наше решение создало еще одну проблему. Спасательный персонаж. Мы видим, что нам также нужно будет избегать любых случаев появления экранирующего персонажа. На этот раз мы используем! в качестве побега:
Подчеркивание тоже
Теперь, когда мы находимся в процессе, мы можем добавить еще один
REPLACE
дескриптор подчеркивания. И просто для удовольствия, на этот раз мы будем использовать $ в качестве escape-символа.Я предпочитаю этот подход к экранированию, потому что он работает в Oracle и MySQL, а также в SQL Server. (Я обычно использую \ backslash в качестве escape-символа, так как это символ, который мы используем в регулярных выражениях. Но зачем ограничиваться соглашением!
Эти надоедливые скобки
SQL Server также позволяет обрабатывать символы подстановки как литералы, заключая их в квадратные скобки
[]
. Так что мы еще не закончили исправление, по крайней мере для SQL Server. Поскольку пары скобок имеют особое значение, нам также нужно избегать их. Если нам удастся правильно убрать скобки, то, по крайней мере, нам не придется беспокоиться о дефисе-
и карате^
в скобках. И мы можем оставить любой%
и_
символы внутри скобок экранированными, так как мы в основном отключили специальное значение скобок.Поиск подходящих пар скобок не должен быть таким сложным. Это немного сложнее, чем обрабатывать вхождения singleton% и _. (Обратите внимание, что недостаточно просто избежать всех вхождений скобок, потому что одиночная скобка считается литералом, и ее не нужно экранировать. Логика становится немного размытой, чем я могу справиться, не выполняя больше тестовых случаев .)
Встроенное выражение становится грязным
Это встроенное выражение в SQL становится длиннее и уродливее. Мы, вероятно, можем заставить это работать, но небеса помогают бедной душе, которая приходит и должна расшифровать это. Как большой поклонник я для встроенных выражений, я склонен не использовать его здесь, главным образом потому, что я не хочу оставлять комментарий, объясняющий причину беспорядка, и извиняюсь за это.
Функция где?
Итак, если мы не будем обрабатывать это как встроенное выражение в SQL, ближайшая альтернатива - это пользовательская функция. И мы знаем, что это ничего не ускорит (если мы не можем определить индекс для него, как мы могли бы с Oracle.) Если нам нужно создать функцию, мы могли бы лучше сделать это в коде, который вызывает SQL заявление.
И эта функция может иметь некоторые различия в поведении, в зависимости от СУБД и версии. (Привет всем разработчикам Java, которые заинтересованы в взаимозаменяемости использования любого механизма базы данных.)
Базовые знания
Мы , возможно, специальные знания в области для столбца, (то есть, набор допустимых значений насильственных для столбца. Мы можем знать априорно , что значения , сохраненные в столбце никогда не будут содержать знак процента, подчеркивание, или кронштейну пары. В этом случае мы просто включаем быстрый комментарий, что эти случаи покрыты.
Значения, хранящиеся в столбце, могут допускать использование символов% или _, но ограничение может потребовать экранирования этих значений, возможно, с использованием определенного символа, так что эти значения LIKE сравнения "безопасны". Опять же, быстрый комментарий о допустимом наборе значений и, в частности, о том, какой символ используется в качестве escape-символа, и он соответствует подходу Джоэла Спольски.
Но, при отсутствии специальных знаний и гарантии, для нас важно, по крайней мере, рассмотреть возможность обработки этих неясных угловых случаев и рассмотреть, является ли поведение разумным и «в соответствии со спецификацией».
Другие вопросы перепросмотрены
Я полагаю, что другие уже в достаточной степени указали на некоторые из других широко обсуждаемых проблемных областей:
Внедрение SQL (взятие того, что представляется информацией, предоставленной пользователем, и включение ее в текст SQL, а не предоставление их через переменные связывания. Использование переменных связывания не требуется, это всего лишь один удобный подход, чтобы помешать внедрению SQL. Есть и другие способы борьбы с этим:
план оптимизатора с использованием сканирования индекса, а не поиска индекса; возможная потребность в выражении или функции для экранирования подстановочных знаков (возможный индекс в выражении или функции)
использование литеральных значений вместо переменных связывания влияет на масштабируемость
Вывод
Мне нравится подход Джоэла Спольски. Это умно. И это работает.
Но как только я это увидел, я сразу же увидел потенциальную проблему, и не в моем характере позволять этому скользить. Я не хочу критиковать усилия других. Я знаю, что многие разработчики относятся к своей работе очень лично, потому что они так много в нее вкладывают, и они так заботятся об этом. Поэтому, пожалуйста, поймите, это не личная атака. То, что я определяю здесь, это тип проблемы, которая возникает в процессе производства, а не тестирования.
Да, я далеко ушёл от первоначального вопроса. Но где еще оставить эту заметку о том, что я считаю важной проблемой с «отобранным» ответом на вопрос?
источник
Вы можете передать параметр в виде строки
Итак, у вас есть строка
Затем все, что вам нужно сделать, это передать строку как 1 параметр.
Вот функция разделения, которую я использую.
источник
Я слышал, как Джефф / Джоэл говорил об этом на подкасте сегодня ( серия 34 , 2008-12-16 (MP3, 31 МБ), 1 ч 03 мин 38 с - 1 ч 06 мин 45 с), и мне показалось, что я вспомнил переполнение стека использовал LINQ to SQL , но, возможно, он был исключен. Вот то же самое в LINQ to SQL.
Вот и все. И, да, LINQ уже выглядит достаточно задом наперед, но этот
Contains
пункт кажется мне слишком задом наперед. Когда мне приходилось делать аналогичный запрос для проекта на работе, я, естественно, пытался сделать это неправильно, выполнив объединение между локальным массивом и таблицей SQL Server, полагая, что транслятор LINQ to SQL будет достаточно умен для обработки перевод как-то. Это не так, но оно предоставило сообщение об ошибке, которое было описательным и указывало на использование Contains .В любом случае, если вы запустите это в настоятельно рекомендуемом LINQPad и выполните этот запрос, вы сможете просмотреть фактический SQL, сгенерированный поставщиком SQL LINQ. Он покажет вам каждое из значений, параметризованных в
IN
предложении.источник
Если вы звоните из .NET, вы можете использовать Dapper dot net :
Здесь Даппер думает, так что тебе не нужно. Нечто подобное возможно с LINQ to SQL , конечно:
источник
Возможно, это наполовину противный способ сделать это, я использовал его однажды, был довольно эффективным.
В зависимости от ваших целей это может быть полезным.
INSERT
каждое значение поиска в этом столбце.IN
вы можете просто использовать ваши стандартныеJOIN
правила. (Гибкость ++)Это дает некоторую дополнительную гибкость в том, что вы можете сделать, но больше подходит для ситуаций, когда у вас есть большая таблица для запроса, с хорошей индексацией и вы хотите использовать параметризованный список более одного раза. Избавляет от необходимости выполнить его дважды и выполнить все санитарные операции вручную.
Я никогда не удосужился понять, насколько быстро это было, но в моей ситуации это было необходимо.
источник
В
SQL Server 2016+
вы можете использоватьSTRING_SPLIT
функцию:или:
LiveDemo
Общепринятый ответ будет, конечно , работа , и это один из способов идти, но это анти-модель.
Приложение :
Чтобы улучшить
STRING_SPLIT
оценку строк табличной функции, рекомендуется материализовать разделенные значения как временную переменную таблицы / таблицы:SEDE - Live Demo
Связанный: Как передать список значений в хранимую процедуру
Оригинальный вопрос имеет требование
SQL Server 2008
. Поскольку этот вопрос часто используется как дубликат, я добавил этот ответ в качестве ссылки.источник
У нас есть функция, которая создает табличную переменную, к которой вы можете присоединиться:
Так:
источник
Это брутто, но если вам гарантированно будет хотя бы один, вы можете сделать:
Наличие IN ('tag1', 'tag2', 'tag1', 'tag1', 'tag1') будет легко оптимизировано SQL Server. Кроме того, вы получаете прямой индекс ищет
источник
На мой взгляд, лучшим источником для решения этой проблемы является то, что было размещено на этом сайте:
Syscomments. Динакар Нети
Использование:
КРЕДИТЫ ДЛЯ: Динакар Нети
источник
Я бы передавал параметр типа таблицы (поскольку это SQL Server 2008 ) и выполнял
where exists
или внутреннее соединение. Вы также можете использовать XML, используяsp_xml_preparedocument
, а затем даже индексировать эту временную таблицу.источник
ИМХО правильным способом является сохранение списка в символьной строке (длина которой ограничена тем, что поддерживает СУБД); единственная хитрость в том, что (для упрощения обработки) у меня есть разделитель (запятая в моем примере) в начале и в конце строки. Идея состоит в том, чтобы «нормализовать на лету», превратив список в таблицу с одним столбцом, которая содержит одну строку на значение. Это позволяет вам включить
в
или (решение, которое я, вероятно, предпочел бы) для обычного объединения, если вы просто добавляете «отличное», чтобы избежать проблем с дублирующимися значениями в списке.
К сожалению, методы нарезки строки довольно специфичны для продукта. Вот версия SQL Server:
Версия Oracle:
и версия MySQL:
(Конечно, «сводка» должна возвращать столько строк, сколько максимальное количество элементов мы можем найти в списке)
источник
Если у вас SQL Server 2008 или более поздняя версия, я бы использовал параметр с табличным значением .
Если вам не повезло застрять в SQL Server 2005, вы можете добавить функцию CLR, например:
Который вы могли бы использовать, как это,
источник
Я думаю, что это тот случай, когда статический запрос просто не подходит. Динамически создавайте список для предложения in, экранируйте одинарные кавычки и динамически создавайте SQL. В этом случае вы, вероятно, не увидите большой разницы с любым методом из-за небольшого списка, но самый эффективный метод на самом деле - отправлять SQL точно так, как написано в вашем посте. Я думаю, что это хорошая привычка - писать это наиболее эффективным способом, а не делать то, что делает код самым красивым, или считать плохой практикой динамическую сборку SQL.
Я видел, что функции разделения занимают больше времени, чем сами запросы, во многих случаях, когда параметры становятся большими. Хранимая процедура с табличными параметрами в SQL 2008 - это единственный вариант, который я бы рассмотрел, хотя в вашем случае это, вероятно, будет медленнее. Вероятно, TVP будет быстрее только для больших списков, если вы будете искать по первичному ключу TVP, потому что SQL все равно создаст временную таблицу для списка (если список большой). Вы не будете знать наверняка, если вы не проверите это.
Я также видел хранимые процедуры, которые имели 500 параметров со значениями по умолчанию, равными null, и имели WHERE Column1 IN (@ Param1, @ Param2, @ Param3, ..., @ Param500). Это привело к тому, что SQL построил временную таблицу, выполнил сортировку / изменение и затем просмотр таблицы вместо поиска по индексу. По сути, это то, что вы будете делать, параметризуя этот запрос, хотя и в достаточно малом масштабе, чтобы он не имел заметного значения. Я настоятельно рекомендую не указывать значение NULL в ваших списках IN, так как если оно будет изменено на NOT IN, оно не будет работать так, как задумано. Вы можете динамически построить список параметров, но единственное очевидное, что вы получите, это то, что объекты будут выходить из одинарных кавычек. Этот подход также немного медленнее на стороне приложения, так как объекты должны анализировать запрос, чтобы найти параметры.
Повторное использование планов выполнения для хранимых процедур или параметризованных запросов может дать вам выигрыш в производительности, но оно привязывает вас к одному плану выполнения, определенному первым выполненным запросом. Это может быть не идеально для последующих запросов во многих случаях. В вашем случае повторное использование планов выполнения, вероятно, будет плюсом, но это может вообще не иметь никакого значения, поскольку пример - действительно простой запрос.
Примечания скал:
Для вашего случая все, что вы делаете, будь то параметризация с фиксированным числом элементов в списке (ноль, если не используется), динамическое построение запроса с параметрами или без параметров или использование хранимых процедур с параметрами с табличными параметрами, не будет иметь большого значения , Тем не менее, мои общие рекомендации таковы:
Ваш случай / простые запросы с несколькими параметрами:
Динамический SQL, возможно с параметрами, если тестирование показывает лучшую производительность.
Запросы с повторно используемыми планами выполнения, вызываемые несколько раз простым изменением параметров или сложным запросом:
SQL с динамическими параметрами.
Запросы с большими списками:
Хранимая процедура с табличными параметрами. Если список может сильно варьироваться, используйте WITH RECOMPILE для хранимой процедуры или просто используйте динамический SQL без параметров, чтобы сгенерировать новый план выполнения для каждого запроса.
источник
Может быть, мы можем использовать XML здесь:
источник
CTE
и@x
могут быть исключены / включены в подвыбор, если это сделано очень тщательно, как показано в этой статье .Я бы подошел к этому по умолчанию с передачей табличной функции (которая возвращает таблицу из строки) в условие IN.
Вот код для UDF (я получил его где-то от переполнения стека, я не могу найти источник прямо сейчас)
Как только вы получите это, ваш код будет таким простым:
Если у вас нет смехотворно длинной строки, это должно хорошо работать с индексом таблицы.
При необходимости вы можете вставить его во временную таблицу, проиндексировать, а затем запустить соединение ...
источник
Другое возможное решение - вместо передачи переменного числа аргументов хранимой процедуре, передать одну строку, содержащую имена, которые вы ищете, но сделайте их уникальными, заключив их в '<>'. Затем используйте PATINDEX, чтобы найти имена:
источник
Используйте следующую хранимую процедуру. Он использует пользовательскую функцию разделения, которую можно найти здесь .
источник
Если в предложении IN хранятся строки с разделителями-запятыми (,), мы можем использовать функцию charindex для получения значений. Если вы используете .NET, то вы можете сопоставить с SqlParameters.
Сценарий DDL:
T-SQL:
Вы можете использовать приведенный выше оператор в своем коде .NET и сопоставить параметр с SqlParameter.
Fiddler demo
РЕДАКТИРОВАТЬ: Создайте таблицу с именем SelectedTags, используя следующий скрипт.
Сценарий DDL:
T-SQL:
источник
Для переменного числа аргументов, подобных этому, единственный известный мне способ состоит в том, чтобы либо сгенерировать SQL явно, либо сделать что-то, что включает заполнение временной таблицы нужными элементами и соединение с временной таблицей.
источник
В ColdFusion мы просто делаем:
источник
Вот техника, которая воссоздает локальную таблицу для использования в строке запроса. Делая это таким образом, устраняет все проблемы синтаксического анализа.
Строка может быть построена на любом языке. В этом примере я использовал SQL, поскольку это была первоначальная проблема, которую я пытался решить. Мне нужен был чистый способ передачи табличных данных на лету в строку, которая будет выполнена позже.
Использование определенного пользователем типа не является обязательным. Создание типа создается только один раз и может быть сделано заранее. В противном случае просто добавьте полный тип таблицы к объявлению в строке.
Общий шаблон легко расширяется и может использоваться для передачи более сложных таблиц.
источник
В SQL Server 2016+ другой возможностью является использование этой
OPENJSON
функции.Этот подход описан в OPENJSON - одном из лучших способов выбора строк по списку идентификаторов .
Полный рабочий пример ниже
источник
Вот еще одна альтернатива. Просто передайте список через запятую как строковый параметр хранимой процедуре и:
И функция:
источник
У меня есть ответ, который не требует UDF, XML, потому что IN принимает оператор выбора, например, SELECT * FROM Test, где Data IN (SELECT Value FROM TABLE)
Вам действительно нужен только способ преобразования строки в таблицу.
Это можно сделать с помощью рекурсивного CTE или запроса с таблицей чисел (или Master..spt_value).
Вот версия CTE.
источник
Я использую более краткую версию ответа с наибольшим количеством голосов :
Это делает цикл через параметры тега дважды; но это не имеет значения большую часть времени (это не будет вашим узким местом; если это так, разверните цикл).
Если вы действительно заинтересованы в производительности и не хотите повторять цикл дважды, вот менее красивая версия:
источник
Вот еще один ответ на эту проблему.
(новая версия опубликована 04.06.13).
Приветствия.
источник
Единственный выигрышный ход - не играть.
Нет бесконечной изменчивости для вас. Только конечная изменчивость.
В SQL у вас есть такое предложение:
В коде C # вы делаете что-то вроде этого:
Таким образом, в основном, если счетчик равен 0, тогда нет фильтра, и все проходит. Если число больше 0, тогда значение должно быть в списке, но список был дополнен до пяти с невозможными значениями (так что SQL все еще имеет смысл)
Иногда хромое решение - единственное, которое действительно работает.
источник