Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Constraints check: TRY/CATCH vs Exists()

I have a table with unique constraint on it:

create table dbo.MyTab
(
    MyTabID int primary key identity,
    SomeValue nvarchar(50)
);
Create Unique Index IX_UQ_SomeValue 
On dbo.MyTab(SomeValue);
Go

Which code is better to check for duplicates (success = 0 if duplicate found)?

Option 1

Declare @someValue nvarchar(50) = 'aaa'
Declare @success bit = 1;
Begin Try 
    Insert Into MyTab(SomeValue) Values ('aaa');
End Try
Begin Catch
    -- lets assume that only constraint errors can happen
    Set @success = 0;
End Catch
Select @success

Option 2

Declare @someValue nvarchar(50) = 'aaa'
Declare @success bit = 1;
IF EXISTS (Select 1 From MyTab Where SomeValue = @someValue)
    Set @success = 0;
Else 
    Insert Into MyTab(SomeValue) Values ('aaa');
Select @success

From my point of view- i do believe that Try/Catch is for errors, that were NOT expected (like deadlock or even constraints when duplicates are not expected). In this case- it is possible that sometimes a user will try to submit duplicate, so the error is expected.

I have found article by Aaron Bertrand that states- checking for duplicates is not much slower even if most of inserts are successful.

There is also loads of advices over the net to use Try/Catch (to avoid 2 statements not 1). In my environment there could be just like 1% of unsuccessful cases, so that kind of makes sense too.

What is your opinion? Whats other reasons to use option 1 OR option 2?

UPDATE: I'm not sure it is important in this case, but table have instead of update trigger (for audit purposes- row deletion also happens through Update statement).

like image 211
Jānis Avatar asked Jun 13 '12 08:06

Jānis


People also ask

What is try and catch in SQL?

A TRY... CATCH construct catches all execution errors that have a severity higher than 10 that do not close the database connection. A TRY block must be immediately followed by an associated CATCH block. Including any other statements between the END TRY and BEGIN CATCH statements generates a syntax error.

Can we use try catch in SQL function?

Note that you cannot use TRY... CATCH blocks inside T-SQL UDFs. If you have to capture errors that occur inside a UDF, you can do that in the calling procedure or code.

Can we use try catch in trigger?

In my experience any error caught in a try catch in a trigger will rollback the entire transaction; you may be able to use a save transaction. I think you need to look at whats happening in "Some more sql" and determine if you can write case / if statements around it to stop the error.

Can we use try catch in stored procedure?

In the scope of a TRY / CATCH block, the following system functions can be used to obtain information about the error that caused the CATCH block to be executed: ERROR_NUMBER() returns the number of the error. ERROR_SEVERITY() returns the severity. ERROR_STATE() returns the error state number.


4 Answers

I've seen that article but note that for low failure rates I'd prefer the "JFDI" pattern. I've used this on high volume systems before (40k rows/second).

In Aaron's code, you can still get a duplicate when testing first under high load and lots of writes. (explained here on dba.se) This is important: your duplicates still happen, just less often. You still need exception handling and knowing when to ignore the duplicate error (2627)

Edit: explained succinctly by Remus in another answer

However, I would have a separate TRY/CATCH to test only for the duplicate error

BEGIN TRY

-- stuff

  BEGIN TRY
     INSERT etc
  END TRY
  BEGIN CATCH
      IF ERROR_NUMBER() <> 2627
        RAISERROR etc
  END CATCH

--more stuff

BEGIN CATCH
    RAISERROR etc
END CATCH
like image 148
gbn Avatar answered Oct 04 '22 18:10

gbn


To start with, the EXISTS(SELECT ...) is incorrect as it fails under concurrency: multiple transactions could run the check concurrently and all conclude that they have to INSERT, one will be the the lucky winner that inserts first, all the rest will hit constraint violation. In other words you have a race condition between the check and the insert. So you will have to TRY/CATCH anyway, so better just try/catch.

like image 28
Remus Rusanu Avatar answered Oct 04 '22 19:10

Remus Rusanu


Error logging

Don't hold me for this but there are likely logging implications when an exception is thrown. If you check before inserting no such thing happens.

Knowing why and when it can break

try/catch block should be used for parts that can break for non-deterministic reasons. I would say it's wiser in your case to check existing records because you know it can break and why exactly. So checking it yourself is from a developer's point of view a better way.

But in your code it may still break on insert because between the check time and insert time some other user inserted it already... But that is (as said previously) non-deterministic error. That's why you:

  1. should be checking with exists
  2. inserting within try/catch

Self explanatory code

Another positive is also that it is plain to see from the code why it can break while the try/catch block can hide that and one may remove them thinking why is this here, it's just inserting records...

like image 37
Robert Koritnik Avatar answered Oct 04 '22 18:10

Robert Koritnik


Option - 3

Begin Try
    SET XACT_ABORT ON
    Begin Tran
        IF NOT EXISTS (Select 1 From MyTab Where SomeValue = @someValue)
        Begin
            Insert Into MyTab(SomeValue) Values ('aaa');
        End
    Commit Tran
End Try

begin Catch
    Rollback Tran
End Catch
like image 25
Pankaj Avatar answered Oct 04 '22 19:10

Pankaj