Использование произвольного числа параметров в T-SQL

#redhat-enterprise-linux #rpm

Вопрос:

Возможно ли создать параметризованную инструкцию SQL, которая будет принимать произвольное количество параметров? Я пытаюсь разрешить пользователям фильтровать список на основе нескольких ключевых слов, каждое из которых разделено точкой с запятой. Таким образом, ввод будет чем-то вроде «Окленд;Город;Планирование», и предложение WHERE будет эквивалентно приведенному ниже:

ГДЕ Имя проекта, ТАКОЕ КАК " %Окленд%", И имя проекта, такое как " %Город%", И имя проекта, такое как " %Планирование%"

Создать такой список с помощью объединения действительно легко, но я не хочу использовать такой подход из-за уязвимостей SQL-инъекции. Каковы мои варианты? Должен ли я создать кучу параметров и надеяться, что пользователи никогда не попытаются использовать больше параметров, которые я определил? Или есть способ безопасно создавать параметризованный SQL на лету?

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

Комментарии:

1. Большое спасибо, Кевин, за прекрасное решение. Это было именно то, что я искал.

Ответ №1:

Базовое доказательство концепции… Фактический код был бы меньше, но так как я не знаю ваших имен таблиц/полей, это полный код, поэтому любой может проверить, работает ли он, настроить его и т. Д.

 --Search Parameters

DECLARE @SearchString VARCHAR(MAX)
SET @SearchString='Oakland;City;Planning' --Using your example search
DECLARE @Delim CHAR(1)
SET @Delim=';' --Using your deliminator from the example

--I didn't know your table name, so I'm making it... along with a few extra rows...

DECLARE @Projects TABLE (ProjectID INT, ProjectName VARCHAR(200))
INSERT INTO @Projects (ProjectID, ProjectName) SELECT 1, 'Oakland City Planning'
INSERT INTO @Projects (ProjectID, ProjectName) SELECT 2, 'Oakland City Construction'
INSERT INTO @Projects (ProjectID, ProjectName) SELECT 3, 'Skunk Works'
INSERT INTO @Projects (ProjectID, ProjectName) SELECT 4, 'Oakland Town Hall'
INSERT INTO @Projects (ProjectID, ProjectName) SELECT 5, 'Oakland Mall'
INSERT INTO @Projects (ProjectID, ProjectName) SELECT 6, 'StackOverflow Answer Planning'

--*** MAIN PROGRAM CODE STARTS HERE ***

DECLARE @Keywords TABLE (Keyword VARCHAR(MAX))

DECLARE @index int 
SET @index = -1 

--Each keyword gets inserted into the table
--Single keywords are handled, but I did not add code to remove duplicates
--since that affects performance only, not the result.

WHILE (LEN(@SearchString) > 0) 
  BEGIN  
    SET @index = CHARINDEX(@Delim , @SearchString)  
    IF (@index = 0) AND (LEN(@SearchString) > 0)  
      BEGIN   
        INSERT INTO @Keywords VALUES (@SearchString)
          BREAK  
      END  
    IF (@index > 1)  
      BEGIN   
        INSERT INTO @Keywords VALUES (LEFT(@SearchString, @index - 1))   
        SET @SearchString = RIGHT(@SearchString, (LEN(@SearchString) - @index))  
      END  
    ELSE 
      SET @SearchString = RIGHT(@SearchString, (LEN(@SearchString) - @index)) 
END


--This way, only a project with all of our keywords will be shown...

SELECT * 
FROM @Projects
WHERE ProjectID NOT IN (SELECT ProjectID FROM @Projects Projects INNER JOIN @Keywords Keywords ON CHARINDEX(Keywords.Keyword,Projects.ProjectName)=0)
 

Я решил объединить несколько разных ответов в один 😛

Это предполагает, что вы передадите список ключевых слов поиска с разделителями (переданный через @searchString) в виде VARCHAR(MAX), который-реально-вы не столкнетесь с ограничением для поиска по ключевым словам.

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

Оттуда любое ключевое слово, которое не является частью имени проекта, удаляет этот проект из списка

Таким образом, поиск «Окленд» дает 4 результата, но «Окленд;Город;Планирование» дает только 1 результат.

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

Кроме того, из-за соединений и того, что не вместо динамического SQL, он не рискует внедрением SQL, как вы беспокоились.

Ответ №2:

Возможно, вы также захотите рассмотреть возможность полнотекстового поиска и использования CONTAINS или CONTAINSTABLE для более «естественного» поиска.

Может быть излишним для 1K строк, но он написан и его нелегко разрушить путем инъекции.

Комментарии:

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

2. @Kevin Fairchild: Понятно — я не имел в виду, что не потребуется никакого дополнительного кодирования, только то, что синтаксис поиска и сопоставления уже есть.

3. 🙂 Просто проверяю. Но помимо этого, да… Я люблю Полный Текст. Неважно, сколько Джефф жалуется на это. хе-хе

Ответ №3:

Обычно хитрость заключается в том, чтобы просто передать список в виде строки, разделенной запятыми (в стиле csv), проанализировать эту строку в цикле и динамически построить запрос.

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

Комментарии:

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

Ответ №4:

Как насчет использования типа данных XML для хранения параметров? Он может быть неограничен и собран во время выполнения…

Я передаю неизвестное количество PK для обновления таблицы, а затем загружаю их во временную таблицу. Затем легко обновить, где PK в PKTempTable.

Вот код для анализа типа данных XML…

     INSERT INTO #ERXMLRead (ExpenseReportID)
    SELECT ParamValues.ID.value('.','VARCHAR(20)')
    FROM @ExpenseReportIDs.nodes('/Root/ExpenseReportID') as ParamValues(ID)
 

Ответ №5:

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

У Франса Боума есть отличная статья о хранимых процедурах по сравнению с динамическим sql и о некоторых преимуществах использования генератора SQL по сравнению с использованием инструкций, созданных вручную

Ответ №6:

Если вы используете хранимые процедуры, вы можете включить значение по умолчанию для параметров, затем вы можете передать их или не передавать в клиентском коде, но вам все равно придется объявлять их по отдельности в хранимой процедуре… Кроме того, только если вы используете сохраненный процесс, вы можете передать один параметр в виде строки значений с разделителями и проанализировать отдельные значения внутри sproc (доступны некоторые «стандартные» функции T-SQL, которые разделят записи на динамическую табличную переменную для вас).

Ответ №7:

Если вы используете SQL server 2008, ознакомьтесь с этой статьей, передающей параметр с табличным значением

Ответ №8:

Каким бы способом вы ни пошли, следите за ограничением параметров SQL Server: ~2000 параметров.

Комментарии:

1. Любой, кто столкнется с этим ограничением, должен быть побежден своим собственным сервером. lol

2. Если параметры генерируются автоматически (например, по списку(T).Содержится в LinqToSql), должен ли автогенератор работать с сервером? Будет ли это иметь значение?

Ответ №9:

Как и в некоторых других ответах, вы можете проанализировать строку с разделителями или XML — документ. Смотрите эту превосходную ссылку, которая демонстрирует оба метода с SQL Server.