Muito se fala na internet sobre como as funções escalares são terríveis e que devemos evitar o uso a todo o custo… Eu gostaria de mostrar o porquê:
Vejam o seguinte script abaixo (um procedimento muito utilizado por aí: a utilização de uma função escalar que trata “N” casos e retorna o valor desejado). Primeiro, a criação da tabela e inserção dos dados:
CREATE TABLE NotaFiscal (Cod int identity primary key, Pessoa varchar(20), Valor decimal (15,2), Data date)
CREATE INDEX Ncl_NotaFiscal ON NotaFiscal (Data) INCLUDE (Pessoa, Valor)
GO
-- Inserindo 10 notas no período de 500 dias (5000 notas).
INSERT INTO NotaFiscal
SELECT 'Logan', 100, DATEADD(DD, Number, '2012-12-31')
from Numbers
WHERE Number < 500
GO 10
GO
CREATE FUNCTION CalculaTributos (@Codigo int, @Tipo char(1))
Returns decimal(15,2)
AS
BEGIN
DECLARE @valor decimal(15,2)
SELECT @Valor =
CASE @tipo
WHEN 'a' then valor * 0.1
WHEN 'b' then valor * 0.3
WHEN 'c' then valor * 0.5
END
FROM NotaFiscal
RETURN @valor
END;
Ao executar uma consulta para retornar as notas fiscais no período de um mês utilizando a função escalar, temos o seguinte resultado:
SELECT Cod, Valor, dbo.CalculaTributos(Cod,'a'), dbo.CalculaTributos(Cod,'b'), dbo.CalculaTributos(Cod,'c') FROM NotaFiscal WHERE Data BETWEEN '2013-01-01' AND '2013-02-01' (320 row(s) affected) Table 'notafiscal'. Scan count 1, logical reads 5, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0. SQL Server Execution Times: CPU time = 1997 ms, elapsed time = 2050 ms.
2 segundos para 320 registros… Pouco, você pode imaginar… Mas pense que isto é para uma função, sem nenhuma regra absurda (apenas um case simples e direto) e na minha máquina, onde somente a minha conexão está aberta. Imaginem isso em um servidor com uma alta carga e concorrência.
Se substituirmos essa função por uma função que retorne uma tabela, teremos o seguinte resultado:
CREATE FUNCTION CalculaTributosTbl (@Codigo INT)
Returns Table
AS
RETURN
(
SELECT Cod as Codigo,
valor * 0.1 as ValorA,
valor * 0.3 as ValorB,
valor * 0.5 as ValorC
FROM NotaFiscal
WHERE Cod = @Codigo
);
SELECT
Cod,
Valor,
ValorA,
ValorB,
ValorC
from notafiscal
cross apply dbo.calculatributosTbl(cod)
where data between '2013-01-01' and '2013-02-01'
(320 row(s) affected)
Table 'notafiscal'. Scan count 2, logical reads 27, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
SQL Server Execution Times:
CPU time = 0 ms, elapsed time = 2 ms.Notaram a diferença? Conseguimos reduzir a consulta de 2 segundos para 2 milissegundos, um ganho realmente considerável.
O que eu quero dizer aqui: Sempre que possível, evitem o uso de funções escalares. Vejam se uma TVP pode ser utilizada e, caso contrário, avaliem tratar o dado na aplicação e não no banco, pois o ganho de desempenho pode ser realmente significativo.
Se tiverem algo a agregar e/ou corrigir, por favor, faça… A ideia aqui é passar a informação da melhor forma e mais correta possível.
PS: A tabela Numbers que eu utilizei para a criação dos dados é a versão do Adam Machanic, que pode ser encontrada aqui: http://sqlblog.com/blogs/adam_machanic/archive/2006/07/12/you-require-a-numbers-table.aspx
[]’s!