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.
1 2 3 4 5 6 7 8 9 10 11 |
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:
1 2 3 4 5 6 7 |
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
1 2 3 4 5 6 7 8 9 10 |
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.
1 2 3 4 5 6 7 8 |
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.
1 2 3 4 5 6 7 8 9 10 11 12 |
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:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
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!
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
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
Olá Luiz. Esse é um daqueles erros de programação que às vezes ocorrem, principalmente quando alterando algo já existente. Aliás, que pode ocorrer em outras linguagens de programação.
Fala José, pois eh, na dúvida eu prefiro sempre usar o BEGIN … END pra não ter esse tipo de problema hehe. =)
Abraço,
Luiz Vitor