Funções escalares, funções de tabela (inline e multi‑statement), uso prático, desempenho e boas práticas.
Funções (UDFs) encapsulam lógica reutilizável. Podem receber parâmetros e retornar um valor escalares ou uma tabela.
| Tipo | Retorno | Uso comum |
|---|---|---|
| Escalar | Um único valor | Cálculos, formatações e validações simples |
| De Tabela (iTVF) | Consulta inline (SELECT) | Filtros reutilizáveis, junções com APPLY |
| De Tabela (mTVF) | Tabela preenchida em etapas | Composições de lógica em múltiplos passos |
INSERT/UPDATE/DELETE diretos). Foque em cálculo/transformação.Retornam um valor único. Úteis para padronizar regras de negócio de baixo acoplamento.
CREATE OR ALTER FUNCTION dbo.NomeDaFuncao (@Parametro Tipo)
RETURNS TipoDeRetorno
AS
BEGIN
DECLARE @Resultado TipoDeRetorno;
-- Processamento
SET @Resultado = <expressão>;
RETURN @Resultado;
END;
CREATE OR ALTER FUNCTION dbo.CalcularIdade (@DataNascimento date)
RETURNS int
AS
BEGIN
DECLARE @Idade int = DATEDIFF(YEAR, @DataNascimento, GETDATE());
IF (MONTH(@DataNascimento) > MONTH(GETDATE())
OR (MONTH(@DataNascimento) = MONTH(GETDATE()) AND DAY(@DataNascimento) > DAY(GETDATE())))
SET @Idade -= 1;
RETURN @Idade;
END;
GO
-- Uso
SELECT Nome, dbo.CalcularIdade(DataNascimento) AS Idade
FROM dbo.Clientes;
DATEDIFF(YEAR,...) apenas compara números de ano. O ajuste garante precisão antes do aniversário.CREATE OR ALTER FUNCTION dbo.FormatarCPF (@CPF varchar(11))
RETURNS varchar(14)
AS
BEGIN
RETURN SUBSTRING(@CPF,1,3) + '.' +
SUBSTRING(@CPF,4,3) + '.' +
SUBSTRING(@CPF,7,3) + '-' +
SUBSTRING(@CPF,10,2);
END;
GO
SELECT Nome, dbo.FormatarCPF(CPF) AS CPF_Formatado
FROM dbo.Clientes;
Escritas como um SELECT retornado diretamente. De fácil otimização, ideais para filtros reutilizáveis.
CREATE OR ALTER FUNCTION dbo.ClientesPorCidade (@Cidade nvarchar(100))
RETURNS table
AS
RETURN
(
SELECT IdCliente, Nome, Cidade, UF
FROM dbo.Clientes
WHERE Cidade = @Cidade
);
GO
APPLY-- Combinar uma iTVF com cada linha de outra fonte:
SELECT v.IdVenda, v.IdCliente, c.Nome, c.Cidade
FROM dbo.Vendas AS v
CROSS APPLY dbo.ClientesPorCidade('São Paulo') AS c
WHERE c.IdCliente = v.IdCliente;
Permitem múltiplas operações para compor a tabela de retorno.
CREATE OR ALTER FUNCTION dbo.ClientesAtivosPorEstado (@UF char(2))
RETURNS @T table
(
IdCliente int,
Nome nvarchar(100),
Cidade nvarchar(100),
UF char(2)
)
AS
BEGIN
INSERT INTO @T (IdCliente, Nome, Cidade, UF)
SELECT IdCliente, Nome, Cidade, UF
FROM dbo.Clientes
WHERE UF = @UF AND Ativo = 1;
-- Etapas adicionais (quando necessário) podem enriquecer @T aqui.
RETURN;
END;
GO
SELECT * FROM dbo.ClientesAtivosPorEstado('SP');
Defina valores padrão para reduzir repetição.
CREATE OR ALTER FUNCTION dbo.CalcularDesconto
(
@Valor decimal(10,2),
@Percentual decimal(5,2) = 10.0 -- padrão 10%
)
RETURNS decimal(10,2)
AS
BEGIN
RETURN @Valor - (@Valor * (@Percentual / 100.0));
END;
GO
SELECT dbo.CalcularDesconto(1000, 15) AS DescontoPersonalizado; -- 850,00
SELECT dbo.CalcularDesconto(1000) AS DescontoPadrao; -- 900,00
-- LEN: comprimento (ignora espaços à direita)
SELECT LEN('ABC ') AS Tamanho; -- 3
-- UPPER / LOWER: caixa alta/baixa
SELECT UPPER('sql server'), LOWER('T-SQL');
-- SUBSTRING + CHARINDEX: recortes localizados
DECLARE @email nvarchar(100) = N"pessoa@empresa.com";
SELECT SUBSTRING(@email, 1, CHARINDEX('@', @email)-1) AS Usuario; -- "pessoa"
-- REPLACE: substituição
SELECT REPLACE('11.222.333/0001-44', '.', '') AS SemPontos;
-- LTRIM/RTRIM / TRIM: remoção de espaços
SELECT LTRIM(RTRIM(' texto ')) AS SemEspacos;
-- ABS (valor absoluto), SIGN (sinal), POWER (potência)
SELECT ABS(-10) AS Abs10, SIGN(-20) AS Sinal, POWER(2,10) AS DoisElevado10;
-- ROUND, CEILING, FLOOR
SELECT ROUND(123.4567, 2) AS Arred2, CEILING(9.01) AS PraCima, FLOOR(9.99) AS PraBaixo;
SELECT GETDATE() AS Agora,
SYSDATETIME() AS AgoraPrecisao;
-- DATEDIFF / DATEADD
SELECT DATEDIFF(DAY, '2025-01-01', GETDATE()) AS DiasNoAno;
SELECT DATEADD(MONTH, 3, '2025-01-15') AS Mais3Meses;
-- EOMONTH: fim do mês
SELECT EOMONTH('2025-02-10') AS FimFevereiro;
-- ISNULL / COALESCE: tratamento de nulos
SELECT ISNULL(NULL, 'valor') AS IsNullEx, COALESCE(NULL, NULL, 'primeiro não nulo');
-- NULLIF: retorna NULL se iguais
SELECT NULLIF(10, 10) AS Nulo, NULLIF(10, 5) AS Dez;
-- IIF: expressão condicional simples
SELECT IIF(5 > 3, 'maior', 'menor');
-- TRY_CONVERT: conversão segura (retorna NULL se falhar)
SELECT TRY_CONVERT(int, '123') AS Ok, TRY_CONVERT(int, 'abc') AS FalhaNull;
WHERE/JOIN (quebra SARGability). Considere pré‑calcular/normalizar.APPLY para parametrização linha‑a‑linha com iTVFs.TRY_CONVERT.schema (ex.: dbo.).WITH SCHEMABINDING e determinismo.-- Ambiente de testes
IF OBJECT_ID('dbo.Vendas','U') IS NOT NULL DROP TABLE dbo.Vendas;
IF OBJECT_ID('dbo.Clientes','U') IS NOT NULL DROP TABLE dbo.Clientes;
GO
CREATE TABLE dbo.Clientes
(
IdCliente int IDENTITY PRIMARY KEY,
Nome nvarchar(100) NOT NULL,
CPF varchar(11) NULL,
DataNascimento date NULL,
Cidade nvarchar(100) NULL,
UF char(2) NULL,
Ativo bit NOT NULL DEFAULT 1
);
CREATE TABLE dbo.Vendas
(
IdVenda int IDENTITY PRIMARY KEY,
IdCliente int NOT NULL REFERENCES dbo.Clientes(IdCliente),
DataVenda date NOT NULL,
Valor decimal(10,2) NOT NULL
);
GO
INSERT INTO dbo.Clientes (Nome, CPF, DataNascimento, Cidade, UF, Ativo) VALUES
(N'João Silva', '11122233344', '1993-05-10', N'São Paulo', 'SP', 1),
(N'Ana Souza', '55566677788', '1997-09-21', N'Campinas', 'SP', 1),
(N'Bruno Lima', NULL, '1985-12-02', N'Curitiba', 'PR', 0);
INSERT INTO dbo.Vendas (IdCliente, DataVenda, Valor) VALUES
(1, '2025-08-01', 1200.00),
(1, '2025-08-15', 350.50),
(2, '2025-08-10', 999.90);
GO
-- Escalar: Idade
CREATE OR ALTER FUNCTION dbo.CalcularIdade (@DataNascimento date)
RETURNS int
AS
BEGIN
DECLARE @Idade int = DATEDIFF(YEAR, @DataNascimento, GETDATE());
IF (MONTH(@DataNascimento) > MONTH(GETDATE())
OR (MONTH(@DataNascimento) = MONTH(GETDATE()) AND DAY(@DataNascimento) > DAY(GETDATE())))
SET @Idade -= 1;
RETURN @Idade;
END;
GO
-- Escalar: Formatar CPF
CREATE OR ALTER FUNCTION dbo.FormatarCPF (@CPF varchar(11))
RETURNS varchar(14)
AS
BEGIN
IF @CPF IS NULL OR LEN(@CPF) <> 11 OR TRY_CONVERT(bigint, @CPF) IS NULL
RETURN NULL; -- validação simples
RETURN SUBSTRING(@CPF,1,3) + '.' + SUBSTRING(@CPF,4,3) + '.' +
SUBSTRING(@CPF,7,3) + '-' + SUBSTRING(@CPF,10,2);
END;
GO
-- iTVF: Clientes por cidade
CREATE OR ALTER FUNCTION dbo.ClientesPorCidade (@Cidade nvarchar(100))
RETURNS table
AS
RETURN
(
SELECT IdCliente, Nome, Cidade, UF
FROM dbo.Clientes
WHERE Cidade = @Cidade
);
GO
-- mTVF: Clientes ativos por estado
CREATE OR ALTER FUNCTION dbo.ClientesAtivosPorEstado (@UF char(2))
RETURNS @T table
(
IdCliente int,
Nome nvarchar(100),
Cidade nvarchar(100),
UF char(2)
)
AS
BEGIN
INSERT INTO @T
SELECT IdCliente, Nome, Cidade, UF
FROM dbo.Clientes
WHERE UF = @UF AND Ativo = 1;
RETURN;
END;
GO
-- Testes rápidos
SELECT Nome, dbo.FormatarCPF(CPF) AS CPF_Formatado, dbo.CalcularIdade(DataNascimento) AS Idade
FROM dbo.Clientes;
SELECT * FROM dbo.ClientesPorCidade(N'São Paulo');
SELECT * FROM dbo.ClientesAtivosPorEstado('SP');
-- Total de vendas por cliente, em SP, com nomes/tratamento via UDFs
SELECT v.IdCliente,
c.Nome,
dbo.FormatarCPF(c.CPF) AS CPF_Formatado,
SUM(v.Valor) AS Total
FROM dbo.Vendas AS v
JOIN dbo.Clientes AS c ON c.IdCliente = v.IdCliente
CROSS APPLY dbo.ClientesAtivosPorEstado('SP') AS ativos
WHERE ativos.IdCliente = c.IdCliente
GROUP BY v.IdCliente, c.Nome, c.CPF;
dbo.CalcularMargemLucro(@PrecoVenda, @Custo) que retorne a margem em % com duas casas.dbo.VendasPorPeriodo(@DataInicial, @DataFinal) retornando IdVenda, IdCliente, DataVenda, Valor.Nome em maiúsculas e CPF com máscara, substituindo NULL por 'SEM CPF'.DataVenda e ÚltimoDiaDoMês usando EOMONTH.TRY_CONVERT e some valores válidos.-- 1) Escalar — Margem (%)
CREATE OR ALTER FUNCTION dbo.CalcularMargemLucro (@PrecoVenda decimal(10,2), @Custo decimal(10,2))
RETURNS decimal(5,2)
AS
BEGIN
IF @PrecoVenda IS NULL OR @PrecoVenda = 0 RETURN NULL;
RETURN ROUND(((@PrecoVenda - @Custo) / @PrecoVenda) * 100.0, 2);
END;
GO
-- 2) iTVF — Vendas por Período
CREATE OR ALTER FUNCTION dbo.VendasPorPeriodo (@DataInicial date, @DataFinal date)
RETURNS table
AS
RETURN
(
SELECT IdVenda, IdCliente, DataVenda, Valor
FROM dbo.Vendas
WHERE DataVenda >= @DataInicial
AND DataVenda <= @DataFinal
);
GO
-- 3) Limpeza de texto
SELECT UPPER(Nome) AS NomeMaiusculo,
ISNULL(dbo.FormatarCPF(CPF), 'SEM CPF') AS CPF_Mascara
FROM dbo.Clientes;
-- 4) EOMONTH
SELECT DataVenda, EOMONTH(DataVenda) AS UltimoDia
FROM dbo.Vendas;
-- 5) TRY_CONVERT
DECLARE @t TABLE (ValorTxt nvarchar(10));
INSERT INTO @t VALUES (N'100'), (N'AB'), (N'250'), (N'xyz');
SELECT SUM(TRY_CONVERT(int, ValorTxt)) AS SomaValidos
FROM @t; -- RESULTADO: 350
WHERE/JOIN em colunas grandes → perda de SARGability.CPF não numérico).