44

I have a question regarding performance of SQL Server.

Suppose I have a table persons with the following columns: id, name, surname.

Now, I want to insert a new row in this table. The rule is the following:

  1. If id is not present in the table, then insert the row.

  2. If id is present, then update.

I have two solutions here:

First:

update persons
  set id=@p_id, name=@p_name, surname=@p_surname
where id=@p_id
if @@ROWCOUNT = 0 
  insert into persons(id, name, surname)
  values (@p_id, @p_name, @p_surname)

Second:

if exists (select id from persons where id = @p_id)
  update persons
    set id=@p_id, name=@p_name, surname=@p_surname
  where id=@p_id
else
  insert into persons(id, name, surname)
  values (@p_id, @p_name, @p_surname)

What is a better approach? It seems like in the second choice, to update a row, it has to be searched two times, whereas in the first option - just once. Are there any other solutions to the problem? I am using MS SQL 2000.

mustaccio
  • 18,234
  • 16
  • 48
  • 57
Markus
  • 3,547
  • 10
  • 39
  • 55
  • 1
    not sure, but I would just do a if ((COUNT(*) ) > 0) then update for the second option – Patrick Kafka Feb 16 '10 at 20:14
  • related question [solutions for insert or update on sql server](https://stackoverflow.com/questions/108403/solutions-for-insert-or-update-on-sql-server) – Rohim Chou Feb 05 '22 at 16:28

5 Answers5

14

Both work fine, but I usually use option 2 (pre-mssql 2008) since it reads a bit more clearly. I wouldn't stress about the performance here either...If it becomes an issue, you can use NOLOCK in the exists clause. Though before you start using NOLOCK everywhere, make sure you've covered all your bases (indexes and big picture architecture stuff). If you know you will be updating every item more than once, then it might pay to consider option 1.

Option 3 is to not use destructive updates. It takes more work, but basically you insert a new row every time the data changes (never update or delete from the table) and have a view that selects all the most recent rows. It's useful if you want the table to contain a history of all its previous states, but it can also be overkill.

dan
  • 9,712
  • 6
  • 49
  • 62
13

Option 1 seems good. However, if you're on SQL Server 2008, you could also use MERGE, which may perform good for such UPSERT tasks.

Note that you may want to use an explicit transaction and the XACT_ABORT option for such tasks, so that the transaction consistency remains in the case of a problem or concurrent change.

Lucero
  • 59,176
  • 9
  • 122
  • 152
  • It's not clear from the linked docs how to use this with `VALUES`, instead of merging rows from another table. Could you provide an example? – z0r Jun 27 '19 at 04:39
  • 1
    @z0r you can always substitute a (read-only) table with VALUES, use the following instead of the table reference: `(VALUES (1,'a'),(2,'b')) AS v(i,s)` – Lucero Jun 27 '19 at 07:08
6

You could just use @@RowCount to see if the update did anything. Something like:

    UPDATE MyTable
       SET SomeData = 'Some Data' WHERE ID = 1
    IF @@ROWCOUNT = 0
      BEGIN
        INSERT MyTable
        SELECT 1, 'Some Data'       
      END
KISS
  • 71
  • 1
  • 2
5

I tend to use option 1. If there is record in a table, you save one search. If there isn't, you don't loose anything. Moreover, in the second option you may run into funny locking and deadlocking issues related to locks incompatibility. There's some more info on my blog:

http://sqlblogcasts.com/blogs/piotr_rodak/archive/2010/01/04/updlock-holdlock-and-deadlocks.aspx

Piotr Rodak
  • 1,931
  • 1
  • 13
  • 8
  • Hi, the link doesn't work. But your post makes a lot of sense. It saves one extra search. Thanks! – Sam May 07 '20 at 01:49
4

Aiming to be a little more DRY, I avoid writing out the values list twice.

begin tran
insert into persons (id)
select @p_id from persons
 where not exists (select * from persons where id = @p_id)

update persons
set name=@p_name, surname=@p_surname
where id = @p_id

commit

Columns name and surname have to be nullable.

The transaction means no other user will ever see the "blank" record.

Edit: cleanup

Patrick
  • 1,089
  • 14
  • 17
  • 3
    Elegant but you will have to also insert the value for each non null field in the table. – Miguel Delgado Nov 28 '16 at 22:52
  • 1
    True. I tend to avoid any functional use of `not null`. The application needs to be pretty bung to be uploading nulls for important fields – Patrick Nov 29 '16 at 02:42
  • My final solution in the end was to do the logic in PHP, for `put` - attempt a full insert, and if it fails with `$sqlErrors[0][1] == 2627`, I do a full update – Patrick Nov 29 '16 at 03:03
  • 1
    Accidentally downvoted and didn't realize until a while later. Would like to remove my downvote, can you make any sort of edit to your answer so it will let me do so? – Nile Jun 26 '17 at 21:18