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

Procedures de CRUD Completo

Fechando o ciclo: UPDATE, DELETE e uma listagem com filtros opcionais — e a convenção de nomenclatura que vai guiar todas as procedures deste curso.

1. Fechando o CRUD

No capítulo anterior você viu usp_ObterClientePorId (Read) e usp_InserirCliente (Create). Faltam Update e Delete — e uma listagem que aceite filtros, que é o padrão mais comum em telas reais de sistema.

2. Procedure de atualização (Update)

SQL usp_ClienteAtualizar
CREATE PROCEDURE dbo.usp_ClienteAtualizar
    @Id    INT,
    @Nome  NVARCHAR(100),
    @Email NVARCHAR(150)
AS
BEGIN
    SET NOCOUNT ON;

    UPDATE dbo.Cliente
    SET Nome  = @Nome,
        Email = @Email
    WHERE Id = @Id;

    IF @@ROWCOUNT = 0
    BEGIN
        RAISERROR('Cliente %d não encontrado.', 16, 1, @Id);
        RETURN;
    END
END;
GO

@@ROWCOUNT é uma variável de sistema que guarda quantas linhas o último comando afetou. É assim que uma procedure de UPDATE "sabe" se o Id passado realmente existia — se zero linhas foram afetadas, não existia.

Atenção

@@ROWCOUNT muda a cada comando executado. Se você precisa checá-lo, capture o valor em uma variável imediatamente após o comando — qualquer linha entre o UPDATE e a checagem que também toque o banco (mesmo um SELECT) já sobrescreve o valor.

3. Procedure de remoção (Delete) — soft delete vs. hard delete

Existem duas estratégias para "excluir" um registro, e a escolha importa mais do que parece:

EstratégiaO que fazQuando usar
Hard deleteDELETE físico — a linha desaparece.Dados sem valor histórico, ou exigência legal de remoção (LGPD/GDPR).
Soft deleteMarca uma coluna (Ativo = 0) — a linha continua existindo.Praticamente todo sistema corporativo: preserva histórico, evita quebrar chaves estrangeiras de registros relacionados (pedidos de um cliente "excluído", por exemplo).
SQL usp_ClienteExcluir — soft delete (recomendado)
CREATE PROCEDURE dbo.usp_ClienteExcluir
    @Id INT
AS
BEGIN
    SET NOCOUNT ON;

    UPDATE dbo.Cliente
    SET Ativo = 0
    WHERE Id = @Id;

    IF @@ROWCOUNT = 0
    BEGIN
        RAISERROR('Cliente %d não encontrado.', 16, 1, @Id);
        RETURN;
    END
END;
GO
Dica

Se o projeto realmente precisa de hard delete, prefira fazer isso dentro de uma transação (capítulo 5) que também trate as tabelas relacionadas — nunca um DELETE solto torcendo para não existir chave estrangeira apontando para aquela linha.

4. Listagem com filtros opcionais

Esse é o padrão mais reaproveitável do capítulo: uma única procedure de listagem que aceita vários filtros, todos opcionais. Em vez de criar uma procedure para "listar por nome", outra para "listar por status", e outra para "listar por nome e status", você escreve uma só:

SQL usp_ClienteListar — filtros opcionais
CREATE PROCEDURE dbo.usp_ClienteListar
    @Nome  NVARCHAR(100) = NULL,
    @Ativo BIT           = NULL
AS
BEGIN
    SET NOCOUNT ON;

    SELECT Id, Nome, Email, Ativo, DataCadastro
    FROM dbo.Cliente
    WHERE (@Nome  IS NULL OR Nome LIKE '%' + @Nome + '%')
      AND (@Ativo IS NULL OR Ativo = @Ativo)
    ORDER BY Nome;
END;
GO

-- todos os clientes
EXEC dbo.usp_ClienteListar;

-- só ativos
EXEC dbo.usp_ClienteListar @Ativo = 1;

-- só ativos com "wel" no nome
EXEC dbo.usp_ClienteListar @Nome = 'wel', @Ativo = 1;

O truque é (@Parametro IS NULL OR Coluna = @Parametro): quando o parâmetro não é informado (fica NULL), a condição inteira vira TRUE e não filtra nada; quando é informado, filtra normalmente.

Atenção — impacto em performance

Esse padrão é ótimo para produtividade, mas tem um custo: o otimizador de consultas do SQL Server tem mais dificuldade de escolher um bom plano de execução quando a condição depende de uma variável que pode ou não ser NULL. Em tabelas grandes (milhões de linhas), isso pode exigir OPTION (RECOMPILE) ou reescrever com SQL dinâmico (capítulo 10) para manter a performance. Para tabelas de porte médio, o padrão acima é suficiente e muito mais simples de manter.

5. Convenção de nomenclatura para CRUD

Vale fixar uma convenção única a partir daqui, para toda procedure deste curso: usp_<Entidade><Ação>.

usp_ClienteObter entidade ação Ações padrão: Obter · Listar · Inserir · Atualizar · Excluir Exemplos completos: usp_ClienteObter · usp_ClienteListar · usp_ClienteInserir · usp_ClienteAtualizar · usp_ClienteExcluir

Fig. 1 — Convenção de nomenclatura: entidade no singular + ação no infinitivo/particípio, sempre nesta ordem.

Nota

A partir deste capítulo, as procedures de exemplo passam a seguir essa convenção (usp_ClienteObter em vez de usp_ObterClientePorId como no capítulo 02). Nomear pela entidade primeiro agrupa alfabeticamente todas as procedures de uma mesma tabela no explorador de objetos do SSMS — uma vantagem prática que só aparece quando o banco cresce para dezenas de procedures.

6. Onde isso te leva

Com Create, Read, Update e Delete cobertos — e um padrão de nomenclatura fixado — falta o que normalmente é ignorado até dar problema em produção: como devolver informação estruturada para quem chamou a procedure (não só resultado de SELECT), e como blindar tudo isso contra falhas parciais com transações. É o assunto dos próximos dois capítulos.

📌 Resumo do capítulo

  • @@ROWCOUNT informa quantas linhas o último comando afetou — use para detectar UPDATE/DELETE que não encontrou o registro.
  • Prefira soft delete (coluna Ativo) a hard delete na maioria dos casos corporativos.
  • Padrão de filtros opcionais: WHERE (@Param IS NULL OR Coluna = @Param).
  • Convenção de nomenclatura fixada: usp_<Entidade><Ação> (Obter, Listar, Inserir, Atualizar, Excluir).

✏️ Praticando

  1. Implemente usp_ClienteAtualizar e usp_ClienteExcluir como nos exemplos.
  2. Implemente usp_ClienteListar com os dois filtros opcionais e teste as três formas de chamada mostradas.
  3. Adicione um terceiro filtro opcional, @CadastradoApos DATETIME2 = NULL, que retorne só clientes cadastrados depois de determinada data.
  4. Renomeie mentalmente (ou de fato, com sp_rename) as procedures do capítulo 02 para a nova convenção.