Eu tenho um requisito para recuperar dados de uma tabela com base nos valores separados por vírgula enviados nos parâmetros para o procedimento armazenado. A partir de agora, fiz o código funcionar para um único valor, sem saber como fazê-lo funcionar para vários valores.
Tabela de exemplo:
CREATE TABLE [dbo].FinalStatus
(
[ID] [int] Primary key IDENTITY(1,1) NOT NULL,
[Col1] [varchar](15) NULL,
[Col2] [varchar](15) NULL,
[Col3] [varchar](100) NOT NULL,
[LastUpdatedDate] [datetime] NOT NULL DEFAULT (getdate())
)
Dados de teste:
Insert into FinalStatus (Col1, Col2, Col3) values ('10','ABC21','Msg1')
Insert into FinalStatus (Col1, Col2, Col3) values ('10','ABC21','Msg2')
Insert into FinalStatus (Col1, Col2, Col3) values ('11','C21','Some Msg1')
Insert into FinalStatus (Col1, Col2, Col3) values ('12','BC21','Some Msg2')
Procedimento armazenado:
CREATE PROCEDURE [dbo].[FindResult]
(@col1 VARCHAR(15) = NULL,
@col2 VARCHAR(15) = NULL)
AS
SET NOCOUNT ON
BEGIN
DECLARE @c1 VARCHAR(15)
DECLARE @c2 VARCHAR(15)
SET @c1 = @col1
SET @c2 = @col2
SELECT
Col2, Col1,
LastUpdatedDate, Col3
FROM
dbo.FinalStatus
WHERE
(Col1 = @c1 OR @c1 IS NULL)
AND (Col2 = @c2 OR @c2 IS NULL)
ORDER BY
LastUpdatedDate DESC
END
Script de execução para valor único (funciona até aqui):
--To get all data
EXEC [dbo].[FindResult]
--passing first parameter alone
EXEC [dbo].[FindResult] @col1 = '10', @col2 = NULL
--passing second parameter alone
EXEC [dbo].[FindResult] @col1 = null , @col2 = 'c21'
Agora, como fazê-lo retornar o resultado apropriado mesmo quando passamos vários valores para o parâmetro?
Algo assim:
EXEC [dbo].[FindResult] @col1 = '10,12', @col2 = NULL
EXEC [dbo].[FindResult] @col1 = null , @col2 = 'ABC21, c21'
A tabela subjacente teria no mínimo 100.000 registros em um determinado momento.
Tentei ler "Arrays and Lists in SQL Server 2008" de Erland Sommarskog, mas está muito acima da minha cabeça. Então, procurando alguma ajuda para modificar esse procedimento armazenado.
Existem várias maneiras de fazê-lo. Alterar o modelo de dados também pode ser uma opção melhor se você puder fazê-lo.
1. Parâmetros separados por vírgulas
Se você quiser ficar com a string separada por vírgula, você pode encontrar muitas funções de divisão de string online usando CLR, CTE, XML, ... Eu não vou colocá-los todos aqui e não vou compará-los. Mas você deve saber que todos eles têm seus próprios problemas. Para mais informações, você pode ver este post de Aaron Bertrand : Dividir as cordas da maneira certa - ou da próxima melhor maneira
Consulta de amostra (usando conversão XML):
Resultado:
Consulta principal:
A ideia é convertê-lo em uma tabela. Ele pode então ser usado em uma subconsulta correlacionada:
Apenas a
WHERE
cláusula deve ser atualizada em seu procedimento. Os parâmetros permanecem do tipo varchar com valores separados por vírgula.Resultado:
2. Parâmetro(s) XML
Se você pode alterar os tipos de parâmetro, o tipo de dados XML pode ser usado. Ele pode ser facilmente desserializado pelo procedimento e pela consulta.
Esta consulta é bastante semelhante à anterior. No entanto, oferece um melhor controle sobre valores inválidos e caracteres especiais, pois não há conversão ou pesquisa e substituição.
Os tipos de parâmetro devem ser alterados para xml. Eu usei apenas uma variável com
c1
ec2
nós, mas 1 variável para cada nó (c1, c2, ...) e nomes de nós diferentes também podem ser usados:O caminho e as variáveis corretos devem ser atualizados na
nodes
parte do arquivoCROSS APPLY
.Usando .Net, Powershell ou outras linguagens, um array ou outros tipos podem ser facilmente convertidos para xml e usados como parâmetro do procedimento.
...
3. Tipos de Tabela Definidos pelo Usuário
Outra opção seria criar um tipo de valor de tabela que pudesse ser usado pelos parâmetros do procedimento armazenado.
Tipo de tabela
Procedimento armazenado com parâmetros de tipo de tabela
Em seguida, você atualiza o procedimento armazenado usando este tipo recém-criado:
READONLY
é obrigatório na declaração do parâmetro.Chamada de procedimento armazenado com parâmetros de tipo de tabela
Com o SQL, você pode chamá-lo da mesma maneira usando uma variável de tabela:
Resultado:
Observe que, se você planeja usar mais de algumas linhas para cada parâmetro, deve executar algum teste de desempenho com dados reais e certificar-se de que funcione corretamente em seu sistema.
Chamada .Net C#
O procedimento pode ser chamado de seu código .Net
SqlDbType.Structured
é mandatório.Observe que não sou um especialista em .Net e existem outras maneiras melhores de escrever e usar esse código.
4. Parâmetros de tipo Json (desde o SQL Server 2016)
Com alterações mínimas, você pode declarar um parâmetro NVARCHAR(MAX) com uma string json.
parâmetros json
Consulta
Observe que o OPENJSON requer o SQL Server 2016.
Em algum momento, você tem que dividir essa string. Antes de chamar seu procedimento como sugerido por @MguerraTorres, ou no procedimento. Aqui está um bom exemplo de uma função de divisão de string: http://www.codeproject.com/Articles/7938/SQL-User-Defined-Function-to-Parse-a-Delimited-Str
Essa função retorna um tipo de dados de tabela. Você pode usar essa tabela para consultar as linhas que estão na tabela dividida.
O SQL Server 2016 tem uma função STRING_SPLIT integrada.
Uma solução rápida aqui é alterar sua cláusula where de '=' para 'in' (como um exemplo)
e altere o @c1 para uma lista de valores com aspas simples, por exemplo
Então aqui está o script de procedimento modificado
Agora você pode usar vários valores em seus parâmetros
One option that entails minimal change in the calling code is to pass in simplistic XML. This is very similar to Option 2 in @JulienVavasseur's answer, except that it keeps the two input parameters separate. The thought here is that it should be easy enough to convince the developer to change the existing
String.Join(',', _SomeIntArray)
to instead be something like:'<v>' + String.Join('</v><v>', _SomeIntArray) + '</v>'
. Then you could use them in a query similar to the following, which I left as is since it is a runnable example:Não é possível executar um procedimento armazenado duas vezes para uma única entrada.
Eu sugiro um CURSOR ou Loop que executará o procedimento armazenado uma vez para cada linha desejada na tabela.
Cada iteração de loop irá: 1) Definir duas variáveis para serem iguais a Col1 e Col2 2) Executar o procedimento com as novas Variáveis como Parâmetros de Entrada.
The simplest way I found was to use
FIND_IN_SET
:For example define
values=(1,2,3)
and execute: