views:

42

answers:

3

I want to execute a query of the form:

INSERT INTO table(field1, field2) SELECT field1, field2 FROM table WHERE id IN ( 4, 8, 15, 16, 23, 42 )

Where id is the auto_increment'ing primary key of table.

After executing this statement, I want to know what all the new id's that just got generated are. How can I recover this information? I'm interested in (maybe) doing this in a stored procedure and returning the list of newly generated id's to my application code (lets say PHP), and I would like the list to be in correlated order to what appears in the IN clause of the SELECT subquery. I'm thinking this would save me a lot of sequential INSERTs in my application code. Is that achievable?

+3  A: 

I don't think so. At least not in a reliable way. LAST_INSERT_ID() will return a first auto generated id from extended insert. You could assume that all higher than this were also inserted during this insert, but this can be false in many cases (especially when there are more than one person working on a database at one time).

Mchl
Would not work reliably indeed. In a stored procedure, one could just loop with a cursor over the select, doing single inserts.
Wrikken
@Wrikken - yes, if they were Eeeeevillll! ;)
AllenG
would that be significantly faster than issuing the queries sequentially from my application code (i.e. over the network)?
vicatcu
what if i issued my INSERT INTO ... SELECT in a TRANSACTION?
vicatcu
or lock table for a moment
killer_PL
Doing this in transaction will take most of the obstacles out of the way. The only other thing that could break it, that I can think of, is non sequential auto-id generation in multi-master replication setups.
Mchl
A: 

You can only get the LAST insert id BUT you can get the affected rows...

so ids will be

  $ids = range($lastID - (--$rows), $lastID);

--$rows as you want to maintain the last id too.

eg. last insert was 18 and 3 rows affected so 16,17, 18 will beyour ids but 18 - 3 would yield 15 and hence give ids 15,16,17,18 which would be wrong!

ToonMariner
lets say you had more like 8000 rows affected...
vicatcu
same holds true...
ToonMariner
I think the the problem is you can't guarantee that the inserts occur without interruption... so I'm not guaranteed to have sequential auto_increment id's generated without interruption.
vicatcu
PHP is not multi-threaded so any database queries will be 'queued' if executed via php. It doesn't matter if you have 200 people connected - any other queries will run after this one has completed.The alternative is to get a write lock on your table then nothing at all could interfere so you can be confident the lastinsert id and affected rows calculation will deliver the right result.
ToonMariner
+3  A: 

If you have control over the table (or can create another table) how about storing a "transaction_id?"

If you added it to your table, you could do something like this:

Declare @tranId int
Select @tranId = Max(transaction_id) + 1 from [table]
Insert Into [table] (field1, field2, transactionId)
Select field1, field2, @tranId
From //the rest of your query

You'd then pull out the new Ids:

Select Id from [table] where transaction_id = @tranId

As a note, you'd either need to run that query immediately as part of your stored procedure, or you'd need to store the transactionId you used somewhere so that you can pull out the correct rows without worrying about multiple concurrent inserts.

AllenG
Do a rewrite to MySQL and you may have a point :)
Wrikken
Dang it, completely missed the MySql tag. Not sure how... Though the only thing that wouldn't be universal to most varieties of Sql would be that call to Max(), I'd think...
AllenG
Nevertheless this is an intriguing answer... If I just kept a dummy table with nothing but an id field, then someone wishing to do this type of query could first do an insert into the dummy table, from which they can recover the insert id, so it becomes their transaction_id "ticket". Then I can SELECT it as a constant in my INSERT INTO ... SELECT statement. So long as the inserts happen "in order" I should subsequently be able to recover them by doing a SELECT on my transaction ticket ordered by id... thoughts?
vicatcu
We actually have to do something like this with a process at work, and it works relatively well. We implement it as a separate table and all it holds is the list of those ids (and is set to flip after 99). Add the "transaction_id" column to your base table, and grab the most recent "id" from your secondary table and you'd probably be okay. You might also want to store a DateTime in case multiple inserts were done between returning the new ids.
AllenG
I'm accepting this answer as inspiration for something to try.
vicatcu