Neste post mostrarei algumas formas diferentes de fazer consultas dinâmicas.
Em muitos relatórios temos diversos filtros possíveis e temos que criar procedures flexíveis para este cenário.
Vale ressaltar que cada forma tem que ser analisada pro o caso em questão.
Criação do ambiente:
create table #Cliente
(
ID int identity(1,1) primary key,
Nome varchar(50),
Idade int,
Vendas int
)
GO
SET NOCOUNT ON
INSERT INTO #Cliente
VALUES(NEWID(), convert(int,RAND()*100), convert(int,RAND()*100))
GO 100000
SET NOCOUNT OFF
CREATE NONCLUSTERED INDEX IX_CLIENTE_Idade ON #Cliente([Idade]) INCLUDE([Nome])
GO
OPÇÃO 1) EXECUTE - Esta é a mais utilizada
CREATE PROCEDURE usp_ConsultaCliente
@pn_Nome varchar(50)=NULL, @pn_Idade int=NULL, @pn_Vendas int=NULL
AS
BEGIN
DECLARE @SQL varchar(1000)
SET @SQL = 'Select Nome from #Cliente where 1=1'
IF @pn_Idade is not null
SET @SQL = @SQL + ' and Idade=' + convert(varchar(5),@pn_Idade)
IF @pn_Vendas is not null
SET @SQL = @SQL + ' and Vendas=' + convert(varchar(5),@pn_Vendas)
IF @pn_Nome is not null
SET @SQL = @SQL + ' and Nome like ''' + convert(varchar(50),@pn_Nome) + '%'''
EXECUTE(@SQL)
END
GO
OPÇÃO 2)SP_EXECUTESQL
Definição da Microsoft: "Para executar uma cadeia de caracteres, recomendamos que você use o procedimento armazenado sp_executesql em vez da instrução EXECUTE."
O sp_executesql permite definirmos o que é parâmetro dentro da string da consulta e passar estes parâmetros com tipos bem definidos e separadamente, o que facilita ao SQL Server de criar um plano efetivo.
Com isto a probabilidade do SQL Server criar um plano de execução mais efetivo e reutiliza-lo depois é maior.
CREATE PROCEDURE usp_ConsultaCliente2
@pn_Nome varchar(50)=NULL, @pn_Idade int=NULL, @pn_Vendas int=NULL
AS
BEGIN
DECLARE @SQL nvarchar(1000)
DECLARE @Param nvarchar(1000)
SET @SQL = 'Select Nome from #Cliente where 1=1'
IF @pn_Idade is not null
SET @SQL = @SQL + ' and Idade=' + convert(varchar(5),@pn_Idade)
IF @pn_Vendas is not null
SET @SQL = @SQL + ' and Vendas=' + convert(varchar(5),@pn_Vendas)
IF @pn_Nome is not null
SET @SQL = @SQL + ' and Nome like ''' + convert(varchar(50),@pn_Nome) + '%'''
Set @Param='@pn_Nome varchar(50), @pn_Idade int, @pn_Vendas int'
EXECUTE sp_executesql @SQL, @Param, @pn_Nome, @pn_Idade, @pn_Vendas
END
GO
OPÇÃO 3) Utilizando OR e NULL
CREATE PROCEDURE usp_ConsultaCliente3
@pn_Nome varchar(50)=NULL, @pn_Idade int=NULL, @pn_Vendas int=NULL
AS
BEGIN
Select Nome from #Cliente
where (Idade=@pn_Idade or @pn_Idade is null)
AND (Vendas=@pn_Vendas or @pn_Vendas is null)
AND (Nome like @pn_Nome + '%' or @pn_Nome is null)
END
GO
Vamos executar as 3 procedures:
exec usp_ConsultaCliente @pn_Idade=55
exec usp_ConsultaCliente2 @pn_Idade=55
exec usp_ConsultaCliente3 @pn_Idade=55
Analisando as estatísticas de IO temos o seguinte:
(932 row(s) affected)
Table 'Worktable'. Scan count 1, logical reads 10
(932 row(s) affected)
Table 'Worktable'. Scan count 1, logical reads 10
(932 row(s) affected)
Table 'Worktable'. Scan count 1, logical reads 761
Veja que a opção 3 é a que utilizou mais leitura de páginas.
Vamos ver o plano de execução:
Veja que a opção 3 é a que gera mais custos pro servidor.
Coloque a opção OPTION(RECOMPILE) no final da consulta da opção 3 e veja a diferença que ela gera.
O objetivo deste Post é apenas mostrar as 3 opções, num post futuro irei explicar mais internamente cada um destes comandos.