MySQL: Get last inserted primary key without auto_increment - mysql

I removed my record ID while I'm using unique hashes as a primpary key. This primary key obviously cannot auto increment.
Now my question is how to retreive the last inserted primary key? MySQL returns 0 on LAST_INSERT_ID() while it's not an auto increment column.

The fact
There's no equivalent to LAST_INSERT_ID() returning a non integer value.
One can simply
The easy approach
Add an integer column which can either be auto incremented or non auto incremented.
To have it auto incremented correctly one has at least to implement an algorithm in MySQL itself or with a language of their choice to fill the existing records with the new IDs.
The more complex approach
https://stackoverflow.com/a/53481729/2323764 (#kellymandem)
Add a second table managing the ID and triggered by the origin table without IDs.
One cannot
I found this very promising Q/A.
Is there a way to get last inserted id of a NON - auto incremented column in MySQL?
It's mentioned there to use LAST_INSERT_ID() in the INSERT statement already.
But
INSERT INTO `table` ( `non_integer_column` ) VALUES ( LAST_INSERT_ID( 42 ) );
SELECT LAST_INSERT_ID( );
-> 42
INSERT INTO `table` ( `non_integer_column` ) VALUES ( LAST_INSERT_ID( 'a0b1c2d3e4f5' ) );
SELECT LAST_INSERT_ID( );
-> 0
Non integer values will be ignored.

I think your problem could best be solved by creating a new table and a trigger to keep track of the newly inserted hash values in the main table.
For example
CREATE TABLE test_table (
hash VARCHAR(30) NOT NULL PRIMARY KEY,
fullname VARCHAR(120) NOT NULL
);
CREATE TABLE hash_tracker(
hash VARCHAR(30) NOT NULL,
created_at DATETIME NOT NULL
);
DELIMITER $$
CREATE TRIGGER `test_trigger`
AFTER INSERT ON `test_table`
FOR EACH ROW
BEGIN
INSERT INTO hash_tracker VALUES (NEW.`hash`, NOW());
END$$
DELIMITER ;
Then after each insert on my test_table, i can run the following query
SELECT hash FROM hash_tracker ORDER BY created_at DESC LIMIT 1;
to get the most recently inserted hash.

Related

How to enforce insert with specific field?

Given the following table:
DROP TABLE IF EXISTS my_table;
CREATE TABLE IF NOT EXISTS my_table(
id INT NOT NULL,
timestamp TIMESTAMP(3) DEFAULT CURRENT_TIMESTAMP(3) NOT NULL,
data BLOB NULL,
PRIMARY KEY (id)
);
I can insert on it with:
INSERT INTO my_table (timestamp, data) VALUES
('2014-07-11 11:25:48.185', LOAD_FILE('sql/file.bin'));
In the above insert I was not enforced to insert the id field.
How may I create the table (my_table) so that it prevents inserts without id?
I would every insert to be made (providing the id) like, i.e.:
INSERT INTO my_table (id, timestamp, data) VALUES
(7, '2014-07-11 11:25:48.185', LOAD_FILE('sql/file.bin'));
I was thinking NOT NULL was there for it.
To prevent inserts with an empty value for ID (or not value passed), simply define the column as NOT NULL as you defined it.
I can't see how your example worked (i.e. inserting only into (timestamp, data)).
Now, the fact that there is another table with a trigger that inserts in this one does not have any effect on the ID column of this table. If you define it as AUTO_INCREMENT, whenever you insert a new row, the ID will automatically get a new value which will be fully independent from any data of the first table.
You can have as many tables as you wish with auto-incremented fields, each running a different sequence (and hence their numbering will be fully independent).
To summarize:
CREATE TABLE IF NOT EXISTS my_table(
id INT NOT NULL AUTO_INCREMENT ,
timestamp TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) ,
data BLOB NULL ,
PRIMARY KEY (id)
);

How to alter Primary Key to have distinct Auto_Increment based on column

I have table, called q_locations.
CREATE TABLE `q_locations` (
`id` int(11) NOT NULL,
`name` varchar(300) NOT NULL,
`standalone` bit(1) NOT NULL DEFAULT b'0',
UNIQUE KEY `id` (`id`),
KEY `standalone` (`standalone`)
)
I want 'id' to increment from 0, when standalone=0, and to increment from 1000, when standalone=1, is it possible to do that and how?
It is possible, but not automatically. The traditional method is to use a trigger to set the values.
However, I don't think you should pursue this route. An auto-incremented primary key should not have meaning associated with it. It is not an encoding of other information; it is just a number assigned to a row (typically in insert order although there may be gaps).
If you want to enumerate the values for standalone, then do so in a query rather than in the primary key column.
EDIT:
The trigger would look something like:
DELIMITER $$
CREATE TRIGGER trigger_insert_q_locations
BEFORE INSERT ON q_locations
FOR EACH ROW
BEGIN
SELECT coalesce(max(id) + 1, (case when standalone = 0 then 0 else 1000 end)) into NEW.id
FROM q_locations
WHERE standalone = NEW.standalone;
END$$
Note that this handles insert. If the value of standalone is updated on a record, the data will be out-of-whack.
Two way auto increment is not possible but you can achieve this scenario manually by using following logic
For first entry:
Before inserting values into table
Check the value of standalone
If it is 0 then insert 0 Else 1000
For next entries:
Before inserting values into table
Check the value of standalone
If it is 0 then Get max value of id column by using where condition standalone = 0
Else if it is 1 then Get max value of id column by using where condition standalone = 1
Then increment id by 1 and insert it into table

