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)
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.
@@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égia | O que faz | Quando usar |
|---|---|---|
| Hard delete | DELETE físico — a linha desaparece. | Dados sem valor histórico, ou exigência legal de remoção (LGPD/GDPR). |
| Soft delete | Marca 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). |
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
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ó:
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.
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>.
Fig. 1 — Convenção de nomenclatura: entidade no singular + ação no infinitivo/particípio, sempre nesta ordem.
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.