Usuário com melhor resposta
Verificar se já existe o registro pela procedure

Pergunta
-
Boa tarde,
pessoal.
Eu fiz uma procedure, na qual preciso verificar se já existe o registro cadastrado e gostaria da opinião do pessoal, se fiz certo ou se seria melhor de outra maneira?
Code SnippetCREATE PROCEDURE
dbo.STP_IncluirAparelho(
...
)
AS
SET NOCOUNT ON DECLARE @ID int-- Verifica se já existe o equipamento cadastrado
SET @ID = (SELECT CodAparelho FROM TB_Aparelho WHERE NumeroSerie = @NumeroSerie)IF (@ID = 0) -- Não existe, cadastra um novo
BEGIN INSERT INTO TB_Aparelho (NumeroSerie, DataColeta, Descricao, Senha, Leitura, CorrecaoTemperatura, AlarmeQualidade1
, AlarmeQualidade2, AlarmeTemperatura, AlarmePressurizacao
, ModoRecirculacao, Condutividade, TempoLigado, TempoDesligado
, DesinfeccaoAtual, DesinfeccaoMax, LampadaUVAtual, LampadaUVMax)
VALUES (@NumeroSerie, @DataColeta, @Descricao, @Senha, @Leitura, @CorrecaoTemperatura, @AlarmeQualidade1, @AlarmeQualidade2
, @AlarmeTemperatura, @AlarmePressurizacao, @ModoRecirculacao
, @Condutividade, @TempoLigado, @TempoDesligado, @DesinfeccaoAtual
, @DesinfeccaoMax, @LampadaUVAtual, @LampadaUVMax)
-- Retorna o último CodAparelho
SELECT @@IDENTITY FROM TB_Aparelho END ELSE BEGIN RETURN -1 -- Indica que já existe o equipamento ENDUma outra dúvida, quando que eu devo tratar os erros de uma procedure? Nesse caso acima, eu deveria tratar ou somente se houvesse necessidade?
Respostas
-
Marcelo,
Armazenar este valor na mesma variável ou em outra, vai depender justamente da forma que você deseja trabalhar com as informações que estão sendo retornadas, caso tenha a necessidade de utilizar os dados que estão sendo retornados em outras partes da sua procedure, seria o caso de armazenar estes dados em variáveis separadas.
-
Olá Marcelo,
Eu consegui:
Code SnippetCREATE
PROCEDURE uspTeste@Valor
CHAR(1)AS
BEGIN
TRYRETURN 1
END
TRYBEGIN
CATCHEND
CATCHGO
DECLARE
@Valor INTEXECUTE
@Valor = uspTeste @Valor = 'S'SELECT
@ValorEXECUTE
@Valor = uspTeste @Valor = 'N'SELECT
@Valor[ ]s,
Gustavo
-
Realmente funciona, foi um erro meu.
Pois pelo método .ExecuteScalar(), ele me retorna o valor do código do aparelho, que é "0"! Uma forma seria eu passar o valor do erro OUTPUT ou fazer um select do ERROR_NUMBER(), que foi o que eu fiz e assim deu certo:
SELECT
-ERROR_NUMBER() AS ErrorNumberObrigado a todos!
Todas as Respostas
-
Olá Marcelo,
Essa maneira não irá funcionar, pois, caso não exista o ID, o resultado do SET será nulo e não zero (não são a mesma coisa). Antes de propriamente emitir o que considero "melhor alternativa", existem alguns pontos que gostaria de saber:
- Como você irá reconhecer que um determinado registro já foi cadastrado se o ID é Identity ?
- Qual é a chance de um registro tentar ser recadastrado ? É alta ou baixa ?
[ ]s,
Gustavo
-
Então eu deveria fazer algo como abaixo?
SET
@ID = (SELECT COUNT(NumeroSerie) FROM TB_Aparelho WHERE NumeroSerie = @NumeroSerie)Quanto as suas perguntas:
1) É que eu estava me baseando em uma outra aplicação que era em VB6, mas eu não retorno o ID, na verdade retorno o Numero de Serie que não está como Identity. E assim, verificava pela propriedade .RecordCount do ADO, já que naquela época eu não utilizava procedure(pois o BD era em access).
2) Tudo depende do usuário. Ele pode muito bem, entrar na janela novo da aplicação e tentar receber o mesmo equipamento ao invés de ir em "Atualizar o equipamento". Mas acredito que seja baixo, será poucas vezes que ele tentará fazer isso.
-
Olá Marcelo,
Há duas abordagens possíveis e cada uma é mais eficiente em determinadas situações.
Se admitirmos que o usuário será teimoso e tentará burlar o sistema, a idéia de consultar antes de inserir (algo como o que você fez é a melhor idéia), pois, ao invés de tentar inserir um registro duplicado, faremos uma consulta antes. Como o usuário certamente irá "teimar", a consulta irá impedir uma tentativa de cadastro errada e o devido tratamento de erro.
Se admitirmos que o usuário não será teimoso e fará como o esperado na maioria das situações, podemos tentar inserir o registro e caso haja uma violação de CONSTRAINT, ou seja, dois registros duplicados, um erro será gerado e tratado. Como a ocorrência de erros é baixa nessa hipótese, não teremos o ônus de consultar toda vez antes de inserir.
Se adotar a primeira possibilidade apenas altere sua SP para que ao invés de usar o EXISTS (ele é mais performático o COUNT). Ex:
IF
EXISTS (SELECT * FROM TB_Aparelho WHERE NumeroSerie = @NumeroSerie)Se considerar a segunda possibilidade, o TRY CATCH do 2005 é perfeito para esse caso.
[ ]s,
Gustavo
-
Marcelo, o Gustavo estava se referindo a forma como vc está comparando o valor da variável no "IF", use null ao invés de "0", pois é null que será retornado em caso de não haver resgistro que satisfaça a condição do select.
ex:
Code SnippetCREATE PROCEDURE dbo.STP_IncluirAparelho
(
...
)
AS
SET NOCOUNT ON DECLARE @ID int-- Verifica se já existe o equipamento cadastrado
SET @ID = (SELECT CodAparelho FROM TB_Aparelho WHERE NumeroSerie = @NumeroSerie)IF (@ID is null) -- Não existe, cadastra um novo
BEGIN INSERT INTO TB_Aparelho (NumeroSerie, DataColeta, Descricao, Senha, Leitura, CorrecaoTemperatura, AlarmeQualidade1
, AlarmeQualidade2, AlarmeTemperatura, AlarmePressurizacao
, ModoRecirculacao, Condutividade, TempoLigado, TempoDesligado
, DesinfeccaoAtual, DesinfeccaoMax, LampadaUVAtual, LampadaUVMax)
VALUES (@NumeroSerie, @DataColeta, @Descricao, @Senha, @Leitura, @CorrecaoTemperatura, @AlarmeQualidade1, @AlarmeQualidade2
, @AlarmeTemperatura, @AlarmePressurizacao, @ModoRecirculacao
, @Condutividade, @TempoLigado, @TempoDesligado, @DesinfeccaoAtual
, @DesinfeccaoMax, @LampadaUVAtual, @LampadaUVMax)
-- Retorna o último CodAparelho
SELECT @@IDENTITY FROM TB_Aparelho END ELSE BEGIN RETURN -1 -- Indica que já existe o equipamento END -
Gustavo:
Se eu optar pela 2ª opção, eu terei que deixar o campo NumeroSerie como CONSTRAINT UNIQUE, correto? E como ficaria o try catch do 2005?
Algo como abaixo:
Code Snippet...
AS
SET NOCOUNT ONBEGIN TRY
BEGIN TRAN INSERT INTO TB_Aparelho (NumeroSerie, DataColeta, Descricao, Senha, Leitura, CorrecaoTemperatura, AlarmeQualidade1
, AlarmeQualidade2, AlarmeTemperatura, AlarmePressurizacao
, ModoRecirculacao, Condutividade, TempoLigado, TempoDesligado
, DesinfeccaoAtual, DesinfeccaoMax, LampadaUVAtual, LampadaUVMax)
VALUES (@NumeroSerie, @DataColeta, @Descricao, @Senha, @Leitura, @CorrecaoTemperatura, @AlarmeQualidade1, @AlarmeQualidade2
, @AlarmeTemperatura, @AlarmePressurizacao, @ModoRecirculacao
, @Condutividade, @TempoLigado, @TempoDesligado, @DesinfeccaoAtual
, @DesinfeccaoMax, @LampadaUVAtual, @LampadaUVMax)
-- Retorna o último CodAparelho
SELECT @@IDENTITY FROM TB_Aparelho COMMIT TRAN END TRY BEGIN CATCH ROLLBACKSELECT
ERROR_NUMBER() AS ErrorNumber END CATCHNesse caso, o ERROR_NUMBER() sempre me retorna valores negativo, correto?
Então, se o retorno da procedure ser negativo ou ser o número específico de duplicidade do registro, eu envio uma mensagem para o usuário.
Para que eu verifique, estarei sempre esperando no meu código em C# um valor int, tanto para o último código do aparelho quanto para o erro:
aparelho.Codigo =
Convert.ToInt32(scmAparelho.ExecuteScalar());Está certo assim ou seria melhor eu armazenar o valor do erro em uma variável a parte ao invés de armazenar na mesma variável do código?
---
Emanuel:
Não tinha percebido isso, obrigado! Bem, de qualquer forma modifiquei a procedure novamente, como acima.
-
Marcelo,
Armazenar este valor na mesma variável ou em outra, vai depender justamente da forma que você deseja trabalhar com as informações que estão sendo retornadas, caso tenha a necessidade de utilizar os dados que estão sendo retornados em outras partes da sua procedure, seria o caso de armazenar estes dados em variáveis separadas.
-
-
Fiz as alterações aqui e funcionou.
Deixei com UNIQUE e no CATCH me retorna o nº 2627, que é duplicidade de valores.
Eu prefiria que retorna-se um valor negativo, pois futuramente pode ocorrer de inserir corretamente e o nº do registro ser 2627. Tem como passar como um valor negativo o ERROR_NUMBER()?
Ou então, uma solução seria sempre retornar 0 no CATCH para qualquer erro!
-
-
-
Olá Marcelo,
Eu consegui:
Code SnippetCREATE
PROCEDURE uspTeste@Valor
CHAR(1)AS
BEGIN
TRYRETURN 1
END
TRYBEGIN
CATCHEND
CATCHGO
DECLARE
@Valor INTEXECUTE
@Valor = uspTeste @Valor = 'S'SELECT
@ValorEXECUTE
@Valor = uspTeste @Valor = 'N'SELECT
@Valor[ ]s,
Gustavo
-
Realmente funciona, foi um erro meu.
Pois pelo método .ExecuteScalar(), ele me retorna o valor do código do aparelho, que é "0"! Uma forma seria eu passar o valor do erro OUTPUT ou fazer um select do ERROR_NUMBER(), que foi o que eu fiz e assim deu certo:
SELECT
-ERROR_NUMBER() AS ErrorNumberObrigado a todos!
-
Olá Marcelo,
O mais conceitualmente correto, seria utilizar o SqlCommand e na coleção parameters capturar o valor do parâmetro Return_Value. Utilizar a saída em um SELECT para capturar pelo ExecuteScalar também é uma opção. Nesse caso é mais interessante, pois, a saída por ser o valor -1 ou o @@Identity (embora você possa usar o @@identity com o Return).
Para situações na qual o único interesse é um valor de retorno considere o Parameters. (Essa é para o fórum de ADO.NET).
[ ]s,
Gustavo
-