Módulo 2 · SQL Server / Procedures — Capítulo 02

Introdução a Stored Procedures

O bloco de construção mais importante deste curso: o que é uma procedure, por que ela existe, e como escrever as primeiras.

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.

ConceitoEm C# / código de aplicaçãoEm SQL Server
Bloco de lógica nomeado e reutilizávelpublic void ObterCliente(int id)CREATE PROCEDURE usp_ObterCliente
Parâmetros(int id, string nome)@Id INT, @Nome NVARCHAR(100)
Chamar / invocarObterCliente(5);EXEC usp_ObterCliente @Id = 5;
Onde viveCompilado no assembly da aplicaçãoArmazenado 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.
SEM procedure — 3 idas e vindas App C# SELECT... UPDATE... INSERT... SQL Server

Fig. 1 — Sem procedure, cada comando é uma viagem de rede separada entre aplicação e banco.

App C# EXEC usp_Pedido @Id=5 SQL Server SELECT + UPDATE + INSERT tudo executado localmente resultado App C#

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:

SQL primeira procedure — sem parâmetros
CREATE PROCEDURE dbo.usp_ContarClientes
AS
BEGIN
    SET NOCOUNT ON;

    SELECT COUNT(*) AS TotalClientes
    FROM dbo.Cliente;
END;
GO
O que é 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:

SQL chamando a procedure
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#:

CREATE PROCEDURE dbo.usp_ObterClientePorId @ClienteId INT ← parâmetro de entrada (tipado) AS marca o início do corpo BEGIN SELECT * FROM Cliente WHERE Id = @ClienteId; END

Fig. 3 — Nome, parâmetros tipados, e corpo: as três partes de toda procedure.

SQL procedure com parâmetro
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;
Dica

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:

SQL procedure de inserção
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

SQL ALTER e DROP
-- 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;
Atenção — o prefixo 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:

C# chamando usp_ObterClientePorId
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.

📌 Resumo do capítulo

  • Uma Stored Procedure é um bloco de T-SQL nomeado e salvo dentro do banco, chamável via EXEC.
  • Vantagens principais: plano de execução em cache, menos tráfego de rede, camada de segurança, lógica centralizada.
  • Estrutura mínima: CREATE PROCEDURE nome @parametro TIPO AS BEGIN ... END.
  • Use SET NOCOUNT ON por hábito no início de toda procedure.
  • Sempre chame com parâmetros nomeados: EXEC nome @Param = valor.
  • SCOPE_IDENTITY() devolve o Id recém-gerado após um INSERT.
  • Nunca use o prefixo sp_ — é reservado para procedures de sistema e causa overhead de busca. Use usp_.

✏️ Praticando

  1. Crie uma tabela Cliente simples (Id, Nome, Email, DataCadastro) e implemente usp_ObterClientePorId e usp_InserirCliente como nos exemplos.
  2. Crie uma terceira procedure, usp_ListarClientes, sem parâmetros, que devolve todos os clientes ordenados por nome.
  3. Altere usp_ObterClientePorId com ALTER PROCEDURE para também aceitar um parâmetro opcional @IncluirInativos BIT = 0 (mesmo que ainda não use o valor no corpo — só pratique a sintaxe de valor padrão).
  4. Escreva a chamada em C# (como na seção 6) para usp_ListarClientes e liste o resultado no console.