1. O que é uma Stored Procedure
Uma Stored Procedure (procedure armazenada) é um bloco de T-SQL — comandos,
variáveis, controle de fluxo — que fica salvo dentro do próprio banco de dados,
com um nome, e que pode ser chamado repetidamente com EXEC. Pense nela
como uma função em C# ou JS, só que ela mora no banco, não na sua aplicação.
Isso é uma mudança de local de execução que vale internalizar bem: em vez de a aplicação C# montar uma query e mandar para o SQL Server, a aplicação manda apenas o nome da procedure e os parâmetros — toda a lógica roda dentro do motor do banco.
| Conceito | Em C# / código de aplicação | Em SQL Server |
|---|---|---|
| Bloco de lógica nomeado e reutilizável | public void ObterCliente(int id) | CREATE PROCEDURE usp_ObterCliente |
| Parâmetros | (int id, string nome) | @Id INT, @Nome NVARCHAR(100) |
| Chamar / invocar | ObterCliente(5); | EXEC usp_ObterCliente @Id = 5; |
| Onde vive | Compilado no assembly da aplicação | Armazenado no próprio banco de dados |
2. Por que usar procedures (e não só mandar SQL direto do C#)
Vindo de um mundo onde é comum montar queries direto no código (ou via um ORM), a primeira pergunta é: por que se dar ao trabalho de mover isso para dentro do banco? Quatro razões concentram a resposta:
- Plano de execução em cache — o SQL Server compila e guarda o plano de execução da procedure. Chamadas seguintes reaproveitam esse plano, evitando recompilar a query toda vez.
- Menos tráfego de rede — a aplicação manda uma linha (
EXEC nome @param) em vez do texto inteiro de uma query complexa. - Camada de segurança — você pode dar permissão para um usuário executar uma procedure sem dar acesso direto de leitura/escrita às tabelas por trás dela.
- Lógica centralizada — regras de negócio que várias aplicações precisam (não só a sua em C#) ficam garantidas num único lugar, dentro do banco.
Fig. 1 — Sem procedure, cada comando é uma viagem de rede separada entre aplicação e banco.
Fig. 2 — Com a procedure, os três comandos rodam dentro do SQL Server; a aplicação só manda uma chamada e recebe um resultado.
3. Sintaxe básica: criando sua primeira procedure
A estrutura mínima é CREATE PROCEDURE, um nome, uma lista opcional
de parâmetros, a palavra AS, e o corpo entre
BEGIN/END:
CREATE PROCEDURE dbo.usp_ContarClientes
AS
BEGIN
SET NOCOUNT ON;
SELECT COUNT(*) AS TotalClientes
FROM dbo.Cliente;
END;
GO
SET NOCOUNT ON?
Por padrão, todo comando SQL dentro de uma procedure devolve uma mensagem extra dizendo
quantas linhas foram afetadas (tipo "3 rows affected"). SET NOCOUNT ON
desliga isso. Vale de hábito em toda procedure: reduz tráfego de rede e evita confundir
o driver ADO.NET/Dapper do lado do C#, que às vezes interpreta essas mensagens como um
result set extra.
Para executar a procedure acima:
EXEC dbo.usp_ContarClientes;
-- ou, forma completa:
EXECUTE dbo.usp_ContarClientes;
4. Anatomia de uma procedure com parâmetros
Parâmetros em T-SQL sempre começam com @ e precisam de um tipo
declarado — igual a tipar um parâmetro de método em C#:
Fig. 3 — Nome, parâmetros tipados, e corpo: as três partes de toda procedure.
CREATE PROCEDURE dbo.usp_ObterClientePorId
@ClienteId INT
AS
BEGIN
SET NOCOUNT ON;
SELECT Id, Nome, Email, DataCadastro
FROM dbo.Cliente
WHERE Id = @ClienteId;
END;
GO
-- chamando com o parâmetro nomeado (recomendado)
EXEC dbo.usp_ObterClientePorId @ClienteId = 42;
Sempre nomeie o parâmetro na chamada (@ClienteId = 42) em vez de
passar só o valor posicional (EXEC usp_ObterClientePorId 42).
É o equivalente T-SQL de usar named arguments em C#: protege sua chamada se alguém reordenar
os parâmetros da procedure no futuro.
Um exemplo com múltiplos parâmetros e um INSERT:
CREATE PROCEDURE dbo.usp_InserirCliente
@Nome NVARCHAR(100),
@Email NVARCHAR(150)
AS
BEGIN
SET NOCOUNT ON;
INSERT INTO dbo.Cliente (Nome, Email, DataCadastro)
VALUES (@Nome, @Email, GETDATE());
SELECT SCOPE_IDENTITY() AS NovoClienteId;
END;
GO
EXEC dbo.usp_InserirCliente
@Nome = 'Wellington Marunaka',
@Email = 'wellington@exemplo.com';
SCOPE_IDENTITY() devolve o último valor de identidade
(IDENTITY, tipo auto-increment) gerado na sessão atual — é assim
que a procedure informa de volta para o C# qual Id foi criado, sem precisar de um segundo
SELECT separado.
5. Alterando e removendo procedures
-- Altera uma procedure existente (mantém permissões já concedidas)
ALTER PROCEDURE dbo.usp_ObterClientePorId
@ClienteId INT
AS
BEGIN
SET NOCOUNT ON;
SELECT Id, Nome, Email FROM dbo.Cliente WHERE Id = @ClienteId;
END;
GO
-- Remove a procedure
DROP PROCEDURE IF EXISTS dbo.usp_ObterClientePorId;
sp_ é uma armadilha
Nunca nomeie suas procedures começando com sp_. Esse prefixo é
reservado para procedures de sistema do SQL Server. Quando você chama algo como
sp_MinhaProcedure, o motor primeiro procura na base
master antes de procurar no seu banco — isso adiciona uma
checagem extra toda vez que a procedure roda. Use um prefixo próprio, como
usp_ (user stored procedure), que é o padrão usado neste curso.
6. Prévia: chamando uma procedure a partir do C#
Este assunto será aprofundado no capítulo 13 (Integração com C#), mas vale ver o formato desde
já — repare como o parâmetro nomeado do T-SQL vira um SqlParameter
nomeado do lado do ADO.NET:
using var conexao = new SqlConnection(connectionString);
using var comando = new SqlCommand("dbo.usp_ObterClientePorId", conexao);
comando.CommandType = CommandType.StoredProcedure;
comando.Parameters.AddWithValue("@ClienteId", 42);
conexao.Open();
using var leitor = comando.ExecuteReader();
while (leitor.Read())
{
Console.WriteLine(leitor["Nome"]);
}
7. Onde isso te leva
Você agora sabe criar, parametrizar, alterar e remover procedures simples. O que falta para
um cenário real de produção — retorno de valores via OUTPUT,
transações com COMMIT/ROLLBACK,
tratamento robusto de erro com TRY/CATCH — é o assunto dos
próximos capítulos deste módulo, que juntos formam 80% deste curso.