Dicas T-SQL – IF … ELSE – BEGIN … END

Fala pessoal,

Nesse post vou mostrar um caso que encontrei em um cliente onde o IF … ELSE impactou no resultado de uma query! #gogogo


YouTube – Vídeo:

Segue abaixo um vídeo que gravei no YouTube mostrando na prática a execução dos scripts desse post:

https://www.youtube.com/watch?v=bIXkJ9sZLkc


Pergunta:

Mas antes, quero fazer um TESTE com você:

Dado o script abaixo, qual será o resultado dessa query?

OBS: Não vale roubar e executar o script OK! Pense por 1 ou 2 minutos e escolha a sua alternativa antes de continuar a leitura do post.

USE Traces

DECLARE @ID INT = 10

IF (@ID = 10)
BEGIN
	SELECT '(1) - O VALOR DA VARIAVEL É IGUAL A 10!!!' 
END
ELSE
	SELECT '(2) - O VALOR DA VARIAVEL É DIFERENTE 10!!!'
	SELECT '(3) - FIM DO PROGRAMA!!!'

(A) ERRO! Pois não utilizou o BEGIN … END no ELSE.

(B) Vai retornar apenas o SELECT (1).

(C) Vai retornar os SELECT (1) e (2).

(D) Vai retornar os SELECT (1) e (3).

(E) Vai retornar os SELECT (1), (2) e (3).

(F) Não vai retornar nenhum SELECT.


Resposta:

Obviamente tem uma pegadinha do malandro aí. Vamos analisar cada uma das alternativas:

“(A) ERRO! Pois não utilizou o BEGIN … END no ELSE.

Essa é só pra tentar complicar um pouco, pois não vai gerar erro. O BEGIN … END são OPCIONAIS, ou seja, você utiliza se quiser. Logo, a alternativa está INCORRETA.

“(B) Vai retornar apenas o SELECT (1).

Se eu consegui te enganar, você pode ter escolhido essa alternativa, mas ela também está INCORRETA.

“(C) Vai retornar os SELECT (1) e (2).

Por eliminação poderíamos desconsiderar essa, pois não faz sentido retornar o SELECT do IF e do ELSE na mesma execução. Ele vai retornar sempre um OU outro, nunca os dois OK. Logo, a alternativa está INCORRETA.

“(D) Vai retornar os SELECT (1) e (3).”

Essa é a alternativa CORRETA!!!

Vamos analisar novamente o IF … ELSE:

IF (@ID = 10)
BEGIN
	SELECT '(1) - O VALOR DA VARIAVEL É IGUAL A 10!!!' 
END
ELSE
	SELECT '(2) - O VALOR DA VARIAVEL É DIFERENTE 10!!!'
	SELECT '(3) - FIM DO PROGRAMA!!!' 

No IF utilizei o BEGIN … END. No ELSE eu não utilizei de propósito e aqui está a pegadinha do malandro.

Quando NÃO utilizamos o BEGIN … END, o SQL Server entende que o ELSE possui apenas uma instrução, ou seja, somente o SELECT (2) faz parte do ELSE e o SELECT (3) está fora do IF … ELSE.

Eu particularmente prefiro SEMPRE utilizar o BEGIN … END para evitar esse tipo de situação. Olha só como ficaria muito mais claro entender o que pertence ou não ao IF … ELSE. #ficaadica

IF (@ID = 10)
BEGIN
	SELECT '(1) - O VALOR DA VARIAVEL É IGUAL A 10!!!' 
END
ELSE
BEGIN
	SELECT '(2) - O VALOR DA VARIAVEL É DIFERENTE 10!!!'
END

SELECT '(3) - FIM DO PROGRAMA!!!'

“(E) Vai retornar os SELECT (1), (2) e (3).

Assim como a alternativa (C), também não faz sentido ele retornar todos os três SELECTS. Logo, a alternativa está INCORRETA.

“(F) Não vai retornar nenhum SELECT.

Por eliminação poderíamos desconsiderar essa, pois vai retornar pelo menos um SELECT (IF ou ELSE). Logo, a alternativa está INCORRETA.

“Ahh Luiz, mas isso é muito bobo, não vou encontrar nada disso na prática no meu dia a dia!!!”

Será mesmo??? Agora vamos para o Caso do Dia a Dia!


Caso do Dia a Dia:

Vou simular um cenário para reproduzir o erro que encontrei em um cliente. Primeiro vamos criar uma tabela chamada “HISTORICO_ESTOQUE” e deixar ela vazia. Repare que utilizamos uma PRIMARY KEY para evitar duplicidades na tabela OK.

USE Traces

CREATE TABLE HISTORICO_ESTOQUE (
	DT_REFERENCIA DATE,
	ID_PRODUTO INT,
	QUANTIDADE INT,
	PRIMARY KEY (DT_REFERENCIA, ID_PRODUTO, QUANTIDADE)
)

Depois vou criar uma outra tabela “ESTOQUE_DIA_ATUAL” para armazenar o estoque do dia atual e inserir alguns registros nela.

CREATE TABLE ESTOQUE_DIA_ATUAL (
	DT_REFERENCIA DATETIME,
	ID_PRODUTO INT,
	QUANTIDADE INT
)

INSERT INTO ESTOQUE_DIA_ATUAL
VALUES	(GETDATE(), 1, 100),
		(GETDATE(), 2, 200),
		(GETDATE(), 3, 300)

SELECT * FROM ESTOQUE_DIA_ATUAL

Agora imagine que temos um JOB que executa diariamente para fazer a carga na tabela “HISTORICO_ESTOQUE” com os dados do estoque do dia atual (tabela “ESTOQUE_DIA_ATUAL”).

O JOB utiliza o script abaixo:

USE Traces

DECLARE @DT_REFERENCIA DATE

-- BUSCA A DATA DE REFERÊNCIA
SELECT TOP 1 @DT_REFERENCIA = DT_REFERENCIA
FROM ESTOQUE_DIA_ATUAL

-- SELECT @DT_REFERENCIA

-- SE NÃO EXISTIR REGISTROS NO DIA, DEVE INSERIR NA TABELA HISTÓRICO
IF NOT EXISTS (SELECT TOP 1 * FROM HISTORICO_ESTOQUE WHERE DT_REFERENCIA = @DT_REFERENCIA)
	INSERT INTO HISTORICO_ESTOQUE
	SELECT * 
	FROM ESTOQUE_DIA_ATUAL
ELSE	-- SE EXISTIR REGISTROS NO DIA, DEVE EXCLUIR PRIMEIRO ANTES DE INSERIR NOVAMENTE
	DELETE HISTORICO_ESTOQUE
	WHERE DT_REFERENCIA = @DT_REFERENCIA

	INSERT INTO HISTORICO_ESTOQUE
	SELECT * 
	FROM ESTOQUE_DIA_ATUAL

Contudo, o cliente informou que estava dando erro de “duplicate key”. Ele tinha analisado, mas não conseguiu encontrar o problema.

(3 rows affected)

Msg 2627, Level 14, State 1, Line 70

Violation of PRIMARY KEY constraint ‘PK__HISTORIC__8CD1B715497217B0’.

Cannot insert duplicate key in object ‘dbo.HISTORICO_ESTOQUE’. The duplicate key value is (2021-01-12, 1, 100).

The statement has been terminated.

Ao analisar o JOB, identifiquei que o problema estava no ELSE. É o mesmo caso do nosso exemplo, pois o cliente achou que o DELETE e o INSERT faziam parte do ELSE, mas na verdade era apenas o DELETE e o último INSERT estava sendo executado sempre. Essa era a causa do erro de “duplicate key”.

Por fim, segue abaixo a correção que sugerimos no JOB. Bastava inserir o BEGIN … END no ELSE para delimitar os dois comandos. Depois desse ajuste ele voltou a executar com sucesso!

USE Traces

DECLARE @DT_REFERENCIA DATE

-- BUSCA A DATA DE REFERÊNCIA
SELECT TOP 1 @DT_REFERENCIA = DT_REFERENCIA
FROM ESTOQUE_DIA_ATUAL

-- SELECT @DT_REFERENCIA

-- SE NÃO EXISTIR REGISTROS NO DIA, DEVE INSERIR NA TABELA HISTÓRICO
IF NOT EXISTS (SELECT TOP 1 * FROM HISTORICO_ESTOQUE WHERE DT_REFERENCIA = @DT_REFERENCIA)
	INSERT INTO HISTORICO_ESTOQUE
	SELECT * 
	FROM ESTOQUE_DIA_ATUAL
ELSE	-- SE EXISTIR REGISTROS NO DIA, DEVE EXCLUIR PRIMEIRO ANTES DE INSERIR NOVAMENTE
BEGIN
	DELETE HISTORICO_ESTOQUE
	WHERE DT_REFERENCIA = @DT_REFERENCIA

	INSERT INTO HISTORICO_ESTOQUE
	SELECT * 
	FROM ESTOQUE_DIA_ATUAL
END

Espero que tenha gostado e que isso também possa ser útil no seu dia a dia. Até o próximo post!

Me siga no LinkedIn e YouTube para ficar por dentro das novidades.

Abraço,

Luiz Vitor França Lima

Consultor SQL Server

2 comentários em “Dicas T-SQL – IF … ELSE – BEGIN … END

Deixe uma resposta