0

If I have table structure as so:

CREATE TABLE a (
  aid INT AUTO_INCREMENT,
  acol1 INT,
  acol2 INT,
  PRIMARY KEY(aid);
)

CREATE TABLE b (
  bid INT AUTO_INCREMENT,
  bcol INT,
  PRIMARY KEY(bid);
)

and run the statement:

`INSERT INTO a SET acol1 = (SELECT MAX(acol1) + 1 as newMax FROM a WHERE id = ?)

Is there anyway for me to retrieve the value of newMax after the query is executed? I am looking for something similar to last_insert_id() in PHP but for temporary values in the query.

Obviously I am trying to not query the database again if possible.

EDIT:

Actual situation:

CREATE TABLE group (
  group_id INT AUTO_INCREMENT,
  PRIMARY KEY(group_id)
) ENGINE = MyISAM;

CREATE TABLE item (
  group_refid INT, --references group.group_id
  group_pos INT, --represents this item's position in its group
  text VARCHAR(4096), --data
  PRIMARY KEY(group_refid, group_pos)
) ENGINE = MyISAM;

So the issue is that when I add a new item to a group, I need to make its

group_pos = MAX(group_pos) WHERE group_refid = ?

which would require a query with something like:

INSERT INTO item (group_refid, group_pos) SET group_refid = 1, group_pos = (SELECT MAX(group_pos) + 1 FROM item WHERE group_refid = 1);

As you know, this query does not work. There is added complexity that there may not be an item entry yet for a particular group_id.

I am trying to get this all into one atomic statement to prevent race conditions.

thatidiotguy
  • 8,701
  • 13
  • 60
  • 105
  • You can't insert _and_ retrieve in one single statement. An obvious answer with an extra query would just to query the row in `b` which matches `LAST_INSERT_ID()`. However, if you _need_ it in one call (and I struggle to know _why_), you could create a stored procedure which does this. – Wrikken Feb 27 '14 at 20:29
  • @Wrikken Are stored procedures atomic? That is, can other SQL statements affecting the rows they are working on be run at the same time? – thatidiotguy Feb 27 '14 at 20:35
  • No, [but you can start a transaction in one](http://stackoverflow.com/questions/18817148/how-can-i-use-transactions-in-my-mysql-stored-procedure). – Wrikken Feb 27 '14 at 20:39
  • @Wrikken Doesn't that require InnoDB? Currently all of the tables I work on are using MyISAM. – thatidiotguy Feb 27 '14 at 20:39
  • Yes, it would. What are the exact clashes or problems do you expect though? Just running `INSERT INTO ...SELECT ..` & then `SELECT FROM b WHERE bid=LAST_INSERT_ID()` would give you the guaranteed correct answer. – Wrikken Feb 27 '14 at 20:41
  • @Wrikken I may have to edit and put exactly what I am doing which will take me a few minutes. – thatidiotguy Feb 27 '14 at 20:43
  • No problem, better a thorough full question then having to discard all kinds of seemingly correct answers which are not valid for your situation due to omitted requirements ;) – Wrikken Feb 27 '14 at 20:46
  • @Wrikken Ok, edited with the situation at hand albeit with simplified table structure. – thatidiotguy Feb 27 '14 at 20:47
  • It seems to me a query like `INSERT INTO item (group_refid, group_pos) SELECT 1, MAX(group_pos) + 1 FROM items WHERE group_refid = 1;` would work, you can just `SELECT` the literal values you want to supply the insert, and mention the columns you want to have in there as well. It seems however a good candidate for a `ON BEFORE INSERT` trigger. – Wrikken Feb 27 '14 at 20:54
  • @Wrikken so that definitely does the trick! Now I just have to use some IF statements to be sure 0 gets inserted properly (when no items are associated with the group yet). If you create an answer I will accept it. – thatidiotguy Feb 27 '14 at 20:58
  • Scratch that query: it would indeed not work very well if there is no item for that group yet (although the trigger could be made to work). You could do a `INSERT INTO item (group_refid,group_pos) SELECT 1, (SELECT IFNULL(MAX(group_pos),0) + 1 FROM item WHERE group_refid=1)` – Wrikken Feb 27 '14 at 20:59

4 Answers4

1

you cant. insert query is for insering not selecting.

You must run other query like that

   SELECT MAX(acol1) + 1 as newMax FROM a WHERE acol2 = ?

for more read this

Community
  • 1
  • 1
echo_Me
  • 37,078
  • 5
  • 58
  • 78
  • This will work, most of the time. We do note that this does leave a small window for a race condition, where the second execution may not return the same value as the previous SELECT (i.e. another session might insert/update/delete rows in `a` between our two statements) unless both statements are in the context of the same InnoDB transaction with REPEATABLE READ isolation level, or absent some concurrency killing locking. If we need to "know" the value we are inserting/did insert, the better practice is to do the SELECT first, and then use the value returned from that in a separate INSERT. – spencer7593 Feb 27 '14 at 21:07
1

I think you can do:

INSERT INTO b
    SET bcol = (SELECT @acol := MAX(acol1) + 1 as newMax FROM a WHERE acol2 = ?);

Then you can use the variable @acol to get the value you want.

EDIT:

Is this what you want?

INSERT INTO item (group_refid, group_pos) 
    SELECT 1, MAX(group_pos) + 1
    FROM item
    WHERE group_refid = 1;
Gordon Linoff
  • 1,242,037
  • 58
  • 646
  • 786
1
INSERT INTO item (group_refid,group_pos) 
SELECT 1, (
   SELECT IFNULL(MAX(group_pos),0) + 1 
   FROM item 
   WHERE group_refid=1
);

However, if we're talking MyISAM tables explicitly, not another engine, this would work:

mysql> CREATE TABLE items (group_refid INT, group_pos INT AUTO_INCREMENT, PRIMARY KEY(group_refid,group_pos)) ENGINE=MyISAM;
Query OK, 0 rows affected (0.12 sec)

mysql> INSERT INTO items (group_refid) VALUES (1),(2),(1),(1),(2),(4),(2),(1);
Query OK, 8 rows affected (0.02 sec)
Records: 8  Duplicates: 0  Warnings: 0

mysql> SELECT * FROM items ORDER BY group_refid, group_pos;
+-------------+-----------+
| group_refid | group_pos |
+-------------+-----------+
|           1 |         1 |
|           1 |         2 |
|           1 |         3 |
|           1 |         4 |
|           2 |         1 |
|           2 |         2 |
|           2 |         3 |
|           4 |         1 |
+-------------+-----------+

However, that AUTO_INCREMENT on a second column in the PK is not portable to another database engine.

Wrikken
  • 69,272
  • 8
  • 97
  • 136
  • Oh wow, I did not know that `AUTO_INCREMENT` would behave like that when the primary key is a composite. That is very cool. Too bad its not zero indexed like programming languages but I can solve that in my code. – thatidiotguy Feb 27 '14 at 21:08
  • Am I correct in assuming that `last_insert_id` will refer to the auto_increment column? – thatidiotguy Feb 27 '14 at 21:12
  • 1
    +1. It bears repeating: this behavior of `AUTO_INCREMENT` is specific to the **MyISAM** storage engine, it does NOT work with InnoDB. (Wrikken did point that out; it's important enough that I'm repeating it here to underline it.) – spencer7593 Feb 27 '14 at 21:12
  • @spencer7593: indeed. I'll add some bolding to that. – Wrikken Feb 27 '14 at 21:14
0

Not directly in the statement, no. You'll need a separate statement to retrieve values.

But, you could "capture" the value from the SELECT into a user-defined variable, and then retrieve that with a SELECT (in the same database session), if you needed to "know" the value returned from the SELECT.

For example:

INSERT INTO b (bcol) 
SELECT @bcol := (MAX(a.acol1) + 1) AS newMax
  FROM a WHERE a.acol2 = ?)

SELECT @bcol + 0 AS new_bcol

NOTE:

Note that the user-defined variable assigned in the select is subject to modification elsewhere in the session, for example, it could be overwritten by the execution of a trigger defined the target table of the INSERT.

As an edge case, not that anyone would do this, but it's also possible there might be a BEFORE INSERT trigger that modifies the value of bcol, before it gets inserted. So, if you need to "know" the value that was actually inserted, that would be available in an AFTER INSERT trigger. You could capture that in a user-defined variable in that trigger.

Running a second, separate query against the a table is subject to a race condition, a small window of opportunity for a another session to insert/update/delete a row in table a, such that it's possible that a second query could return a different value than the first query... it might not be the value that was retrieved the first time. Unless of course you are within the context of an InnoDB transaction with REPEATABLE READ isolation level, or you've implemented some concurrency-killing locking strategy.

spencer7593
  • 106,611
  • 15
  • 112
  • 140