0

Consider following tables:

product

+----+------+-------------+
| id | name | category_id |
+----+------+-------------+
| 1  | foo  |           1 |
+----+------+-------------+

categories

+----+------+
| id | name |
+----+------+
| 1  | food |
+----+------+

Now lets assume I someone POSTs a new product:

{
  "name": "bar",
  "category": "drink"
}

In this case we need to create the new category automatically:

INSERT IGNORE INTO categories (name) VALUES ('drink')

Then we finally could insert the actual product row:

INSERT INTO products (name, category_id) VALUES ('bar', SELECT id FROM categories WHERE name = 'drink')

However though this works it would require a transaction setup to be safe and as this does not seem to be a super complicated query I would like to know if it would be possible to merge both queries together (e.g. put the insert query of the categories into the select subquery of the product insertion)?

bodokaiser
  • 15,122
  • 22
  • 97
  • 140

2 Answers2

2

I would advise against INSERT IGNORE, for the reasons that @Bill Karwin so eloquently explains. Furthermore, as documented under INSERT ... ON DUPLICATE KEY UPDATE Syntax:

If a table contains an AUTO_INCREMENT column and INSERT ... ON DUPLICATE KEY UPDATE inserts or updates a row, the LAST_INSERT_ID() function returns the AUTO_INCREMENT value. Exception: For updates, LAST_INSERT_ID() is not meaningful prior to MySQL 5.1.12. However, you can work around this by using LAST_INSERT_ID(expr). Suppose that id is the AUTO_INCREMENT column. To make LAST_INSERT_ID() meaningful for updates, insert rows as follows:

INSERT INTO table (a,b,c) VALUES (1,2,3)
  ON DUPLICATE KEY UPDATE id=LAST_INSERT_ID(id), c=3;

One can then obtain the id (whether preexisting or newly inserted) as documented under How to Get the Unique ID for the Last Inserted Row:

If you insert a record into a table that contains an AUTO_INCREMENT column, you can obtain the value stored into that column by calling the mysql_insert_id() function.

[ deletia ]

When a new AUTO_INCREMENT value has been generated, you can also obtain it by executing a SELECT LAST_INSERT_ID() statement with mysql_query() and retrieving the value from the result set returned by the statement.

So, for example, one could simply do:

INSERT INTO categories (name) VALUES ('drink')
  ON DUPLICATE KEY UPDATE id=LAST_INSERT_ID(id);

INSERT INTO products (name, category_id) VALUES ('bar', LAST_INSERT_ID());

Of course, these statements would still need to be executed within a transaction if you require atomicity.

Community
  • 1
  • 1
eggyal
  • 122,705
  • 18
  • 212
  • 237
  • Hey, I read about "INSERT ... ON DUPLICATE KEY UPDATE" but decided against it as I read that it increased the auto increment counter. Is this true? Will a normal inserted row skip ids? – bodokaiser Mar 16 '14 at 09:50
  • 1
    @bodokaiser: See [Avoiding auto-increment holes on InnoDB with INSERT IGNORE](http://www.mysqlperformanceblog.com/2011/11/29/avoiding-auto-increment-holes-on-innodb-with-insert-ignore/). – eggyal Mar 16 '14 at 10:05
-1

No, that is not possible. What you can do however, is creating a BEFORE INSERT trigger on the product table and check inside the trigger, if the category already exists. In case it doesn't exist, you can create it there.

The trigger will be automatically executed in the same transaction as your INSERT-statement, so no additional transactions necessary there.

The only problem I see is, when you forget about the Trigger, you might start wondering why categories appear automatically for each new product with a new category (something similar happened to me).

Update:

You can see example of simple triggers in the MySQL Documentation

You can access the new to be inserted value in a BEFORE INSERT trigger with NEW.column_name so for you it would be NEW.category

peter
  • 14,348
  • 9
  • 62
  • 96