Clearing LAST_INSERT_ID() before inserting to tell if what's returned is from my insert
Asked Answered
P

5

16

LAST_INSERT_ID() returns the most recent id generated for the current connection by an auto increment column, but how do I tell if that value is from the last insert and not from a previous insert on the same connection?

Suppose I am using a connection from a pool, which may have inserted a row before I got the connection, and I execute an conditional insert:

insert into mytable (colA)
select 'foo' from bar
where <some condition>;
select LAST_INSERT_ID();

I have no way of knowing if the value returned is from my insert.

One way I thought of is:

@previousId := LAST_INSERT_ID();
insert into mytable (colA)
select 'foo' from bar
where <some condition>;
select if(LAST_INSERT_ID() != @previousId, LAST_INSERT_ID(), null);

Is there a way to "clear" the LAST_INSERT_ID() value, so I know it's a fresh value caused by my SQL if a non-zero value is returned?

Perrie answered 20/12, 2013 at 13:44 Comment(2)
This is a partially-solved problem. Could you post the original problem you are trying to solve?Boarer
What's wrong with using row_count, as Digital Chris suggested?Subsocial
P
5

Use ROW_COUNT() to determine if your conditional insert attempt was successful, and then return LAST_INSERT_ID() or a default value based on that:

select IF(ROW_COUNT() > 0, LAST_INSERT_ID(), 0);
Perrie answered 29/12, 2013 at 1:5 Comment(0)
A
13

I agree with @Digital Chris's answer, that you should not be determining whether an insertion succeeded or failed via inspection of the value returned by LAST_INSERT_ID(): there are more direct routes, such as the affected row count. Nevertheless, there might still be some requirement to obtain a "clean" value from LAST_INSERT_ID().

Of course, one problem with your proposed solution (of comparing against the pre-insertion value) is that it might happen that the insertion was successful and that its assigned auto-incremented value is coincidentally the same as that of the previous insertion (presumably on another table). The comparison could therefore lead to an assumption that the insertion failed, whereas it had in fact succeeded.

I recommend that, if at all possible, you avoid using the LAST_INSERT_ID() SQL function in preference for the mysql_insert_id() API call (via your driver). As explained under the documentation for the latter:

mysql_insert_id() returns 0 if the previous statement does not use an AUTO_INCREMENT value. If you need to save the value for later, be sure to call mysql_insert_id() immediately after the statement that generates the value.

[ deletia ]

The reason for the differences between LAST_INSERT_ID() and mysql_insert_id() is that LAST_INSERT_ID() is made easy to use in scripts while mysql_insert_id() tries to provide more exact information about what happens to the AUTO_INCREMENT column.

In any event, as documented under LAST_INSERT_ID(expr):

If expr is given as an argument to LAST_INSERT_ID(), the value of the argument is returned by the function and is remembered as the next value to be returned by LAST_INSERT_ID().

Therefore, before performing your INSERT, you could reset with:

SELECT LAST_INSERT_ID(NULL);

This also ought to reset the value returned by mysql_insert_id(), although the documentation suggests the call to LAST_INSERT_ID(expr) must take place within an INSERT or UPDATE statement—may require testing to verify. In any event, it ought to be pretty trivial to create such a no-op statement if so required:

INSERT INTO my_table (my_column) SELECT NULL WHERE LAST_INSERT_ID(NULL);

It may be worth noting that one can also set the identity and last_insert_id system variables (however these only affect the value returned by LAST_INSERT_ID() and not by mysql_insert_id()):

SET @@last_insert_id := NULL;
Abdication answered 22/12, 2013 at 17:8 Comment(0)
S
9

You speculate that you might get the previous LAST_INSERT_ID(), but practically, I don't see where this case would happen. If you're doing a conditional insert, you would HAVE to check whether it was successful before taking next steps. For example, you would always use ROW_COUNT() to check inserted/updated records. Pseudo-code:

insert into mytable (colA)
select 'foo' from bar
where <some condition>;

