none
Verificar se já existe o registro pela procedure RRS feed

  • 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 Snippet

    CREATE 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

         END

     

     

    Uma outra dúvida, quando que eu devo tratar os erros de uma procedure? Nesse caso acima, eu deveria tratar ou somente se houvesse necessidade?

     

    segunda-feira, 8 de dezembro de 2008 19:35

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.

    terça-feira, 9 de dezembro de 2008 11:41
  • Olá Marcelo,

     

    Eu consegui:

     

    Code Snippet

    CREATE PROCEDURE uspTeste

    @Valor CHAR(1)

    AS

     

    BEGIN TRY

    IF @Valor = 'S'

    RAISERROR('Deu erro',16,1)

     

    RETURN 1

    END TRY

    BEGIN CATCH

    RETURN -1

    END CATCH

    GO

     

    DECLARE @Valor INT

    EXECUTE @Valor = uspTeste @Valor = 'S'

    SELECT @Valor

     

    EXECUTE @Valor = uspTeste @Valor = 'N'

    SELECT @Valor

     

    [ ]s,

     

    Gustavo

    terça-feira, 9 de dezembro de 2008 16:16
  • 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 ErrorNumber

     

    Obrigado a todos!

    terça-feira, 9 de dezembro de 2008 18:16

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

     

    segunda-feira, 8 de dezembro de 2008 19:41
  • 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.

    segunda-feira, 8 de dezembro de 2008 19:58
  • 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

    segunda-feira, 8 de dezembro de 2008 20:23
  • 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 Snippet

    CREATE 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

     

     

    segunda-feira, 8 de dezembro de 2008 20:26
  • 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 ON

     

         BEGIN 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

                ROLLBACK

                SELECT ERROR_NUMBER() AS ErrorNumber

         END CATCH

     

     

    Nesse 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.

    terça-feira, 9 de dezembro de 2008 11:15
  • 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.

    terça-feira, 9 de dezembro de 2008 11:41
  • Ok, Junior.

     

    Bem, então vou deixar assim.

    Mas e quanto a procedure, está correto a forma que eu fiz?

    E vou ter que deixar o NumeroSerie como UNIQUE?

     

     

    terça-feira, 9 de dezembro de 2008 12:10
  • 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!

     

    terça-feira, 9 de dezembro de 2008 14:03
  • Boa Tarde,

     

    Você pode testar o erro (se for 2627) utilizar um RETURN -1. Se for outro lance novamente o erro ou trate-o.

     

    [ ]s,

     

    Gustavo

     

    terça-feira, 9 de dezembro de 2008 15:02
  • Boa tarde, Gustavo.

     

    Eu até tentei fazer isso, mas só que me retorna sempre 0:

     

    Code Snippet

    BEGIN CATCH

          ROLLBACK TRAN

          RETURN -1

    END CATCH

     

    // ou ainda

     

    BEGIN CATCH

          ROLLBACK TRAN

          RETURN -ERROR_NUMBER()

    END CATCH

     

     

    terça-feira, 9 de dezembro de 2008 15:43
  • Olá Marcelo,

     

    Eu consegui:

     

    Code Snippet

    CREATE PROCEDURE uspTeste

    @Valor CHAR(1)

    AS

     

    BEGIN TRY

    IF @Valor = 'S'

    RAISERROR('Deu erro',16,1)

     

    RETURN 1

    END TRY

    BEGIN CATCH

    RETURN -1

    END CATCH

    GO

     

    DECLARE @Valor INT

    EXECUTE @Valor = uspTeste @Valor = 'S'

    SELECT @Valor

     

    EXECUTE @Valor = uspTeste @Valor = 'N'

    SELECT @Valor

     

    [ ]s,

     

    Gustavo

    terça-feira, 9 de dezembro de 2008 16:16
  • 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 ErrorNumber

     

    Obrigado a todos!

    terça-feira, 9 de dezembro de 2008 18:16
  • 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

    terça-feira, 9 de dezembro de 2008 18:33
  • Marcelo,

     

    Desculpe-me por não ter respondido, estava um pouco ocupado, ainda bem que o Gustavo Maia ajudou.

    terça-feira, 9 de dezembro de 2008 18:45