On the manual it's stated
Returns the ID of the last inserted row, or the last value from a sequence object, depending on the underlying driver.
Where if there's no $name variable passed as the first parameter to PDO::LastInsertID(), it'll check the last row that was inserted into the database. The state of this function also can be determined to work or not by the drivers that are stated, if you're using MySQL you're fine as that's heavily supported, but if you're using a database that doesn't support the OID or AUTO_INCRAMENT table definitions, PDO::LastInsertId()'s most likely not going to work.
That's all good and makes sense, but how is this achieved?
I understand PDO is just another abstraction layer to database connection, but we're also talking about the mysqli_insert_id function as well.
My idea of this method was that there's a cache (not on PHP's side), which I later discovered was true when reading this bit of code from warezthebeef at gmail dot com on the manual page.
// Assume $dbh connection handle is already established
$sql = "INSERT INTO product (product_name) OUTPUT INSERTED.product_id VALUES (?)";
$sth = $dbh->prepare($sql);
$sth->execute(array('widgets'));
$temp = $sth->fetch(PDO::FETCH_ASSOC);
echo "<pre>",print_r($temp),"</pre>";
This would display:
Array
(
[product_id] => E1DA1CB0-676A-4CD9-A22C-90C9D4E81914
)
This is cause the table in the database would contain the row product_id and in the SQL you're stating to output that row after inserting, which would be the id for more common-ground terms, and if using AUTO_INCRAMENT would just be a single number. (you can read more into it by looking here) (this is also achievable with the RETURNING SQL keyword)
So, in a way if we didn't want to use the PDO::LastInsertID() method, we could just use:
$sql = "INSERT INTO table (name, country) OUTPUT INSERTED.id VALUES (?,?)";
$sth = $dbh->prepare($sql);
$sth->execute( ['jack', 'Australia'] );
$temp = $sth->fetch(PDO::FETCH_ASSOC);
echo $temp['id']
Which should output the new ID we just inserted into the database, noting that we use an AUTO_INCRAMENT primary or OID column.
So in my head (that's why I'm asking this question) PDO could be caching the data in PHP's Temp then retrieving or writing over it when the method is called, possibly adding the RETURNING id or such to it, but that's not the way it'd work since it's based on the OID column.
I answered half of the question - I'm just wondering how the method actually stores the last ID or pulls it
Thanks!
The exact implementation depends on the driver. I can only describe how it works for MySQL because that's what I'm familiar with.
As we described in the answers to How does MySqlCommand.LastInsertedId work? question:
If MySQL successfully executes a query requested by a client, then MySQL sends an OK_Packet as a response.
In the payload of the OK_Packet MySQL includes the last inserted id (see documentation linked above):
Type | Name | Description
------------|----------------|-----------------------------------
int<1> | header | [00] or [fe] the OK packet header
int<lenenc> | affected_rows | affected rows
int<lenenc> | last_insert_id | last insert-id
...
On the server no select last_insert_id() is executed to populate this value into the OK_packet. The driver retrieves the last inserted id from the packet and PDO in turn retrieves the value from the driver.
Related
I often use the function rowCount of PDO like this for example:
$sql = $dataBase->prepare('SELECT email, firstname, lastname
FROM pr__user
WHERE id = :id');
$sql->execute(array('id' => $_SESSION['user_id']));
$rowCount = $sql->rowCount();
It al the time worked fine but I saw in the PHP manual:
If the last SQL statement executed by the associated PDOStatement was
a SELECT statement, some databases may return the number of rows
returned by that statement. However, this behaviour is not guaranteed
for all databases and should not be relied on for portable
applications.
http://php.net/manual/en/pdostatement.rowcount.php
It works fine with MySQL and MariaDB so I kept on using it. As I use it an application I wish portable, should I modify my code?
I never ask for the row count. Querying always returns an array of result rows (in some format), I can simply ask how many rows in the array -- such as with PHP's count(..) function.
What you're missing is that PDO is an interface to many different databases, not just MySQL. They make no guarantees that the function will return the same sort of values on completely different back-ends.
This is what "for portable applications" means: If you want your code to run universally on an arbitrary database you may need to avoid using that function. If that's not the case, you're not writing generic library code, you can depend on MySQL's particular behaviour.
Just be sure to test whatever you're doing to ensure that assumption is reasonable.
Rather, it's just pointless and superfluous. You are bloating your code for no reason.
From your example it is evident that you are going to use the data selected (otherwise there is no point in selecting email). So it means that you can use that data instead of row count all the way. Assuming the next operator would be fetch(), you can omit rowCount()
$sql = $dataBase->prepare('SELECT email, firstname, lastname
FROM pr__user
WHERE id = :id');
$sql->execute(array('id' => $_SESSION['user_id']));
$user = $sql->fetch();
if ($user) { ...
and from now on you can use $user in the every single condition where $rowCount has been used. Simply because you don't actually need a count here, but rather a boolean flag for which purpose an array would serve as good as an integer.
Even in a case when you don't need email but only to know whether a user exists, you can simply select just 1 scalar value and then fetch it - so your code remains uniform.
I use PDO transaction
try {
DB::$db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
DB::$db->beginTransaction();
$db->prepare( insert query );
$db->execute();
$last_insert_id = $db->lastInsertId();
...
...
Multiple concurrent requests are expected on this script.
Question: is it possible that lastInsertId() return incorrect value for the user, who actually inserted the row?
(by "incorrect value" i mean: id that is inserted by some other user).
You're safe. The ID you get will be the correct one.
PDO's lastInsertId (and mysql's last_insert_id to which your PDO delegates the call in this case) gives the last autogenerated ID on a per-connection basis.
From mysql's documentation:
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.
Concurrent connections will not compromise the integrity of the returned id. And as you mention in your comment, transactions have no bearing on this. Just don't forget to commit!
I'll also mention there is the unlikely possibility, if you run multiple statements on the same connection, and if your execute method throws an exception which isnt handled correctly, that lastInsertId could return the id of the last successful insert on that connection. But it can never return an ID from another user's query.
Maybe i have some stupid questions about mysql_insert_id...
I need to get the last inserted Id of a query using mysql_insert_id after mysql_query("INSERT INTO").
mysql_insert_id retrieves the generated Id by the previous query, but my question is...
One php file called test.php with the following code
mysql_query("INSERT INTO table (FirstName, LastName) VALUES ('" . $_POST['FirstName'] . "', '" . $_POST['LastName'] . "'); ")
$lastId = mysql_insert_id();
...continues some code using $lastId
What happens if the test.php file called from different places and different browsers at the SAME TIME ?
or what happens if other php file that containts INSERT INTO query called at the same time ?
What actually Id gets back the mysql_insert_id() ?
We have to do with a rare probability ?
I do not know if I become understandable...
The PHP manual states that the value of mysql_insert_id() returns only the most recently generated AUTO_INCREMENT value:
The value of the MySQL SQL function LAST_INSERT_ID() always contains the most recently generated AUTO_INCREMENT value, and is not reset between queries.
The MySQL manual states the it will return the last AUTO_INCREMENT based on a per-connection basis:
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.
Additionally, you should stop using mysql_ functions as they are being deprecated.
Each client will be sitting on a separate connection. mysql_insert_id() will get the id of the last insert query, based on the passed/current connection. So you don't have to worry about multiple scripts causing problems.
Also, from the code you provided above, your script is vulnerable to a SQL injection attack. Escape your user-input or even better, use PDO/MySQLi.
You get back the autoincrement ID from the last query on your connection. It will not cross connections, so there is no harm in two scripts running simultaneously.
Side note: if you have the option, use the mysqli or PDO libraries instead of the deprecated mysql libarary.
Im wondering if the way i use to retrieve the id of the last row inserted in a postgresql table is efficent..
It works, obviously, but referencing on the serial sequence currval value could be problematic when i have many users adding rows in the same table at the same time.
My actual way is:
$pgConnection = pg_connect('host=127.0.0.1 dbname=test user=myuser password=xxxxx')or die('cant connect');
$insert = pg_query("INSERT INTO customer (name) VALUES ('blabla')");
$last_id_query = pg_query("SELECT currval('customer_id_seq')");
$last_id_results = pg_fetch_assoc($last_id_query);
print_r($last_id_results);
pg_close($pgConnection);
Well, its just a test atm.
But anyway, i can see 3 issues with this way:
Referencing on the customer_id_seq, if two user do the same thing in the same time, could happen that them both get the same id from that way... or not?
I have to know the table's sequence name. Becose pg_get_serial_sequence dont works for me (im newbie on postgresql, probably is a configuration issue)
Any suggestion/better ways?
p.s: i can't use the PDO, becose seem lack a bit with the transaction savepoint; I wont use zend and, in the end, i'll prefer to use the php pg_* functions (maybe i'll build up my classes in the end)
EDIT:
#SpliFF(thet deleted his answer): this would works better?
$pgConnection = pg_connect('host=127.0.0.1 dbname=test user=myuser password=xxxxx')or die('cant connect');
pg_query("BEGIN");
$insert = pg_query("INSERT INTO customer (name) VALUES ('blabla')");
$last_id_query = pg_query("SELECT currval('customer_id_seq')");
$last_id_results = pg_fetch_assoc($last_id_query);
print_r($last_id_results);
//do somethings with the new customer id
pg_query("COMMIT");
pg_close($pgConnection);
If you use a newer version of PostgreSQL (> 8.1) you should use the RETURNING clause of INSERT (and UPDATE) command.
OTOH if you insist on using one of the sequence manipulation functions, please read the fine manual. A pointer: "Notice that because this is returning a session-local value, it gives a predictable answer whether or not other sessions have executed nextval since the current session did."
Insert and check curval(seq) inside one transaction. Before commiting transaction you'll see curval(seq) for your query and no matter who else inserted at the same time.
Don't remember the syntax exactly - read in manual (last used pgsql about 3 years ago), but in common it looks like this:
BEGIN TRANSACTION;
INSERT ...;
SELECT curval(seq);
COMMIT;
ex. minsert into log (desc,user_id) values ('drop her mind',6) returning id
I always hear that using "lastInsertId" (or mysql_insert_id() if you're not using PDO) is evil. In case of triggers it obviously is, because it could return something that's totally not the last ID that your INSERT created.
$DB->exec("INSERT INTO example (column1) VALUES ('test')");
// Usually returns your newly created ID.
// However when a TRIGGER inserts into another table with auto-increment:
// -> Returns newly created ID of trigger's INSERT
$id = $DB->lastInsertId();
What's the alternative?
IMHO it's only considered "evil" because hardly any other SQL database (if any) has it.
Personally I find it incredibly useful, and wish that I didn't have to resort to other more complicated methods on other systems.
One alternative is to use sequences instead, so you generate the ID yourself before you do the insert.
Unfortunately they are not supported in MySQL but libraries like Adodb can emulate them using another table. I think however, that the emulation itself will use lastInsertId() or equivalent... but at least you are less likely to have a trigger on a table which is purely used for a sequence
If you go the route of ADOdb (http://adodb.sourceforge.net/), then you can create the insert ID before hand and explicitly specific the ID when inserting. This can be implemented portably (ADOdb supports a ton of different databases...) and guarantees you're using the correct insert ID.
The PostgreSQL SERIAL data type is similar except that it's per-table/per-sequence, you specify the table/sequence you want the last insert ID for when you request it.
I guess it's not really state of the art either but I use write locks to make sure that I really get the last inserted ID.
It's not sophisticated and it's not efficient, but if the data you've inserted include unique fields, then a SELECT can obviously yield what you're after.
For example:
INSERT INTO example (column1) VALUES ('test');
SELECT id FROM example WHERE column1 = 'test';
You could try this:
$sql = "SELECT id FROM files ORDER BY id DESC LIMIT 1";
$PS = $DB -> prepare($sql);
$PS -> execute();
$result = $PS -> fetch();