1. Por que "rascunhos" de dados
Procedures complexas frequentemente precisam calcular um resultado intermediário antes de devolver o resultado final — por exemplo, filtrar um conjunto de clientes, depois cruzar esse conjunto com outra tabela. Guardar esse intermediário exige uma estrutura tipo tabela que existe só durante a execução. T-SQL oferece duas:
Tabela Temporária (#temp) | Variável de Tabela (@tabela) | |
|---|---|---|
| Onde vive | tempdb, como uma tabela real | tempdb, mas tratada como variável |
| Aceita índices? | Sim, inclusive depois de criada | Só os definidos na declaração (PRIMARY KEY, UNIQUE) |
| Estatísticas para o otimizador | Sim — o otimizador conhece o volume de dados | Limitadas — o otimizador costuma assumir poucas linhas |
| Escopo | Visível em procedures aninhadas chamadas depois | Só no bloco/procedure onde foi declarada |
| Transação | Respeita ROLLBACK | Sobrevive a um ROLLBACK |
| Melhor para | Volumes médios/grandes, muitas operações | Volumes pequenos (dezenas/centenas de linhas) |
2. Tabela temporária (#temp)
CREATE PROCEDURE dbo.usp_ClienteRelatorioTop
@Quantidade INT = 10
AS
BEGIN
SET NOCOUNT ON;
CREATE TABLE #ClientesComPedidos
(
ClienteId INT PRIMARY KEY,
Nome NVARCHAR(100),
TotalPedidos INT,
ValorTotal DECIMAL(12,2)
);
INSERT INTO #ClientesComPedidos (ClienteId, Nome, TotalPedidos, ValorTotal)
SELECT c.Id, c.Nome, COUNT(p.Id), SUM(p.Valor)
FROM dbo.Cliente c
INNER JOIN dbo.Pedido p ON p.ClienteId = c.Id
GROUP BY c.Id, c.Nome;
SELECT TOP (@Quantidade) *
FROM #ClientesComPedidos
ORDER BY ValorTotal DESC;
-- opcional: o SQL Server já limpa ao fim da procedure,
-- mas DROP explícito documenta a intenção
DROP TABLE #ClientesComPedidos;
END;
GO
Uma tabela temporária criada com # (uma cerquilha) é
local — só existe na sessão atual e desaparece quando a procedure termina.
Duas cerquilhas (##) criam uma tabela global,
visível para qualquer sessão — uso raro e arriscado (uma sessão pode interferir na outra).
3. Variável de tabela (@tabela)
CREATE PROCEDURE dbo.usp_ClienteRelatorioTop
@Quantidade INT = 10
AS
BEGIN
SET NOCOUNT ON;
DECLARE @ClientesComPedidos TABLE
(
ClienteId INT PRIMARY KEY,
Nome NVARCHAR(100),
TotalPedidos INT,
ValorTotal DECIMAL(12,2)
);
INSERT INTO @ClientesComPedidos (ClienteId, Nome, TotalPedidos, ValorTotal)
SELECT c.Id, c.Nome, COUNT(p.Id), SUM(p.Valor)
FROM dbo.Cliente c
INNER JOIN dbo.Pedido p ON p.ClienteId = c.Id
GROUP BY c.Id, c.Nome;
SELECT TOP (@Quantidade) *
FROM @ClientesComPedidos
ORDER BY ValorTotal DESC;
END;
GO
Repare que a sintaxe de uso é quase idêntica — a diferença real está em como o SQL Server trata cada uma por baixo dos panos, o que só importa na prática quando o volume de linhas cresce.
Por não ter estatísticas confiáveis, o otimizador de consultas costuma presumir que uma
variável de tabela tem apenas 1 linha, independente do volume real. Isso pode gerar
planos de execução ruins quando ela guarda milhares de linhas. Regra prática: até ~1000 linhas,
@tabela é seguro; acima disso, prefira #temp.
4. Onde isso te leva
Essas duas estruturas resolvem "preciso de uma tabela temporária dentro da procedure". O próximo capítulo resolve o problema complementar: "preciso receber uma lista de valores como parâmetro" — que é para isso que existem os Table-Valued Parameters.