IF ROW_COUNT() > 0
    select LAST_INSERT_ID();
    -- ...use selected last_insert_id for other updates/inserts...
END IF;
Salverform answered 20/12, 2013 at 14:6 Comment(0)
P
5

Use ROW_COUNT() to determine if your conditional insert attempt was successful, and then return LAST_INSERT_ID() or a default value based on that:

select IF(ROW_COUNT() > 0, LAST_INSERT_ID(), 0);
Perrie answered 29/12, 2013 at 1:5 Comment(0)
T
4

Since LAST_INSERT_ID() is full of gotchas, I use an AFTER INSERT trigger to record the ID in a log table and in a manner consistent with the problem I'm trying to solve.

In your scenario, since the insert is conditional, use a unqiue identifier to "tag" the insert, then check for the presence of that tag in the insert log. If the identifying tag is present, your insert occurred and you have the inserted ID. If the identifying tag is not present, no insert occurred.

Reference Implementation

DELIMITER $$
CREATE TRIGGER MyTable_AI AFTER INSERT ON MyTable FOR EACH ROW
BEGIN
    INSERT INTO MyTable_InsertLog (myTableId, ident) VALUES (NEW.myTableId, COALESCE(@tag, CONNECTION_ID()));
END $$
DELIMITER ;

CREATE TABLE MyTable_InsertLog (
    myTableId BIGINT UNSIGNED PRIMARY KEY NOT NULL REFERENCES MyTable (myTableId),
    tag CHAR(32) NOT NULL
);

Example Usage

SET @tag=MD5('A');
INSERT INTO MyTable SELECT NULL,colA FROM Foo WHERE colA='whatever';
SELECT myTableId FROM MyTable_InsertLog WHERE tag=@tag;
DELETE FROM MyTable_InsertLog WHERE tag=@tag;

If the insert succeeds, you will get rows from the select -- and those rows will have your ID. No rows, no insert. Either way, delete results from the insert log so that you can reuse that tag in subsequent calls.

Traveller answered 20/12, 2013 at 13:58 Comment(2)
after insert trigger wouldn't help. my app code needs the id of the row just added, but only if it was added. i need to be able to distinguish that the last insert id returned from the function is from the sql i just executedPerrie
@Bohemian: Sure you can, if I'm understanding your use case correctly. See my edit for working example.Traveller
B
2

The LAST_INSERT_ID() function has a rather narrow scope of application: as MySQL does not support SQL SEQUENCEs, this is used to create a transaction that INSERTs data consistently into multiple tables, if one references the surrogate key from the other table:

CREATE TABLE foo (id INTEGER PRIMARY KEY AUTO_INCREMENT, value INTEGER NOT NULL);
CREATE TABLE bar (id INTEGER PRIMARY KEY AUTO_INCREMENT, fooref INTEGER NOT NULL);
INSERT INTO foo(value) VALUES ('1');
INSERT INTO bar(fooref) VALUES (LAST_INSERT_ID());

If you really want to use this in a conditional insert, you need to make the second INSERT conditional as well by joining the inserted values against the SELECT from the first INSERT and subsequently throwing away the extra columns (so that the second INSERT also has zero or one row).

I'd advise against that, though. The conditional insert in this manner is already somewhat brittle, and building on top of it will not add stability to your application.

Boarer answered 20/12, 2013 at 14:9 Comment(4)
No good. There is no second insert. The last insert id is used outside the database by the app code. Besides, this does't answer the question either.Perrie
The answer is "you're not expected to use LAST_INSERT_ID() in this way." That is why I commented asking for a description of the original problem.Boarer
I want to get the last inserted id from the sql into my application, but my sql does a conditional insert, so I need to be able to distinguish between the last inserted id coming from my insert or the previous insert for the connection I'm using. If it's not from my insert (attempt), I want it to be zero or null.Perrie
That is not a supported use case. Even if you find a solution that works, it may just break at any time in the future, and you get to keep the pieces.Boarer

© 2022 - 2024 — McMap. All rights reserved.