sp_executesql с оператором 'IN' - PullRequest
       33

sp_executesql с оператором 'IN'

3 голосов
/ 08 апреля 2010

Я пытаюсь использовать sp_executesql для предотвращения внедрения SQL в SQL 2005, у меня есть простой запрос вроде этого:

SELECT * from table WHERE RegionCode in ('X101', 'B202')

Однако, когда я использую sp_executesql для выполнения следующего, он ничего не возвращает.

Set @Cmd = N'SELECT * FROM table WHERE RegionCode in (@P1)'
SET @ParamDefinition = N'@P1 varchar(100)';
DECLARE @Code as nvarchar(100);
SET @Code = 'X101,B202'
EXECUTE sp_executesql @Cmd, @ParamDefinition, @P1 = @Code

Это то, что я проверил:

SET @Code = 'X101'   <-- This works, it returns a single region
SET @Code = 'X101,B202'   <--- Returns nothing
SET @Code = '''X101'',''B202'''  <-- Returns nothing

Пожалуйста, помогите .... что я сделал не так?

Ответы [ 3 ]

3 голосов
/ 08 апреля 2010

Причина, по которой это не работает, заключается в том, что @ P1 рассматривается как одно, одно значение.

например. когда @Code - X101, B202, тогда запрос просто выполняется как: SELECT * FROM Table, ГДЕ RegionCode IN ('X101, B202') Итак, он ищет RegionCode со значением, которое содержится в @ P1. Даже если вы включаете одинарные кавычки, все, что означает, - это значение, которое он ищет в RegionCode, как ожидается, будет содержать эти одинарные кавычки.

Вам нужно было бы на самом деле объединить переменную @Code в текст команды @Cmd sql, чтобы она работала так, как вы думаете:

SET @Code = '''X101'',''B202'''
SET @Cmd = 'SELECT * FROM Table WHERE RegionCode IN (' + @Code + ')'
EXECUTE (@Cmd)

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

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

Ознакомьтесь с примерами в моем блоге для двух подходов, которые вы можете использовать с SQL Server 2005. Один из них включает передачу списка CSV в форме «Value1, Value2, Value3», которую вы затем разбиваете на переменная TABLE с использованием определенной пользователем функции (есть много упоминаний об этом подходе, если вы делаете быстрый Google или поиск по этому сайту). После разделения вы присоединяете эту переменную TABLE к своему основному запросу. Второй подход заключается в передаче большого двоичного объекта XML, содержащего значения, и использовании встроенной функции XML SQL Server. Оба эти подхода демонстрируются с показателями производительности в этой ссылке, и они не требуют динамического SQL.

Если бы вы использовали SQL Server 2008, лучше всего было бы использовать Table Value Parameters - это третий подход, который я продемонстрировал в этой ссылке, которая лучше всего получается.

2 голосов
/ 08 апреля 2010

Есть много способов разбить строку в SQL Server.В этой статье рассматриваются преимущества и недостатки практически каждого метода:

«Массивы и списки в SQL Server 2005 и более поздних версиях, когда параметры табличных значений не сокращаются», Эрланд Соммарског

Вам необходимо создать функцию разделения.Вот как можно использовать функцию разделения:

SELECT
    *
    FROM YourTable                               y
    INNER JOIN dbo.yourSplitFunction(@Parameter) s ON y.ID=s.Value

Я предпочитаю подход с таблицей чисел для разделения строки в TSQL , но в SQL Server существует множество способов разделения строк, см.предыдущая ссылка, которая объясняет за и против каждого из них.

Чтобы метод таблицы чисел работал, вам нужно выполнить эту настройку таблицы времени, которая создаст таблицу Numbers, содержащую строки из 1до 10 000:

SELECT TOP 10000 IDENTITY(int,1,1) AS Number
    INTO Numbers
    FROM sys.objects s1
    CROSS JOIN sys.objects s2
ALTER TABLE Numbers ADD CONSTRAINT PK_Numbers PRIMARY KEY CLUSTERED (Number)

После настройки таблицы чисел создайте эту функцию разделения:

CREATE FUNCTION [dbo].[FN_ListToTable]
(
     @SplitOn  char(1)      --REQUIRED, the character to split the @List string on
    ,@List     varchar(8000)--REQUIRED, the list to split apart
)
RETURNS TABLE
AS
RETURN 
(

    ----------------
    --SINGLE QUERY-- --this will not return empty rows
    ----------------
    SELECT
        ListValue
        FROM (SELECT
                  LTRIM(RTRIM(SUBSTRING(List2, number+1, CHARINDEX(@SplitOn, List2, number+1)-number - 1))) AS ListValue
                  FROM (
                           SELECT @SplitOn + @List + @SplitOn AS List2
                       ) AS dt
                      INNER JOIN Numbers n ON n.Number < LEN(dt.List2)
                  WHERE SUBSTRING(List2, number, 1) = @SplitOn
             ) dt2
        WHERE ListValue IS NOT NULL AND ListValue!=''

);
GO 

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

DECLARE @Cmd as nvarchar(1000),@ParamDefinition nvarchar(1000);
Set @Cmd = N'SELECT * FROM table WHERE RegionCode in (SELECT ListValue FROM dbo.FN_ListToTable('','',@P1))'
SET @ParamDefinition = N'@P1 varchar(100)';
DECLARE @Code as nvarchar(1000);
SET @Code = 'X101,B202'
EXECUTE sp_executesql @Cmd, @ParamDefinition, @P1 = @Code

ЗДЕСЬ - это рабочий пример, который нужно опробовать (сначала должна быть таблица чисел и настройка функции разделения):

CREATE TABLE YourTable (PK int primary key, RowValue varchar(5))
INSERT YourTable VALUES (1,'A')
INSERT YourTable VALUES (2,'BB')
INSERT YourTable VALUES (3,'CCC')
INSERT YourTable VALUES (4,'DDDD')
INSERT YourTable VALUES (5,'EEE')
INSERT YourTable VALUES (6,'FF')
INSERT YourTable VALUES (7,'G')

DECLARE @SQL              nvarchar(1000)
       ,@ParamDefinition  nvarchar(1000)
       ,@ParamValue       varchar(100)
SELECT @SQL = N'SELECT * FROM YourTable WHERE PK IN (SELECT ListValue FROM dbo.FN_ListToTable('','',@P1))'
      ,@ParamDefinition = N'@P1 varchar(100)'
      ,@ParamValue = '2,4,,,6,,8,,2,,4'
EXECUTE sp_executesql @SQL, @ParamDefinition, @P1 = @ParamValue

ВЫХОД:

PK          RowValue
----------- --------
2           BB
4           DDDD
6           FF

(3 row(s) affected)
1 голос
/ 08 апреля 2010

Похоже, проблема в одном параметре. В результате вы получите:

SELECT * from table WHERE RegionCode in ('X101,B202')

или

SELECT * from table WHERE RegionCode in ('''X101'', ''B202''')

То есть RegionCode должен быть равен 'X101,B202' или ''X101','B202'' (полная строка).

Лучше всего использовать здесь два параметра:

Set @Cmd = N'SELECT * FROM table WHERE RegionCode in (@P1,@P2)'
SET @Code1 = 'X101'
SET @Code2 = 'B202'

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

...