110

I am having a difficult time forming a conditional INSERT

I have x_table with columns (instance, user, item) where instance ID is unique. I want to insert a new row only if the user already does not have a given item.

For example trying to insert instance=919191 user=123 item=456

Insert into x_table (instance, user, item) values (919191, 123, 456) 
    ONLY IF there are no rows where user=123 and item=456 

Any help or guidance in the right direction would be much appreciated.

The Unknown
  • 19,224
  • 29
  • 77
  • 93

13 Answers13

136

If your DBMS does not impose limitations on which table you select from when you execute an insert, try:

INSERT INTO x_table(instance, user, item) 
    SELECT 919191, 123, 456
        FROM dual
        WHERE NOT EXISTS (SELECT * FROM x_table
                             WHERE user = 123 
                               AND item = 456)

In this, dual is a table with one row only (found originally in Oracle, now in mysql too). The logic is that the SELECT statement generates a single row of data with the required values, but only when the values are not already found.

Alternatively, look at the MERGE statement.

Community
  • 1
  • 1
Jonathan Leffler
  • 730,956
  • 141
  • 904
  • 1,278
  • 1
    Does `dual` need to be created before hand in this case or is it some kind of special "temporary" table created by the query for the use of the query alone? – itsmequinn Feb 24 '12 at 17:13
  • 8
    If you don't have `dual` already, you need to create it. `CREATE TABLE dual ( dummy CHAR(1) DEFAULT 'x' NOT NULL CHECK (dummy = 'x') PRIMARY KEY ); INSERT INTO dual VALUES('x'); REVOKE ALL ON dual FROM PUBLIC; GRANT SELECT ON dual TO PUBLIC;` – Jonathan Leffler Feb 24 '12 at 17:18
  • 11
    Note that, in MySQL, you don't really need to have a table called 'dual' to exist: it is a special table-name that can be used to select anything from it. – Christopher Schultz Feb 12 '13 at 15:44
  • @ChristopherSchultz: since the MySQL [SELECT](http://dev.mysql.com/doc/refman/5.6/en/select.html) syntax page documents DUAL, you're right that you do not need to create the table. My "If you don't have `dual` already" statement has a false precedent (because MySQL does already have the `dual`) and remains a true statement overall (because in logic _IF false THEN consequent_ evaluates to true regardless of the consequent). But thanks for noting that DUAL does indeed exist in MySQL. – Jonathan Leffler Feb 12 '13 at 16:34
  • 3
    MySQL "respects" the concept of "dual" but it does not actually exist. For example, if you `SELECT COUNT(*) FROM dual` you always get `1`, and if you `SELECT 'foo' FROM dual` you always get `foo`. But you can't `SELECT * FROM dual` and you can't `DESCRIBE dual` or anything like that. I haven't checked, but I also don't think you can revoke permissions on `dual`, either. So it's worth pointing out that it works as you expect... except when it doesn't ;) – Christopher Schultz Feb 12 '13 at 17:43
  • How do you make mysql raise an error (`Duplication error '1062'`) when duplicates are found? – hlin117 Nov 18 '16 at 03:02
  • Doesn't it do that automatically? If you have unique constraints on the relevant sets of columns, it shouldn't have any option but to raise an error. If you haven't told it which sets of columns need to be unique, it won't be able to tell you when you break the rules. More detail than requires you to look in the manual (the same as I'd have to). – Jonathan Leffler Nov 18 '16 at 03:04
  • Does it lock the rows corresponding to ```SELECT * FROM x_table WHERE user = 123 AND item = 456``` while the insert is happening? – user830818 Apr 12 '19 at 21:03
  • @user830818 — that's a level of detail that I don't know. It might depend on the isolation level at which you're working. In theory, if you were working with 'SERIALIZABLE' or 'REPEATABLE READ' isolation, then there should be appropriate locks (shared locks) applied to ensure things are repeatable. But I don't know the MySQL locking strategies well enough to be able to pontificate. – Jonathan Leffler Apr 12 '19 at 21:06
57

You can also use INSERT IGNORE which silently ignores the insert instead of updating or inserting a row when you have a unique index on (user, item).

The query will look like this:

INSERT IGNORE INTO x_table(instance, user, item) VALUES (919191, 123, 456)

You can add the unique index with CREATE UNIQUE INDEX user_item ON x_table (user, item).

MrD
  • 2,405
  • 3
  • 22
  • 23
44

Have you ever tried something like that?

INSERT INTO x_table (instance, user, item)
SELECT 919191 as instance, 123 as user, 456 as item
FROM x_table
WHERE (user=123 and item=456)
HAVING COUNT(*) = 0;
trinalbadger587
  • 1,905
  • 1
  • 18
  • 36
temple
  • 441
  • 4
  • 2
10

With a UNIQUE(user, item), do:

Insert into x_table (instance, user, item) values (919191, 123, 456) 
  ON DUPLICATE KEY UPDATE user=123

the user=123 bit is a "no-op" to match the syntax of the ON DUPLICATE clause without actually doing anything when there are duplicates.

Alex Martelli
  • 854,459
  • 170
  • 1,222
  • 1,395
  • 1
    I can't setup a Unique(user, item) because there can be instances when multiple instances with same user and item... just not when I am doing this insert. – The Unknown May 27 '09 at 04:59
3

In case you don't want to set a unique constraint, this works like a charm :

INSERT INTO `table` (`column1`, `column2`) SELECT 'value1', 'value2' FROM `table` WHERE `column1` = 'value1' AND `column2` = 'value2' HAVING COUNT(`column1`) = 0

Hope it helps !

Gew
  • 85
  • 3
3

What you want is INSERT INTO table (...) SELECT ... WHERE .... from MySQL 5.6 manual.

In you case it's:

INSERT INTO x_table (instance, user, item) SELECT 919191, 123, 456 
WHERE (SELECT COUNT(*) FROM x_table WHERE user=123 AND item=456) = 0

Or maybe since you're not using any complicated logic to determiante whether to run the INSERT or not you could just set a UNIQUE key on the combination of these two columns and then use INSERT IGNORE.

martin
  • 93,354
  • 25
  • 191
  • 226
  • Did you run your answer? I keep getting `ERROR 1064 (42000): You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'where...` (running 5.6.34) – hlin117 Nov 18 '16 at 00:10
  • I think you need to say `from dual` before `where`. – hlin117 Nov 18 '16 at 00:13
  • @hlin117 In MariaDB `FROM DUAL` is the default if no tables were referenced ([see this documentation](https://mariadb.com/kb/en/dual/)). – Yeti Jan 29 '20 at 23:46
1

You can use the following solution to solve your problem:

INSERT INTO x_table(instance, user, item) 
    SELECT 919191, 123, 456
        FROM dual
        WHERE 123 NOT IN (SELECT user FROM x_table)
Grant Miller
  • 27,532
  • 16
  • 147
  • 165
Keith Becker
  • 556
  • 1
  • 6
  • 16
  • While this code may answer the question, providing additional context regarding how and/or why it solves the problem would improve the answer's long-term value. – Nic3500 Aug 16 '18 at 00:13
1

If you add a constraint that (x_table.user, x_table.item) is unique, then inserting another row with the same user and item will fail.

eg:

mysql> create table x_table ( instance integer primary key auto_increment, user integer, item integer, unique (user, item));
Query OK, 0 rows affected (0.00 sec)

mysql> insert into x_table (user, item) values (1,2),(3,4);
Query OK, 2 rows affected (0.00 sec)
Records: 2  Duplicates: 0  Warnings: 0

mysql> insert into x_table (user, item) values (1,6);
Query OK, 1 row affected (0.00 sec)

mysql> insert into x_table (user, item) values (1,2);
ERROR 1062 (23000): Duplicate entry '1-2' for key 2
NickZoic
  • 7,575
  • 3
  • 25
  • 18
1

Although it's good to check for duplication before inserting your data I suggest that you put a unique constraint/index on your columns so that no duplicate data can be inserted by mistake.

Beatles1692
  • 5,214
  • 34
  • 65
0

So this one stands for PostgreSQL

INSERT INTO x_table
SELECT NewRow.*
FROM (SELECT 919191 as instance, 123 as user, 456 as item) AS NewRow
LEFT JOIN x_table
ON x_table.user = NewRow.user AND x_table.item = NewRow.item
WHERE x_table.instance IS NULL
xsubira
  • 474
  • 1
  • 5
  • 14
0

I have found out today that a SELECT statement can have a WHERE condition even if it has no FROM clause and does not read any tables at all.

This makes it very easy to conditionally insert something using this construct:

SELECT ... INTO @condition;
-- or if you prefer: SET @condition = ...

INSERT INTO myTable (col1, col2) SELECT 'Value 1', 'Value 2' WHERE @condition;

Tested this on MySQL 5.7 and MariaDB 10.3.

jlh
  • 4,349
  • 40
  • 45
  • Works on MySQL 5.7 but does not work on MySQL 5.5.51. Found this out the hard way. I have a development server with MySQL 5.7, but my deployment server is MySQL 5.5. 'dual' works with MySQL 5.5.51 – Manish Mar 13 '22 at 21:15
0

Slight modification to Alex's response, you could also just reference the existing column value:

Insert into x_table (instance, user, item) values (919191, 123, 456) 
  ON DUPLICATE KEY UPDATE user=user
Danny
  • 11
-1
Insert into x_table (instance, user, item) values (919191, 123, 456) 
    where ((select count(*) from x_table where user=123 and item=456) = 0);

The syntax may vary depending on your DB...

Rick J
  • 2,723
  • 2
  • 22
  • 31