5

I'm having trouble getting an INSERT query to execute properly, and I can't seem to find anything on Google or Stack Overflow that solves this particular issue.

I'm trying to create a simple table for featured entries, where the entry_id is saved to the table along with it's current order.

My desired output is this:

If the featured table currently has these three entries:

featured_id    entry_id    featured_order
1              27          0
2              54          1
4              23          2

I want the next entry to save with featured_order=3.

I'm trying to get the following query to work with no luck:

INSERT INTO `featured`
(
    `entry_id`, `featured_order`
)
VALUES
(
    200,
    (SELECT COUNT(*) AS `the_count` FROM `featured`)
)

The error I'm getting is: You can't specify target table 'featured' for update in FROM clause.

Can anyone help with a solution that gets the count without causing an error?

Thanks in advance!

OMG Ponies
  • 325,700
  • 82
  • 523
  • 502
jlengstorf
  • 447
  • 1
  • 5
  • 10
  • Possible Duplicate http://stackoverflow.com/questions/45494/sql-delete-cant-specify-target-table-for-update-in-from-clause – Tim May 27 '11 at 01:52
  • 2
    Not a duplicate - that question was for deletion; this is for insertion. Doesn't apply at all here. – Bohemian May 27 '11 at 03:09

5 Answers5

15

Here is a cool thing: MySQL's INSERT . . . SELECT:

INSERT INTO `featured`
(
    `entry_id`, `featured_order`
)
SELECT 200, COUNT(*) + 1
FROM `featured`

No subquery required.


@Bohemian has a good point:

Better to use max(featured_order) + 1 if you use this approach

So a better query would probably be:

INSERT INTO `featured`
(
    `entry_id`, `featured_order`
)
SELECT 200, MAX(`featured_order`) + 1
FROM `featured`

His trigger method describe in his answer is also a good way to accomplish what you want.


The potential problem with query 1 is if you ever delete a row the rank will be thrown off, and you'll have a duplicate in featured_order. With the second query this is not a problem, but you will have gaps, just as if you were using an auto-increment column.

If you absolutely must have an order with no gaps the best solution I know of is to run this series of queries:

SET @pos:=0;

DROP TABLE IF EXISTS temp1;

CREATE TEMPORARY TABLE temp1 LIKE featured;

ALTER TABLE featured ORDER BY featured_order ASC;

INSERT INTO temp1 (featured_id, entry_id, featured_order) 
SELECT featured_id, entry_id, @pos:=@pos+1 FROM words;

UPDATE featured 
JOIN temp1 ON featured.featured_id = temp1.featured_id 
SET featured.rank = temp1.rank;

DROP TABLE temp1;

Whenever you delete a row

jisaacstone
  • 4,234
  • 2
  • 25
  • 39
  • I don't think this will work. From the docs for [`INSERT ... SELECT`](http://dev.mysql.com/doc/refman/5.5/en/insert-select.html): "However, you cannot insert into a table and select from the same table in a subquery." – Ted Hopp May 27 '11 at 01:59
  • 1
    Problems with this: a) you'll want to add one to count(*), b) what if numbering doesn't align with count(*) - eg if rows get deleted. Better to use max(featured_order) + 1 if you use this approach – Bohemian May 27 '11 at 01:59
  • @JIStone - I admit I didn't try it. The documentation (this was for 5.5) seemed pretty clear. But then, it may be wrong. What version of MySQL did you use? – Ted Hopp May 27 '11 at 02:18
  • the SELECT statement in this example isn't considered a subquery. – dtbarne May 27 '11 at 05:16
  • It's very odd, since the warning about a subquery is on the manual page about INSERT...SELECT (as well as on the page about subquery syntax). – Ted Hopp May 27 '11 at 14:05
  • Very cool! I'd never seen `INSERT...SELECT` before. This worked for me using `MAX(featured_order) + 1` — I've got a plan to keep things sequenced properly that should work out alright. Thanks so much! – jlengstorf May 27 '11 at 18:39
3
INSERT INTO `featured`
(
    `entry_id`, `featured_order`
)
VALUES
(
    200,
    (SELECT COUNT(*) AS `the_count` FROM `featured` F1)
)

Correction is just adding "F1" table alias.

This standard sql solution works fine on various dbms (not only mysql)


I also suggest an improvement over:

  • SELECT COUNT(*) +1 (Problem: if some row gets deleted you may collide with existing index)

  • SELECT MAX(featured_order)+1 (Problem: the first insert with empty table gets error)

SELECT (COALESCE(MAX(featured_order), 0)+1) (no Problem)

3

Use a trigger:

drop trigger if exists featured_insert_trigger; 

delimiter //
create trigger featured_insert_trigger before insert on featured
for each row
begin
  set new.featured_order = ifnull((select max(featured_order) from featured), -1) + 1;
end; //
delimiter ;

Now your inserts look like this:

insert into featured (entry_id) values (200);

featured_order will be set to the highest featured_order value plus one. This caters for rows being deleted/updated and always guarantee uniqueness.

The ifnull is there in case there are no rows in the table, in which case the first value will be zero.

This code has been tested as works correctly.

Bohemian
  • 412,405
  • 93
  • 575
  • 722
  • I did a little research on triggers (I'd never heard of them before), and this definitely sounds like the best solution. However, when I tried it, I get this error: `TRIGGER command denied to user 'xxxx'@'xxxx' for table 'featured'` I've given the MySQL user "all privileges" according to HostGator, so it looks like a trigger isn't in the cards for this project. :/ – jlengstorf May 27 '11 at 18:23
  • Does this work if there are two inserts exactly at the same time? – xms Aug 11 '17 at 08:04
  • @xms there are never any updates/inserts that happen at "exactly the same time". The database serialises all mutating queries. Each always starts and completes atomically. – Bohemian Aug 11 '17 at 10:36
2

You have to simpley use alias that will solve the problem :

INSERT INTO `featured`
(
    `entry_id`, `featured_order`
)
VALUES
(
    200,
    (SELECT COUNT(*) AS `the_count` FROM `featured` as f1)
)
Angelin Nadar
  • 8,944
  • 10
  • 43
  • 53
-1

From the MySQL manual regarding subqueries:

Another restriction is that currently you cannot modify a table and select from the same table in a subquery.

Perhaps an alias or a join (otherwise useless) in the subquery would help here.

EDIT: It turns out that there's a work-around. The work-around is described http://www.xaprb.com/blog/2006/06/23/how-to-select-from-an-update-target-in-mysql/.

Ted Hopp
  • 232,168
  • 48
  • 399
  • 521