I'd like to insert two associated records into two tables. One record is associated with another record by a foreign key.
e.g. I have two records:
product (productid,product_name,category_id)
category (category_id,category_name)
But the category_id is auto_increment. So I don't know its value until I insert it into the category table. So here I have to invoke three sql queries, one is to insert record into category table, second is retrieval the category_id, the last sql query is to insert record into product table.
Overall, it seems the performance will not be good because of executing three sql queries. I just want to know is there any best practice for this scenario ? Thanks
Jeff Zhang
Check here how you can get the last inserted unique id
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.
Also note that
For LAST_INSERT_ID(), the most
recently generated ID is maintained in
the server on a per-connection basis
You don't have to invoke SQL to see the inserted autoincrement value, you can use last_insert_id function.
Related
Short version
Would someone provide an example of this? There are 3 SQL tables. Using INSERT ... SELECT, take data from table 1 and insert it into table 2. Then, INSERT rows into table 3, using the auto-increment id of each table 2 row just inserted using that INSERT ... SELECT statement.
INSERT ... SELECT creates multiple rows but you cannot obtain the auto-increment ID from them, for use in a subsequent INSERT statement.
Expanded version
I'm looking for an efficient way to use the auto increment IDs, created from an INSERT ... SELECT, in a second INSERT.
Imagine this scenario in a warehouse.
The warehouse receives a pallet of goods from a supplier. The pallet contains multiple individual items, which must be dispatched to different customers. The pallet is booked in, broken down and checked. Each item is then assigned to the correct customer and marked as "ready". At this point, each item is dispatched with the dispatch status recorded per customer. Each Customer's account balance is reduced by a given value based on the item.
The issue is linking the account reduction to the item dispatch. There are 3 tables:
GoodsIn: records the pallet arrival from the supplier
CREATE TABLE GoodsIn ('InID' 'CustomerID', 'ItemSKU_ID', 'HasBeenChecked')
GoodsOut: records the SKU dispatch to the Customer
CREATE TABLE GoodsOut ('OutID', 'CustomerID', 'ItemSKU_ID', 'DateDispatched')
Account: records each Customer transaction/balance
CREATE TABLE Ledger ('LedgerID', 'BalanceClose', 'AdjustmentAmount', 'CustomerID', 'ActionID')
(I've massively simplified this - please accept that GoodsIn and GoodsOut cannot be combined)
When an SKU is marked as ready for dispatch, I can use the following to automatically update the Ledger balance, taking the last balance row per customer and updating it
INSERT INTO Ledger (BalanceClose, AdjustmentAmount, CustomerID)
SELECT Ledger.BalanceClose +
(SELECT #Price:=ItemSKUData.ItemPrice FROM ItemSKUData WHERE ItemSKUData.ItemSKU_ID = GoodsIn.ItemSKU_ID) AS NEWBALANCECLOSE,
#Price AS ADJUSTMENTAMOUNT,
Ledger.CustomerID
FROM Ledger
INNER JOIN GoodsIn ON GoodsIn.CustomerID = Ledger.CustomerID
WHERE GoodsIn.HasBeenChecked = TRUE
AND Ledger.LedgerID IN (SELECT MAX(Ledger.LedgerID) FROM Ledger GROUP BY Ledger.CustomerID)
This all works absolutely fine - I get a new Ledger row, with the updated BalanceClose, for each GoodsIn row where GoodsIn.HasBeenChecked = TRUE. Each of these Ledger rows gets an auto-increment Ledger.LedgerID on INSERT.
I can then do pretty much the same code to INSERT into the GoodsOut table. Again as with Ledger, GoodsOut.OutID is an auto-increment ID.
I now need to link those Ledger rows (Ledger.ActionID) to the GoodsOut.OutID. This is the purpose of Ledger.ActionID - it needs to map to each GoodsOut.OutID, so that the reduction of the Ledger balance is linked to the action of sending the goods to the customer.
In theory, if this was a single INSERT and not an INSERT SELECT, I would simply take the GoodsOut.LAST_INSERT_ID() and use it on the INSERT INTO Ledger.
But because I'm using an INSERT ... SELECT, I can't get the auto-increment ID of each row.
The only way I can see to do this is to use a dummy column in the GoodsOut table, and store the GoodsIn.InID in it. I could then get the GoodsOut.OutID using a WHERE in the INSERT ... SELECT for the Ledger.
It doesn't feel very elegant and safe though.
So this is my question. I need to link table A to table B using table B's auto-increment ID, when all rows in BOTH table A and table B are created using INSERT ... SELECT.
You're right, when you do INSERT...SELECT for batch inserts, you don't have easy access to the auto-increment id. LAST_INSERT_ID() returns only the first id generated.
One documented behavior of bulk inserts is that the id's generated are guaranteed to be consecutive, because bulk inserts lock the table until the end of the statement.
https://dev.mysql.com/doc/refman/5.7/en/innodb-auto-increment-handling.html says:
innodb_autoinc_lock_mode = 1 (“consecutive” lock mode)
This is the default lock mode. In this mode, “bulk inserts” use the special AUTO-INC table-level lock and hold it until the end of the statement. This applies to all INSERT ... SELECT, REPLACE ... SELECT, and LOAD DATA statements. Only one statement holding the AUTO-INC lock can execute at a time.
This means if you know the first value generated, and the number of rows inserted (which you should be able to get from ROW_COUNT()), and the order of rows inserted, then you can reliably know all the id's generated.
The MySQL JDBC driver relies on this, for example. When you do a bulk insert, the full list of id's generated is not returned to the client (that is, the JDBC driver), but the driver has a Java method to return the full list. This is accomplished by Java code inferring the values, and assuming they are consecutive.
If i have insert query for example:
INSERT INTO user(username) VALUES('admin');
And then get the id of the inserted record with
LAST_INSERT_ID();
Looks find but what happens if between the insert and LAST_INSERT_ID() another insert is executed.
How MySQL knows to return the correct id (for the first or second insert) since no parameter is passed to LAST_INSERT_ID();
Is it save to use this function?
Thanks
I'm supposing that you mean what happen if i'm connected to the MySQL server and executing an INSERT but others are also doing insert, like updating a table on a website while client are currently using it.
If you go take a look at the documentation https://dev.mysql.com/doc/refman/8.0/en/information-functions.html there is a point that answers your questions:
The ID that was generated is maintained in the server on a
per-connection basis. This means that the value returned by the
function to a given client is the first AUTO_INCREMENT value generated
for most recent statement affecting an AUTO_INCREMENT column by that
client. This value cannot be affected by other clients, even if they
generate AUTO_INCREMENT values of their own. This behavior ensures
that each client can retrieve its own ID without concern for the
activity of other clients, and without the need for locks or
transactions.
This should be the same in MariaDB.
As discussed in the comment, you are wondering if you can use this in a php PDO environment. If you mean to use this directly from the database, it's a no, you won't be able to have the last inserted ID because you won't have the same client connection as PDO. If you want to use it directly from PDO please use the specific PDO function: http://php.net/manual/fr/pdo.lastinsertid.php , this should allow to do what you want.
If you insert multiple rows into a table using a single INSERT query, the LAST_INSERT_ID function returns the last insert id of the first row.
i.e
If your table id has 3 as column value and you will insert 4 rows in a single query then LAST_INSERT_ID will give you 4 instead of 7
If you insert 4 rows in 4 different insert query then LAST_INSERT_ID will give you 7
last_insert_id( ) Or mysqli_insert_id( ) will always return the id of last or most recent query. And also be noted that id field must have AUTO_INCREMENT enabled.
It doesn't give you the freedom to choose any specific table. Or you can't have id which is generated from your previous query.
So, from this point it serves a very small purpose. Only the last id, it doesn't matter which table.
To get last id from any specific table this query would be helpful : "SELECT id FROM table_name ORDER BY id DESC LIMIT 1"
I want to insert multiple rows into a table, using a single INSERT statement. This is no problem, since SQL offers the option to provide multiple rows as parameter for a single INSERT statement. Now, those rows contain an ID field that is incremented automatically, i.e. its value is set by the database, not by my code.
As a result, I would like to get the ID values of the inserted rows. My basic question is: How do I do that for MariaDB / MySQL?
As it turns out, this is pretty simple, e.g. in PostgreSQL, as PostgreSQL has the RETURNING clause for INSERT which returns the desired values for one or even for multiple rows. This is exactly what I want and it works.
Unfortunately, neither MariaDB nor MySQL have PostgreSQL's RETURNING clause, so I need to fallback to something such as LAST_INSERT_ID(), but this only returns the ID of the single last inserted row, even if multiple rows were inserted using a single INSERT. How do I get all the ID values?
My code currently looks like this:
INSERT INTO mytable
(foo, bar)
VALUES
('fooA', 'barA'),
('fooB', 'barB');
SELECT LAST_INSERT_ID() AS id;
How can I solve this issue in a way that works even with concurrent writes?
(And no, it's not an option to change to a UUID field, or something like this; the auto-increment field is given, and can not be changed.)
MySQL & MariaDB have the LAST_INSERT_ID() function, and it returns the id generated by the most recent INSERT statement in your current session.
But when your INSERT statement inserts multiple rows, LAST_INSERT_ID() returns the first id in the set generated.
In such a batch of multiple rows, you can rely on the subsequent id's being consecutive. The MySQL JDBC driver depends on this, for example.
If the rows you insert include a mix of NULL and non-NULL values for the id column, you have a risk of messing up this assumption. The JDBC driver returns the wrong values for the set of generated id's.
As stated in the comments, you can capture the inserted IDs (SQL Server):
use tempdb
create table test (
id int identity(1,1) primary key,
t varchar(10) null
)
create table ids (
i int not null
)
insert test(t)
output inserted.id into ids
values (null), (null), (null)
select *
from test
select *
from ids
Just wanted to runt this by someone who knows more MySQL than myself
I perform a MySQL insert where I insert say 10 rows in one query. Then when you get MySQL's last insert id, it gives you the ID of the first inserted ID.
Is it safe to assume that the other IDs are consecutively the insert_id - insert_id+9? or is there any possible way this could not turn out to be the case?
Thanks
Yes, there is a possibility this will not always be the case.
With innodb_autoinc_lock_mode = 2, the rows inserted by a single INSERT statement can be assigned AUTO_INCREMENT values that are not consecutive (when concurrent INSERT statements are running.)
http://dev.mysql.com/doc/refman/5.1/en/innodb-auto-increment-handling.html
I need to use the resulting key of one insert, in another insert. Is this possible with MySQL?
mysql_insert_id returns the id of last inserted record you can use that one
Retrieves the ID generated for an
AUTO_INCREMENT column by the previous
query (usually INSERT).