Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement Upsert with single SQL statement #34

Open
isolaol opened this issue May 30, 2014 · 4 comments
Open

Implement Upsert with single SQL statement #34

isolaol opened this issue May 30, 2014 · 4 comments

Comments

@isolaol
Copy link

isolaol commented May 30, 2014

Dear developer,

The current incarnation (2.3) of SqlFu has Upsert method in CrudHelpers.cs. The upsert (ie. UpdateOrInsert) is a great facility and used in real world applications. I like it!

The implementation seems to try to update the record first and then insert it if the update fails. This two-pass protocol is "chatty". The single statement upsert would be far more efficient due to reduced round-trips to the database.

There are two ways of implementing single statement upsert. The traditional method is a combination of both UPDATE and INSERT statements. The fast method takes advantage of MERGE statement and VALUES table constructor.

A demostration of upsert without autogenerated primary key.

-- create a sample table
CREATE TABLE dbo.tabX (
    key int NOT NULL PRIMARY KEY,
    value nvarchar(20) NULL
)

-- insert record {key=8, value="foo"}

-- traditional method, primary key generated by application
IF EXISTS (SELECT 1 FROM [tabX] WHERE [key] = 8) BEGIN
    UPDATE [tabX] SET [value] = 'foo' WHERE [key] = 8
END
ELSE BEGIN
    INSERT INTO [tabX]([key], [value]) VALUES(8, 'foo')
END

-- fast method, primary key generated by application
MERGE INTO [tabX] AS D
USING (VALUES (8, 'foo')) AS S([key], [value])
ON (S.[key] = D.[key])
WHEN MATCHED THEN UPDATE SET D.[value] = S.[value]
WHEN NOT MATCHED BY TARGET THEN INSERT ([key], [value]) VALUES (S.[key], S.[value])

A demostration of upsert when primary key value is generated by database. Please note the presence of OUTPUT clause. It allows the library the read back any value generated by the database.

-- create a sample sequence of numbers
CREATE SEQUENCE dbo.SampleSeq AS int START WITH 1 INCREMENT BY 1;

-- create a sample table, with sequence as default value in primary key column
CREATE TABLE dbo.tabY (
    key int NOT NULL PRIMARY KEY DEFAULT (NEXT VALUE FOR dbo.SampleSequence),
    value nvarchar(20) NULL
);

-- insert record {key=NULL, value="bar"}

-- traditional method, primary key generated by database for new records
-- please note that IF EXISTS can be omitted due to key=NULL
INSERT INTO [tabY]([value]) OUTPUT INSERTED.[key] VALUES('bar')

-- fast method, primary key generated by database for new records
MERGE INTO [tabY] AS D
USING (VALUES (NULL, 'bar')) AS S([key], [value])
ON (S.[key] = D.[key])
WHEN MATCHED THEN UPDATE SET D.[value] = S.[value]
WHEN NOT MATCHED BY TARGET THEN INSERT ([value]) VALUES (S.[value])
OUTPUT INSERTED.[key]

I prefer MERGE statement over combining IF EXISTS, UPDATE and INSERT. MERGE statement is easier to query optimizer and does its job in single pass. The added benefit of MERGE statement is atomicity. I do not need to wrap the call of upsert method inside transaction!

For more information about the implementation of the MERGE statement in Microsoft SQL Server, please visit http://msdn.microsoft.com/en-us/library/bb510625(v=sql.100).aspx.

MERGE statatement has been supported since SQL Server 2008.

I hope this helps you to develop the best micro-orm available to the community of the .NET developers!

Keep going!

Kind regards,
Olli-Pekka Isola

@sapiens
Copy link
Owner

sapiens commented May 30, 2014

Thanks for your input, I'll take it into account

@sapiens sapiens added this to the ver 3.0 milestone May 30, 2014
@sapiens sapiens removed this from the ver 3.0 milestone Jun 28, 2014
@sapiens sapiens added this to the ver 3.0 milestone Jan 7, 2016
@jeremycook
Copy link

MySQL 5 also supports an "upsert" syntax as documented here:

http://dev.mysql.com/doc/refman/5.0/en/insert-on-duplicate.html

This blog post provides a great example of using it:

http://mechanics.flite.com/blog/2013/09/30/how-to-do-an-upsert-in-mysql/

mysql> select * from sakila.actor where actor_id = 50;
+----------+------------+-----------+---------------------+
| actor_id | first_name | last_name | last_update         |
+----------+------------+-----------+---------------------+
|       50 | NATALIE    | HOPKINS   | 2006-02-15 04:34:33 |
+----------+------------+-----------+---------------------+
1 row in set (0.01 sec)

mysql> insert into sakila.actor (actor_id, first_name, last_name, last_update) 
    -> values (50,'NATALIE','SMITH','2013-09-27 12:34:56')
    -> on duplicate key update
    ->   first_name = values(first_name),
    ->   last_name = values(last_name),
    ->   last_update = values(last_update);
Query OK, 2 rows affected (0.00 sec)

mysql> select * from sakila.actor where actor_id = 50;
+----------+------------+-----------+---------------------+
| actor_id | first_name | last_name | last_update         |
+----------+------------+-----------+---------------------+
|       50 | NATALIE    | SMITH     | 2013-09-27 12:34:56 |
+----------+------------+-----------+---------------------+
1 row in set (0.00 sec)

Some key takeaways from the docs:

With on duplicate key update, the affected-rows value per row is 1 if the row is inserted as a new row, 2 if an existing row is updated, and 0 if an existing row is set to its current values.

If a table contains an AUTO_INCREMENT column and INSERT ... UPDATE inserts a row, the LAST_INSERT_ID() function returns the AUTO_INCREMENT value. If the statement updates a row instead [a.k.a. the number of affected-rows is 2], LAST_INSERT_ID() is not meaningful.

@sapiens
Copy link
Owner

sapiens commented Jan 28, 2016

Cool, thank you!

@sapiens sapiens added the backlog label Feb 3, 2016
@sapiens sapiens removed this from the ver 3.0 milestone Feb 3, 2016
@dario-l
Copy link

dario-l commented Oct 26, 2020

Be careful with MERGE on MS SQL without transaction.
https://www.mssqltips.com/sqlservertip/3074/use-caution-with-sql-servers-merge-statement/

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

4 participants