SP_EXECUTESQL com 'In' Declaração
-
25-09-2019 - |
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?
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:
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.