I have created a trigger to help me to make the primary key when I insert a row. I want to get the primary key I just insert. But because the primary key is not auto_increment I cannot use
SELECT LAST_INSERT_ID();
So how can I get the primary key?
Here is the trigger
CREATE TRIGGER before_insert_user_info
BEFORE INSERT ON USER_INFO
FOR EACH ROW
SET new.uID = CONCAT('U', ((SELECT MAX(CAST(SUBSTRING(uID, 2, length(uID)) AS UNSIGNED)) FROM USER_INFO)+1));
And here is the insert
INSERT INTO USER_INFO(name) VALUES ('Peter');
It is kind of unusual to rely on a trigger to generate a primary key. There is no way to retreive data back from a trigger (e.g. it has no return value). But you can reuse your tigger's logic to retreive the generated value:
INSERT INTO user_info VALUE (#newUID, ...);
SELECT MAX(CAST(SUBSTRING(#newUID, 2, LENGTH(#newUID)) AS UNSIGNED)) AS last_insert_id
FROM user_info; -- this is the generated value
Wrap these two statements in a transaction to make sure a new user is not inserted in between.
As an alternative, I would create another INT AUTO_INCREMENT column, so that you can retreive your new row after insertion with LAST_INSERT_ID();
And while we are at it, I would make this new field the (surrogate) primary key. The same trigger could still generate the "public" user ID, but then we would be back to a more usual architecture.
Last food for thoughts: do you really need to store your user ID's with the U prefix? Perhaps you could just store a plain INT value, and preppend the U on selection.
Related
In my database, all Primary Keys are surogate. There are some Unique keys, but not always, so the most safe way to access specific row is Primary Key. Many of them use AUTO_INCREMENT. Do I have to lock access to database when inserting into two related table? For example.
create table foo
(
foo_id numeric not null auto_increment,
sth varchar,
PRIMARY KEY(foo_id)
)
create table bar
(
bar_id numeric not null auto_increment,
foo_id numeric not null,
PRIMARY KEY(bar_id),
FOREIGN KEY (foo_id) REFERENCES foo(foo_id)
)
First I insert sth to foo, and then I need foo_id value to insert related stuff into bar. This value I can get from INFORMATION_SCHEMA.TABLES. But what if somebody will add new row into foo before I get the auto_increment value? If all these steps are in stored procedure is there implicitly started transactions which locks all needed resources for one procedure call? Or maybe I have to use explicitly START TRANSACTION. What if I dont use procedure - just sequence of inserts and selects?
Instead of looking in INFORMATION_SCHEMA.TABLE, I would suggest that you use LAST_INSERT_ID.
From the MySQL 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 imply that an insert done at the same time on a different connection will not change the value that is returned on your current connection.
Run queries in that sequence:
INSERT INTO foo (sth) VALUES ('TEST');
Than:
INSERT INTO bar (foo_id) VALUES (SELECT LAST_INSERT_ID());
I've been reading up on how to use MySQL insert on duplicate key to see if it will allow me to avoid Selecting a row, checking if it exists, and then either inserting or updating. As I've read the documentation however, there is one area that confuses me. This is what the documentation says:
If you specify ON DUPLICATE KEY UPDATE, and a row is inserted that would cause a duplicate value in a UNIQUE index or PRIMARY KEY, an UPDATE of the old row is performed
The thing is, I don't want to know if this will work for my problem, because the 'condition' I have for not inserting a new one is the existence of a row that has two columns equal to a certain value, not necessarily that the primary key is the same. Right now the syntax I'm imagining is this, but I don't know if it will always insert instead of replace:
INSERT INTO attendance (event_id, user_id, status) VALUES(some_event_number, some_user_id, some_status) ON DUPLICATE KEY UPDATE status=1
The thing is, event_id and user_id aren't primary keys, but if a row in the table 'attendance' already has those columns with those values, I just want to update it. Otherwise I would like to insert it. Is this even possible with ON DUPLICATE? If not, what other method might I use?
The quote includes "a duplicate value in a UNIQUE index". So, your values do not need to be the primary key:
create unique index attendance_eventid_userid on attendance(event_id, user_id);
Presumably, you want to update the existing record because you don't want duplicates. If you want duplicates sometimes, but not for this particular insert, then you will need another method.
If I were you, I would make a primary key out of event_id and user_id. That will make this extremely easy with ON DUPLICATE.
SQLFiddle
create table attendance (
event_id int,
user_id int,
status varchar(100),
primary key(event_id, user_id)
);
Then with ease:
insert into attendance (event_id, user_id, status) values(some_event_number, some_user_id, some_status)
on duplicate key
update status = values(status);
Maybe you can try to write a trigger that checks if the pair (event_id, user_id) exists in the table before inserting, and if it exists just update it.
To the broader question of "Will INSERT ... ON DUPLICATE respect a UK even if the PK changes", the answer is yes: SQLFiddle
In this SQLFiddle I insert a new record, with a new PK id, but its values would violate the UK. It performs the ON DUPLICATE and the original PK id is preserved, but the non-UK ON DUPLICATE KEY UPDATE value changes.
Let's say this is my table:
CREATE TABLE tab (
id INT AUTO_INCREMENT NOT NULL,
val VARCHAR(9),
KEY(id),
PRIMARY KEY (xx)
);
Would it possible to insert multiple rows at the same time in a way that they would all get the same auto-increment value?
The following works, but increments each new row, regardless of the fact that we are doing a single query.
INSERT INTO tab (id,val) VALUES (LAST_INSERT_ID(),'a'), (LAST_INSERT_ID(),'b');
How could I make sure they all receive the same auto-incremented ID in a single query?
You will need to keep the first AI value in a variable and pass it in the INSERT query for different pairs
im trying to create a function
CREATE FUNCTION `func`(param1 INT, param2 INT, param3 TEXT) RETURNS int(11)
BEGIN
INSERT INTO `table1` (`column1`, `column2`, `column3` )
VALUES (param1, param2, param3)
ON DUPLICATE KEY
UPDATE `time_stamp` = UNIX_TIMESTAMP();
RETURN last_insert_id();
END
this would insert into a table a row if it doesn't exist but otherwise update it.
Notice that i returned last_insert_id() which would be correct if the function would insert otherwise would be unpredictable if it updates.
I know the alternative to solving this is using separate SELECTS and identify if it exists; if it exists retrieve the id and update using that id; otherwise just do a plain INSERT.
Now my question: Is there any alternative to doing 2 sql statements as opposed to what i'm doing now?
EDIT 1
Addendum:
there is an auto incremented index.
All of the values to be inserted are unique
I'd rather not alter the index since it is being referred in another table..
If a table contains an AUTO_INCREMENT column and INSERT ... UPDATE inserts a row, the LAST_INSERT_ID() function returns the AUTO_INCREMENT value. If the statement updates a row instead, LAST_INSERT_ID() is not meaningful. However, you can work around this by using LAST_INSERT_ID(expr). Suppose that id is the AUTO_INCREMENT column. To make LAST_INSERT_ID() meaningful for updates, insert rows as follows:
INSERT INTO table (a, b, c) VALUES (1, 2, 3)
ON DUPLICATE KEY UPDATE id = LAST_INSERT_ID(id), c = 3;
Found it on this link. I've never tried it though, but it might help you.
EDIT 1
You might want to check out REPLACE:
REPLACE INTO table1 (column1, column2, column3) VALUES (param1, param2, param3);
This should work for tables with correct PRIMARY KEY/UNIQUE INDEX.
In the end, you'll just have to stick with:
IF (VALUES EXISTS ON TABLE ...)
UPDATE ...
SELECT Id;
ELSE
INSERT ...
RETURN last_insert_id();
END IF
Just in case anyone shows up here from Google, I ran into a problem where ON DUPLICATE KEY UPDATE kept triggering the same wrong value.
When inserting a user with only a first name and last name, it didn't AUTO_INCREMENT the primary key. The reason is we have a users table with a unique constraint on the username, but it has a default value of ''. So when you insert a user without a username, it triggers it to update the duplicate value of that username, and that random account kept getting returned as the correct one.
The solution is to make sure that only NULL is the default value for a unique key in a table that also has a separate auto-increment primary key, or that you do generate a unique value for the unique constraint.
I have a table with items in it (id, name, etc) and I want some kind of database scheme to be able to have multiple copies of the item while still being able to increment the ids. There will be a field called startdate or date_from_which_this_entry_should_be_used.
I've thought of two ways of implementing this:
Have a table with only ids (primary key, auto-increment) and do joins to a table that has all the item information.
Advantages:
easy to understand
hard for someone that comes after me to get confused
Disadvantages:
requires more debugging and coding since this system is already in use
seems weird to have a table with a single field
Have a single table using a sub-select to get the MAX value (+1) to apply to new items.
Advantages:
single table
only minor code adjustments (but not all that different, maybe)
Disadvantages:
prone to errors (manual increment, can't allow deletions or the MAX value might be off)
Thanks in advance!
You should create a table called item_ids or something to generate id values. It's okay that this has only a single column.
CREATE TABLE item_ids (
item_id INT AUTO_INCREMENT PRIMARY KEY
) ENGINE=InnoDB;
You don't even need to commit any data to it. You just use it to generate id values:
START TRANSACTION;
INSERT INTO item_ids DEFAULT VALUES;
SET #id = LAST_INSERT_ID();
ROLLBACK;
So now you have a concurrency-safe method to create new id's.
Then you make a compound primary key for your items table. You must use MyISAM for this.
CREATE TABLE items (
item_id INT,
seq_id INT AUTO_INCREMENT,
name VARCHAR(20),
etc VARCHAR(20),
PRIMARY KEY (item_id, seq_id)
) ENGINE=MyISAM;
MyISAM supports an auto-increment column in a compound primary key, which will start over at value 1 for each new item_id.* It also uses MAX(item_id)+1 so if you delete the last one, its value will be reallocated. This is unlike other use of AUTO_INCREMENT where a deleted value is not re-used.
Whether you insert a new item, or whether you insert a new copy of an existing item, you use a similar INSERT:
INSERT INTO items (item_id, name, etc) VALUES (#id, 'Stephane', 'etc');
The #id parameter is either a value of an existing item, or else the auto-generated value you got from the item_ids table.
* InnoDB supports auto-increment only as the first column of a primary or unique key, and it does not start over the count for each distinct value of the other column.