I'm seeing a weird behavior when I INSERT some data into a table and then run a SELECT query on the same table. This table has an auto-increment primary key (uid), and this problem occurs when I try to then select results where 'uid IS NULL'.
I've golfed this down to the following SQL commands:
DROP TABLE IF EXISTS test_users;
CREATE TABLE test_users (uid INTEGER PRIMARY KEY AUTO_INCREMENT, name varchar(20) NOT NULL);
INSERT INTO test_users(name) values('foo');
SELECT uid FROM test_users WHERE uid IS NULL;
SELECT uid FROM test_users WHERE uid IS NULL; -- no output from this query
I'd expect SELECT uid FROM test_users WHERE uid IS NULL to never return anything, but it does, sometimes. Here's what I've found:
Version of MySQL/MariaDB seems to matter. The machine having this problem is running MySQL 5.1.73 (CentOS 6.5, both 32-bit and 64-bit). My other machine running 5.5.37-MariaDB (Fedora 19, 64-bit). Both running default configs, aside from being configured to use MyISAM tables.
Only the first SELECT query after the INSERT is affected.
If I specify a value for uid rather than let it auto-increment, then it's fine.
If I disconnect and reconnect between the INSERT and SELECT, then I get the expected no results. This is easiest to see in something like Perl where I manage the connection object. I have a test script demonstrating this at https://gist.github.com/avuserow/1c20cc03c007eda43c82
This behavior is by design.
It's evidently equivalent to SELECT * FROM t1 WHERE id = LAST_INSERT_ID(); which would also work only from the connection where you just did the insert, exactly as you described.
It's apparently a workaround that you can use in some environments that make it difficult to fetch the last inserted (by your connection) row's auto-increment value in a more conventional way.
To be precise, it's actually the auto_increment value assigned to the first row inserted by your connection's last insert statement. That's the same thing when you only inserted one row, but it's not the same thing when you insert multiple rows with a single insert statement.
http://dev.mysql.com/doc/connector-odbc/en/connector-odbc-usagenotes-functionality-last-insert-id.html
Related
I'm working on an application that is deployed to Heroku and using the JawsDB add on to replicate my local MySQL database. I recently updated a table in the local MySQL database (Plans) to include two new rows, 'plan_dates' and 'plan_times', but the hosted JawsDB did not automatically sync with the changes made on my local connection.
I opened up the JawsDB database on MySQL using the connection string provided by Heroku to try and add in the rows manually. These are the rows in the Plan table that I am trying to edit:
database rows
I've searched online, but I cannot find any resources on how to add new (empty) rows to a table on MySQL -- all I can find online are references to 'INSERT INTO', which inserts specific values into rows, but that's not exactly what I'm looking to do. I did try running INSERT INTO as follows:
INSERT INTO Plans (plan_dates, plan_times)
VALUES(NULL, NULL);
but then I ran into the following error:
Unknown column 'plan_dates' in 'field list'
Am I missing a very simple command or is there any other way to add new empty rows to a table that has already been created in MySQL?
First of all, The columns are not exist in your table and you must add it to the table using ALTER Table query.
Syntax would be
ALTER TABLE table
ADD [COLUMN] column_name column_definition [FIRST|AFTER existing_column];
So your query should be
ALTER TABLE Plans
ADD COLUMN plan_dates Date AFTER UserId,
ADD COLUMN plan_times TIME AFTER UserId;
And you can use INSERT statement to add new empty rows.
INSERT INTO Plans (id, start_date, end_date, maxMins, totalMins, UserId, plan_dates, plan_times) VALUES(1, NULL, NULL, NULL, NULL, NULL, NULL, NULL);
I have the following sql table:
id|email|fbid
When I perform the query
INSERT INTO users(email,fbid) VALUES('randomvalue','otherrandomvalue')
I want to get the id of the inserted row. To do so, I've tried to edit the query like this:
INSERT INTO users(email,fbid) VALUES('randomvalue','otherrandomvalue') OUTPUT Inserted.id
But I'm getting:
1064 - You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use
near 'OUTPUT Inserted.id' at line 1
What could be the problem?
Unfortunately (as far as I can tell) mysql does not support output as sql-server does.
You do have an option for what you're trying to accomplish in a single row insert (assuming auto_increment primary key):
SELECT LAST_INSERT_ID();
This unfortunately would not work in the case of a batch insert - though in your case you are not (at least not in your example), so this should be fine.
I'm going to use the process i describe below to handle the same situation with a private at home (non-enterprise) application that i wrote for personal use. I know this question is a year old right now but there doesn't seem to be an adequate answer for batch processing. I can't find an adequate answer. MySQL doesn't seem to have the facilities built into it to handle this type of thing.
I had concerns about the reliability of this solution, when put into a production environment where multiple different users/jobs could access the same procedure at the same time to do the insert. I believe I have resolved these concerns by adding the connection id to the #by variable assignment. Doing this makes it so that the by has a: the connection id for the session and b: the name of the program/job/procedure doing the insert. Combined with the date AND time of the insert, I believe these three values provide a very secure key to retrieve the correct set of inserted rows. If absolute certainty is required for this, you could possibly add a third column of a GUID type (or varchar) generate a GUID variable to insert into that, then use the GUID variable along with #by and #now as your key. I feel it's unnecessary for my purpose because the process I'm going to use it in is an event (job) script that runs on the server rather than in PHP. So I am not going to exemplify it unless someone asks for that.
WARNING
If you are doing this in PHP, consider using a GUID column in your process rather than the CreatedBy. It's important that you do that in PHP because your connection can be lost in between inserting the records and trying to retrive the IDS and your CreatedBy with the connection ID will be rendered useless. If you have a GUID that you create in PHP, however, you can loop until your connection succeeds or recover using the GUID that you saved off somewhere in a file. The need for this level of connection security is not necessary for my purposes so I will not be doing this.
The key to this solution is that CreatedBy is the connection id combined with the name of the job or procedure that is doing the insert and CreatedDate is a CURRENT_TIMESTAMP that is held inside a variable that is used through the below code. Let's say you have a table named "TestTable". It has the following structure:
Test "Insert Into" table
CREATE TABLE TestTable (
TestTableID INT NOT NULL AUTO_INCREMENT
, Name VARCHAR(100) NOT NULL
, CreatedBy VARCHAR(50) NOT NULL
, CreatedDate DATETIME NOT NULL
, PRIMARY KEY (TestTableID)
);
Temp table to store inserted ids
This temporary table will hold the primary key ids of the rows inserted into TestTable. It has a simple structure of just one field that is both the primary key of the temp table and the primary key of the inserted table (TestTable)
DROP TEMPORARY TABLE IF EXISTS tTestTablesInserted;
CREATE TEMPORARY TABLE tTestTablesInserted(
TestTableID INT NOT NULL
, PRIMARY KEY (TestTableID)
);
Variables
This is important. You need to store the CreatedBy and CreatedDate in a variable. CreatedBy is stored for consistency/coding practices, CreatedDate is very important because you are going to use this as a key to retrieve the inserted rows.
An example of what #by will look like: CONID(576) BuildTestTableData
Note that it's important to encapsulate the connection id with something that indicates what it is since it's being used as a "composite" with other information in one field
An example of what #now will look like: '2016-03-11 09:51:10'
Note that it's important to encapsulate #by with a LEFT(50) to avoid tripping a truncation error upon insert into the CreatedBy VARCHAR(50) column. I know this happens in sql server, not so sure about mysql. If mysql does not throw an exception when truncating data, a silent error could persist where you insert a truncated value into the field and then matches for the retrieval fail because you're trying to match a non-truncated version of the string to a truncated version of the string. If mysql doesn't truncate upon insert (i.e. it does not enforce type value restrictions) then this is not a real concern. I do it out of standard practice from my sql server experience.
SET #by = LEFT(CONCAT('CONID(', CONNECTION_ID(), ') BuildTestTableData'), 50);
SET #now = CURRENT_TIMESTAMP;
Insert into TestTable
Do your insert into test table, specifying a CreatedBy and CreatedDate of #by and #now
INSERT INTO TestTable (
Name
, CreatedBy
, CreatedDate
)
SELECT Name
, #by
, #now
FROM SomeDataSource
WHERE BusinessRulesMatch = 1
;
Retrieve inserted ids
Now, use #by and #now to retrieve the ids of the inserted rows in test table
INSERT INTO tTestTablesInserted (TestTableID)
SELECT TestTableID
FROM TestTable
WHERE CreatedBy = #by
AND CreatedDate = #now
;
Do whatever with retreived information
/*DO SOME STUFF HERE*/
SELECT *
FROM tTestTablesInserted tti
JOIN TestTable tt
ON tt.TestTableID = tti.TestTableID
;
if You are using php then it is better to use following code :
if ($conn->query($sql) === TRUE) {
$last_id = $conn->insert_id;
echo "New record created successfully. Last inserted ID is: " . $last_id;
} else {
echo "Error: " . $sql . "<br>" . $conn->error;
}
where $conn is connection variable.
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?
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;
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;
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);
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.
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.
I am seeing a curious name dependency in the following MySQL table definition. When I code the table as shown, it seems to break MySQL. When I perform "select * from dictionary_pair_join" from MySQLQueryBrowser, the status bar says "No resultset returned" -- no column names and no errors. When I insert a row into the table, the status bar says "1 row affected by the last command, no resultset returned", and subsequent selects give the same "No resultset returned" response.
When I enclose the tablename in backticks in the "select" statement, all works fine. Surely there are no mysql entities named "dictionary_pair_join"!
Here is the table definition:
DROP TABLE IF EXISTS dictionary_pair_join;
CREATE TABLE dictionary_pair_join (
version_id int(11) UNSIGNED NOT NULL default '0',
pair_id int(11) UNSIGNED default NULL,
KEY (version_id),
KEY (pair_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
Here is the broken select statement:
select * from dictionary_pair_join;
Here is its working counterpart:
select * from `dictionary_pair_join`;
Why are backticks required in the select statement?
Update: This also fails in the Python mysqldb interface, which is why I started looking at it. I can put the backticks into my Python "select" generators, but I was hoping this was some stupid and easily-changed nit. I suppose I can also find a different name.
I've uprated the comments from Quassnoi and Software Guy, together they've persuaded me that it's just a bug in mysql/mysqldb/mysqlquerybrowser.
I changed the table name (to "dictionary_pair_cons") and the problem went away, in both the query browser and mysqldb.
Pretty straight forward problem. I simply want to return the affected row created by a SQL INSERT query so I can get data from it.
There's a way I can do it already, I suppose, but I'm hoping the Mysql or Mysql2 gem provides some mechanism for me not having to make the second SELECT query.
The solution I'm leaning towards right now is something akin to:
"INSERT INTO table (col1) VALUES ('value');"
and then:
"SELECT cid FROM table ORDER BY cid DESC LIMIT 1;"
Since cid is the auto-increment index of the table (it's InnoDB fyi), it will always be the largest cid value in the table until you do another INSERT.
Is there any mechanism in Mysql or Mysql2 to avoid having to make that second SELECT query?
MySQL2 has a last_id method. The documentation for that method is worthless but the implementation looks like this:
static VALUE rb_mysql_client_last_id(VALUE self) {
GET_CLIENT(self);
REQUIRE_OPEN_DB(wrapper);
return ULL2NUM(mysql_insert_id(wrapper->client));
}
And the MySQL mysql_insert_id function does this:
Returns the value generated for an AUTO_INCREMENT column by the previous INSERT or UPDATE statement.
So you can do your INSERT and then get the last ID by calling the last_id method.
And BTW, your current approach:
SELECT cid FROM table ORDER BY cid DESC LIMIT 1;
is not safe if you're in a multiprocess environment, consider this:
You INSERT a row.
Another process INSERTs a row.
You SELECT cid FROM table ORDER BY cid DESC LIMIT 1 and get the ID from (2).
You could SELECT last_insert_id() though, last_insert_id() is session-specific so you don't have to worry about other processes when using it.