1. Três canais de retorno
Diferente de um método em C#, que tem um único valor de retorno, uma Stored Procedure tem três canais independentes para devolver informação:
| Canal | Tipo de dado | Uso típico |
|---|---|---|
RETURN | Um único INT | Código de status (0 = sucesso, outro valor = erro específico) |
Parâmetros OUTPUT | Qualquer tipo, múltiplos valores | Devolver um Id gerado, um total calculado, uma mensagem |
SELECT | Um conjunto de linhas (result set) | Devolver dados para exibir em tela — o mais comum |
Fig. 1 — Os três canais de saída de uma procedure funcionam ao mesmo tempo, sem se excluir.
2. RETURN — código de status
RETURN aceita apenas um inteiro e encerra a procedure imediatamente
nesse ponto — nenhum comando depois dele executa. A convenção mais comum: 0
para sucesso, qualquer outro valor identifica um tipo de falha.
CREATE PROCEDURE dbo.usp_ClienteExcluir
@Id INT
AS
BEGIN
SET NOCOUNT ON;
IF NOT EXISTS (SELECT 1 FROM dbo.Cliente WHERE Id = @Id)
BEGIN
RETURN 1; -- 1 = cliente não encontrado
END
UPDATE dbo.Cliente SET Ativo = 0 WHERE Id = @Id;
RETURN 0; -- 0 = sucesso
END;
GO
-- capturando o código de retorno
DECLARE @StatusCode INT;
EXEC @StatusCode = dbo.usp_ClienteExcluir @Id = 42;
IF @StatusCode = 1
PRINT 'Cliente não encontrado';
ELSE
PRINT 'Excluído com sucesso';
RETURN só aceita INT — não dá para
devolver uma string ou um valor decimal por esse canal. Para qualquer outro tipo, use
parâmetros OUTPUT.
3. Parâmetros OUTPUT
Um parâmetro marcado OUTPUT funciona como out
em C#: a procedure escreve um valor nele, e quem chamou lê esse valor depois da execução.
CREATE PROCEDURE dbo.usp_ClienteInserir
@Nome NVARCHAR(100),
@Email NVARCHAR(150),
@NovoId INT OUTPUT
AS
BEGIN
SET NOCOUNT ON;
INSERT INTO dbo.Cliente (Nome, Email)
VALUES (@Nome, @Email);
SET @NovoId = SCOPE_IDENTITY();
END;
GO
DECLARE @IdGerado INT;
EXEC dbo.usp_ClienteInserir
@Nome = 'Wellington Marunaka',
@Email = 'wellington@exemplo.com',
@NovoId = @IdGerado OUTPUT;
PRINT 'Novo cliente criado com Id ' + CAST(@IdGerado AS VARCHAR(10));
Repare que OUTPUT aparece duas vezes: na declaração
do parâmetro (@NovoId INT OUTPUT) e na chamada
(@NovoId = @IdGerado OUTPUT). Esquecer o segundo
OUTPUT não gera erro — a procedure roda, mas
@IdGerado continua NULL do lado de fora.
É um dos bugs mais silenciosos do T-SQL.
É possível combinar múltiplos parâmetros OUTPUT com RETURN na mesma procedure:
CREATE PROCEDURE dbo.usp_ClienteInserir
@Nome NVARCHAR(100),
@Email NVARCHAR(150),
@NovoId INT OUTPUT,
@Mensagem NVARCHAR(200) OUTPUT
AS
BEGIN
SET NOCOUNT ON;
IF EXISTS (SELECT 1 FROM dbo.Cliente WHERE Email = @Email)
BEGIN
SET @Mensagem = 'E-mail já cadastrado';
RETURN 1;
END
INSERT INTO dbo.Cliente (Nome, Email)
VALUES (@Nome, @Email);
SET @NovoId = SCOPE_IDENTITY();
SET @Mensagem = 'Cliente criado com sucesso';
RETURN 0;
END;
GO
4. Parâmetros opcionais com valor padrão
Você já viu isso no capítulo 03 (@Ativo BIT = NULL). A regra geral:
todo parâmetro com = valor na declaração se torna opcional na chamada.
CREATE PROCEDURE dbo.usp_ClienteInserir
@Nome NVARCHAR(100),
@Email NVARCHAR(150),
@Ativo BIT = 1 -- opcional, assume 1 (ativo) se omitido
AS
BEGIN
SET NOCOUNT ON;
INSERT INTO dbo.Cliente (Nome, Email, Ativo)
VALUES (@Nome, @Email, @Ativo);
END;
GO
-- as duas chamadas abaixo são válidas
EXEC dbo.usp_ClienteInserir @Nome = 'A', @Email = 'a@x.com';
EXEC dbo.usp_ClienteInserir @Nome = 'B', @Email = 'b@x.com', @Ativo = 0;
Isso é o equivalente direto de um parâmetro opcional em C#:
public void InserirCliente(string nome, string email, bool ativo = true).
Mesma regra também vale em T-SQL: parâmetros obrigatórios (sem valor padrão) devem vir antes dos
opcionais na lista de declaração.
5. Onde isso te leva
Agora você sabe fazer uma procedure "conversar" de volta com quem a chamou de três formas diferentes. O que ainda falta é a parte que protege tudo isso quando algo dá errado no meio do caminho — se um INSERT funciona mas um UPDATE relacionado falha, o banco não pode ficar num estado parcialmente atualizado. É exatamente o que transações resolvem, no próximo capítulo.