Pergunta

Estou tentando usar sp_executesql para impedir a injeção de SQL no SQL 2005, tenho uma consulta simples como esta:

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

No entanto, quando uso o sp_executesql para executar o seguinte, ele não retorna nada.

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

É o que eu testei:

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

Por favor me ajude .... o que eu fiz de errado?

Foi útil?

Solução

A razão pela qual não funciona é porque o @p1 é tratado como um, valor único.

Por exemplo, quando o @code é x101, b202, então a consulta está sendo executada como: selecione * da tabela onde a RegionCode em ('x101, b202'). Mesmo quando você inclui cotações únicas, tudo o que significa é o valor que ele procura no RegionCode deve conter essas cotações únicas.

Você precisaria concatenar a variável @code no texto do comando @cmd sql para que ele funcione da maneira que você está pensando:

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

Obviamente, porém, isso apenas abre você para a injeção de SQL, para que você precise ter muito cuidado se adotasse essa abordagem para garantir que você se proteja contra isso.

Existem maneiras alternativas de lidar com essa situação em que você deseja passar em uma lista dinâmica de valores a serem pesquisados.

Confira os exemplos em meu blog Para 2 abordagens que você pode usar com o SQL Server 2005. Um envolve a passagem de uma lista de CSV no formulário "Value1, Value2, Value3", que você dividiu em uma variável de tabela usando uma função definida pelo usuário (há muitas menções a isso Abordagem se você fizer um Google rápido ou pesquisar neste site). Uma vez dividido, você se junta a essa tabela var à sua consulta principal. A segunda abordagem é passar em uma blob XML contendo os valores e usar a funcionalidade XML interna do SQL Server. Ambas as abordagens são demonstradas com métricas de desempenho nesse link e não requerem SQL dinâmico.

Se você estava usando o SQL Server 2008, os parâmetros de valor da tabela seriam o caminho a seguir - essa é a terceira abordagem que demonstrará nesse link que sai melhor.

Outras dicas

Existem muitas maneiras de dividir a string no SQL Server. Este artigo abrange os prós e contras de praticamente todos os métodos:

"Matrizes e listas no SQL Server 2005 e além, quando os parâmetros do valor da tabela não o cortam", de Erland Sommarskog

Você precisa criar uma função dividida. É assim que uma função dividida pode ser usada:

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

Eu prefiro a abordagem da tabela numérica para dividir uma corda em tsql Mas existem inúmeras maneiras de dividir as cordas no SQL Server, consulte o link anterior, o que explica os prós e contras de cada um.

Para que o método da tabela de números funcione, você precisa fazer isso uma configuração única, que criará uma tabela Numbers que contém linhas de 1 a 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)

Depois que a tabela de números estiver configurada, crie esta função dividida:

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 

Agora você pode dividir facilmente uma string csv em uma tabela e participar ou usá -la como precisar, mesmo dentro do SQL dinâmico. Aqui está como usá -lo na consulta parametrizada dinâmica de sua pergunta:

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

Aqui está uma amostra de trabalho para experimentar (deve ter a tabela de números e a configuração da função dividida primeiro):

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

RESULTADO:

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

(3 row(s) affected)

Parece que o problema é o parâmetro único. Com efeito, você está acabando com:

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

ou

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

Isto é, o código da região deve ser igual a 'X101,B202' ou ''X101','B202'' (a string completa) para trabalhar.

Sua melhor aposta é usar dois parâmetros aqui:

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

Se você tiver mais de dois itens nessa lista, convém seguir outra rota, provavelmente com tabelas temporárias ou parâmetros com valor de tabela.

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top