knowing next value from auto increment field mysql java

I want to know the next value of auto increment field
I wanted to test this :
select max(contactid) from contact
and I add 1
but I realized that it can give me an error
for exemple
if I insert one record and I delete it
so if I insert after the field will increase by two
how can I achieve that ?
thank you
There are multiple solutions to this problem:
1. (Preferable) Stop trying to predict auto-increment values
This is the more typical case, and basically is using auto-increment as designed. This assumes that you don't actually need the auto-increment value before you insert. What you can do is:
DROP TABLE IF EXISTS t;
CREATE TABLE t (id INT UNSIGNED NOT NULL auto_increment, x INT NOT NULL, PRIMARY KEY(id));
INSERT INTO t (x) VALUES (100);
SELECT LAST_INSERT_ID();
The call to SELECT LAST_INSERT_ID() will return the ID that was just generated for your INSERT.
2. Set up an ID generation table specifically to generate IDs
You can create a table with just an auto-increment column, like so:
DROP TABLE IF EXISTS id_generator;
CREATE TABLE id_generator (id INT UNSIGNED NOT NULL auto_increment, PRIMARY KEY(id));
You can then generate a new, unique ID with:
INSERT INTO id_generator (id) VALUES (NULL);
SELECT LAST_INSERT_ID();
And use that ID to insert into the table you're actually working with. As long as all generated IDs come from this ID generation table, there will be no conflicts. However there is a cost to generating these IDs, and auto-increment is not very efficient at it.
3. Use an external ID generation scheme
This is more or less similar to solution 2, but doesn't use MySQL at all for the ID generation. You can use something like a UUID/GUID scheme which generates a string, or you could use something like Snowflake to generate integer IDs.
You should use LAST_INSERT_ID like this:
SELECT LAST_INSERT_ID()
It will return the last value of AUTO_INCREMENT ID field.
More details here: http://goo.gl/RkmR5
This will give you the next id value that will be inserted:
SELECT LAST_INSERT_ID() + 1;

When doing a MySQL INSERT, if the insert fails because of a duplicate key, is there a way to return that key's id?

So I have a table like this:
create table `test` (
`testId` int(11) not null auto_increment,
`text` varchar(10) not null default '',
primary key(`testId`),
unique(`text`)
) engine=innodb;
My insert would be
insert into test (text) values ('a');
insert into test (text) values ('b');
insert into test (text) values ('a');
the 3rd insert will fail, but I want it to return the testId for the duplicate (for 'a').
Is this possible without writing a second query?
You can't do this in an INSERT query because the INSERT does not return any rows.
When an INSERT fails because of a duplicated key - normally the thing that built the query knows what data it sent, so it could use this.
You may be able to achieve what you want by using 12.2.5.3. INSERT ... ON DUPLICATE KEY UPDATE
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;
There are two ways of doing this. If you just want to replace the existing data, you can use
REPLACE INTO <table_name> VALUES <values>
That is the simplest thing. But if you just want to check whether the data exist, you can first, do this
SELECT COUNT(*) FROM <table> WHERE <field>='<field_value>
If exists, query returns more than 0.
Hope this helps.

Emulate auto-increment in MySQL/InnoDB

Assume I am going to emulate auto-increment in MySQL/InnoDB
Conditions
Using MySQL/InnoDB
The ID field don't have unique index, nor it is a PK
Is it possible to emulate only using program logics, without table level lock.
Thanks.
Use a sequence table and a trigger - something like this:
drop table if exists users_seq;
create table users_seq
(
next_seq_id int unsigned not null default 0
)engine = innodb;
drop table if exists users;
create table users
(
user_id int unsigned not null primary key,
username varchar(32) not null
)engine = innodb;
insert into users_seq values (0);
delimiter #
create trigger users_before_ins_trig before insert on users
for each row
begin
declare id int unsigned default 0;
select next_seq_id + 1 into id from users_seq;
set new.user_id = id;
update users_seq set next_seq_id = id;
end#
delimiter ;
insert into users (username) values ('f00'),('bar'),('bish'),('bash'),('bosh');
select * from users;
select * from users_seq;
insert into users (username) values ('newbie');
select * from users;
select * from users_seq;
CREATE TABLE sequence (id INTEGER); -- possibbly add a name;
INSERT INTO sequence VALUES (1); -- starting value
SET AUTOCOMMIT=0;
START TRANSACTION;
UPDATE sequence SET id = LAST_INSERT_ID(id+1);
INSERT INTO actualtable (non_autoincrementing_key) VALUES (LAST_INSERT_ID());
COMMIT;
SELECT LAST_INSERT_ID(); Is even a session-safe value to check which ID you got. Be sure your table support transactions, or that holes in a sequence are no problem.
Create another table with a single row and column that stores the next id value. Then create an insert trigger on the original table that increments the value in the second table, grabs it, and uses that for the ID column on the first table. You would need to be careful with the way you do the select and update to ensure they are atomic.
Essentially you are emulating an Oracle sequence in MySQL. It would cause a lock on single row in the sequence table though, so that may make it inappropriate for what you are doing.
ETA:
Another similar but maybe better performing option would be to create a second "sequence" table that just has a single auto-increment PK column and no other data. Have your insert trigger insert a row into that table and use the generated ID from there to populate the ID in the original table. Then either have the trigger or another process periodically delete all the rows from the sequence table to clean it up.
sequence table need to have id as the autoincrement PK