Prevent auto increment on duplicated entry? - mysql

I have Table called url_info and the structure of the table is:
url_info:
url_id ( auto_increment, primary key )
url ( unique,varchar(500) )
When I insert into table like this:
INSERT INTO url_info(url) VALUES('Tom');
INSERT INTO url_info(url) VALUES('Jerry');
The output is:
1 Tom
2 Jerry
When I insert like this
INSERT INTO url_info(url) VALUES('Tom');
INSERT INTO url_info(url) VALUES('Tom');
INSERT INTO url_info(url) VALUES('Jerry');
The output is
1 Tom
3 Jerry
The auto-increment id is incremented when I try to insert to duplicate entry. I have also tried Insert Ignore
How to prevent it from incrementing when I try to insert a duplicate entry?

It's probably worth creating a stored procedure to insert what you want into the table. But, in the stored procedure check what items you have already in the table. If these match what you're trying to insert, then the query should not even attempt the insert.
Ie. The procedure needs to contain something like this:
IF NOT EXISTS(SELECT TOP 1 url_id FROM url_info WHERE url = 'Tom')
INSERT INTO url_info(url) VALUES('Tom')
So, in your stored procedure, it would look like this (assuming the arguments/variables have been declared)
IF NOT EXISTS(SELECT TOP 1 url_id FROM url_info WHERE url = #newUrl)
INSERT INTO url_info(url) VALUES(#newUrl)

This is expected behaviour in InnoDB. The reason is that they want to let go of the auto_increment lock as fast as possible to improve concurrency. Unfortunately this means they increment the AUTO_INCREMENT value before resolving any constraints, such as UNIQUE.
You can read more about the idea in the manual on AUTO_INCREMENT Handling in InnoDB, but the manual is also unfortunately buggy and doesn't tell why your simple insert will give non-consecutive values.
If this is a real problem for you and you really need consecutive numbers, consider setting the innodb_autoinc_lock_mode option to 0 in your server, but this is not recommended as it will have severe effects on your database (you cannot do any inserts concurrently).

Auto_increment is performed updated by the engine. This is done before hand of checking a value is unique or not. And we can't roll back the operation to get back to former value of auto_increment.
Hence NO to start from where you last read on auto_increment.
And it is not an issue in loosing some intermediate values on auto_increment field.
The MAX value you can store into a SIGNED INT field is 2^31-1 equal to 2,147,483,647. If you read it loud, it sounds 2 billion+.
And I don't think it is small and won't suite your requirement.

CREATE TABLE `url_info` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`url` varchar(255) COLLATE utf8_unicode_ci NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci AUTO_INCREMENT=4 ;
When I execute:
INSERT INTO url_info(url) VALUES('Tom');
INSERT INTO url_info(url) VALUES('Tom');
INSERT INTO url_info(url) VALUES('Jerry');
I get:
Make sure you ID column is UNIQUE too.
As the manual says:
A UNIQUE index creates a constraint such that all values in the index
must be distinct. An error occurs if you try to add a new row with a
key value that matches an existing row. This constraint does not apply
to NULL values except for the BDB storage engine. For other engines, a
UNIQUE index permits multiple NULL values for columns that can contain
NULL. If you specify a prefix value for a column in a UNIQUE index,
the column values must be unique within the prefix.

Related

Manually updating primary key in a row causes subsequent insert to fail / Duplicate entry for key

I have a table with schema like this:
CREATE TABLE `things` (
`thing_id` INT(11) NOT NULL AUTO_INCREMENT,
`name` VARCHAR(32) NOT NULL DEFAULT '',
PRIMARY KEY (`thing_id`)
) ENGINE=INNODB DEFAULT CHARSET=utf8mb4;
And some sample data:
INSERT INTO things VALUES (NULL, 'thing 1'), (NULL, 'thing 2');
In my application, sometimes I want to bump the primary key of a row to be the highest in the table. For example, I want the thing_id of thing 1 changed to 3 (the table's current autoincrement value). Previously the table was MyISAM and this was accomplished as follows:
UPDATE things t
JOIN (SELECT MAX(thing_id) + 1 AS max_id FROM things) v
SET t.thing_id = v.max_id
WHERE thing_id = 1;
That part still works. However, now with InnoDB, the next insert fails since doing that update leaves the table's autoincrement value still pointing to the same thing. So now if I do an insert now like this:
INSERT INTO things VALUES (NULL, 'thing 3');
I will get an error like:
Duplicate entry '3' for key 'PRIMARY'
This was not an issue with MyISAM. How can I accomplish the same thing using InnoDB without running into this error? My guess is that there's a better way to change the thing_id which will keep the table's autoincrement value intact, but that's where I'm stumped.
And lastly, here's a dbfiddle:
https://www.db-fiddle.com/f/enJPVkwNN6hocjquw38BHD/0
Reference: Innodb Auto Increment Initialization
If you specify an AUTO_INCREMENT column for an InnoDB table, the table handle in the InnoDB data dictionary contains a special counter called the auto-increment counter that is used in assigning new values for the column. This counter is stored only in main memory, not on disk.
Update:
You can reset this counter using below query, works for InnoDB storage engine in MySQL 5.7
ALTER TABLE things AUTO_INCREMENT = 1;
Executing this is resetting auto-increment counter to Max + 1 value.

Bi-directional unique key constraint for combination of two columns

I have the below table columns in MySQL.
id
user_primary_email
user_secondary_email
I want to make the combination of columns user_primary_email and user_secondary_email unique which I can achieve by using UNIQUE KEY unique_key_name (user_primary_email, user_secondary_email)
The above addition of unique key constraint will help me achieve the below scenario or rather just by adding a unique key to the individual column itself.
user_primary_email = 'xyz#gmail.com' AND user_secondary_email = 'pqr#gmail.com'
user_primary_email = 'xyz#gmail.com' AND user_secondary_email = 'pqr#gmail.com' //This will not be allowed to enter due to unique key constraint
Now the problem which I am facing is the same combination should not be allowed to add in a reverse way as mentioned below.
user_primary_email = 'pqr#gmail.com' AND user_secondary_email = 'xyz#gmail.com' //This should not be allowed to add since already same email id combination added once
id | user_primary_email | user_secondary_email
-------------------------------------------------------
1 | xyz#gmail.com | pqr#gmail.com
-------------------------------------------------------
2 | pqr#gmail.com | xyz#gmail.com
-------------------------------------------------------
In the above case during insert of row id 2 it should throw error as both the email id combination is already used in row id 1.
Any help would be great.
In any MariaDB:
CREATE TABLE `t` (
`id` int(11) NOT NULL,
`user_primary_email` varchar(64) DEFAULT NULL,
`user_secondary_email` varchar(64) DEFAULT NULL,
`mycheck` varchar(128) AS (IF(user_primary_email<user_secondary_email,CONCAT(user_primary_email,user_secondary_email),CONCAT(user_secondary_email,user_primary_email))) PERSISTENT,
PRIMARY KEY (`id`),
UNIQUE KEY `mycheck` (`mycheck`)
);
MariaDB [test]> insert into t values (1,'a','b',null);
Query OK, 1 row affected (0.03 sec)
MariaDB [test]> insert into t values (2,'b','a',null);
ERROR 1062 (23000): Duplicate entry 'ab' for key 'mycheck'
There is no direct support for that, but you can use a workaround to create your bidirectional key: You need a unique key on an ordered version of your two columns.
Fortunately, you can very easily do that. MySQL 5.7.6+ supports generated columns and unique indexes for them, which you can use to order your two values and to enforce uniqueness.
create table testBiDirKey (
a varchar(100),
b varchar(100),
a_ordered varchar(100) as (least(a, b)) STORED,
b_ordered varchar(100) as (greatest(a, b)) STORED,
unique key unqBi_test_ab (a_ordered, b_ordered)
);
insert into testBiDirKey(a,b) values('a', 'b');
insert into testBiDirKey(a,b) values('b', 'a');
Error Code: 1062. Duplicate entry 'a-b' for key 'unqBi_test_ab'
This will treat null exactly as your current normal unique key, so
insert into testBiDirKey(a,b) values('a', null);
insert into testBiDirKey(a,b) values('a', null);
insert into testBiDirKey(a,b) values(null, 'a');
are all allowed. You can add coalesce(x,'') to only allow one empty value (either null OR '') if you want. If you verify your values before you add them (e.g. if they don't contain a ,), you can combine the two columns to just one, concatenated with an , - although with little benefit apart from just having 1 additional column.
For 5.7.8+, you don't need the STORED keyword anymore to be able to use these columns in an index. That keyword effects if the values are stored (using disk space) or calculated when required (default).
Before MySQL 5.7.6, you can use a trigger (on update and insert) to update the two columns with the these values, the same logic applies, it's just a little more code.

MySQL: Generate Autokey but use same for multiple rows

I have a mysql table that stores a mapping from an ID to a set of values:
CREATE TABLE `mapping` (
`ID` bigint(20) unsigned NOT NULL,
`Value` int(11) NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
This table is a list of values and the ID of a row selects the set, this value belongs to.
So the column ID is unique per set, but not unique per row.
I insert data into the table using the following statement:
INSERT INTO `mapping`
SELECT 5, `value` FROM `set1`;
In this example I calculated and set the ID manually to 5.
It would be great if mysql could set this ID automatically. I know the autokey feature, but using it will not work, because all rows inserted with the same insert statement should have the same ID.
So each insert statement should generate a new ID and then use it for all inserted rows.
Is there a way to accomplish this?
I am not convinced to it (I'm not sure whether locking table is good idea, I think it's not), but this might help:
lock tables `mapping` as m write, m as m1 read;
insert into m
select (select max(id) + 1 from m1), `value` from `set1`;
ulock tables;
One option is to have an additional table with an autogenerated key on single rows. Insert (with or without an necessary or appropriate other data) into that table, thus generating the new ID, and then use the generated key to insert into the mapping table.
This moves you to a world where the non-unique id is a foreign key reference to a truly unique key. Much more in keeping with typical relational database thinking.

MySQL Auto-Inc Bug?

In my MySQL table I've created an ID column which I'm hoping to auto-increment in order for it to be the primary key.
I've created my table:
CREATE TABLE `test` (
`id` INT( 11 ) NOT NULL AUTO_INCREMENT PRIMARY KEY ,
`name` VARCHAR( 50 ) NOT NULL ,
`date_modified` DATETIME NOT NULL ,
UNIQUE (
`name`
)
) TYPE = INNODB;
then Inserted my records:
INSERT INTO `test` ( `id` , `name` , `date_modified` )
VALUES (
NULL , 'TIM', '2011-11-16 12:36:30'
), (
NULL , 'FRED', '2011-11-16 12:36:30'
);
I'm expecting that my ID's for the above are 1 and 2 (respectively). And so far this is true.
However when I do something like this:
insert into test (name) values ('FRED')
on duplicate key update date_modified=now();
then insert a new record, I'm expecting it to be 3, however now I'm shown an ID of 4; skipping the place spot for 3.
Normally this wouldn't be an issue but I'm using millions of records which have thousands of updates every day.. and I don't really want to even have to think about running out of ID's simply because I'm skipping a ton of numbers..
Anyclue to why this is happening?
MySQL version: 5.1.44
Thank you
My guess is that the INSERT itself kicks off the code that generates the next ID number. When the duplicate key is detected, and ON DUPLICATE KEY UPDATE is executed, the ID number is abandoned. (No SQL dbms guarantees that automatic sequences will be without gaps, AFAIK.)
MySQL docs say
In general, you should try to avoid using an ON DUPLICATE KEY UPDATE
clause on tables with multiple unique indexes.
That page also says
If a table contains an AUTO_INCREMENT column and INSERT ... ON
DUPLICATE KEY UPDATE inserts or updates a row, the LAST_INSERT_ID()
function returns the AUTO_INCREMENT value.
which stops far short of describing the internal behavior I guessed at above.
Can't test here; will try later.
Is it possible to change your key to unsigned bigint - 18,446,744,073,709,551,615 is a lot of records - thus delaying the running out of ID's
Found this in mysql manual http://dev.mysql.com/doc/refman/5.1/en/example-auto-increment.html
Use a large enough integer data type for the AUTO_INCREMENT column to hold the
maximum sequence value you will need. When the column reaches the upper limit of
the data type, the next attempt to generate a sequence number fails. For example,
if you use TINYINT, the maximum permissible sequence number is 127.
For TINYINT UNSIGNED, the maximum is 255.
More reading here http://dev.mysql.com/doc/refman/5.6/en/information-functions.html#function_last-insert-id it could be inferred that the insert to a transactional table is a rollback so the manual says "LAST_INSERT_ID() is not restored to that before the transaction"
What about for a possible solution to use a table to generate the ID's and then insert into your main table as the PK using LAST_INSERT_ID();
From the manual:
Create a table to hold the sequence counter and initialize it:
mysql> CREATE TABLE sequence (id INT NOT NULL);
mysql> INSERT INTO sequence VALUES (0);
Use the table to generate sequence numbers like this:
mysql> UPDATE sequence SET id=LAST_INSERT_ID(id+1);
mysql> SELECT LAST_INSERT_ID();
The UPDATE statement increments the sequence counter and causes the next call to
LAST_INSERT_ID() to return the updated value. The SELECT statement retrieves that
value. The mysql_insert_id() C API function can also be used to get the value.
See Section 20.9.3.37, “mysql_insert_id()”.
It's really a bug how you can see here: http://bugs.mysql.com/bug.php?id=26316
But, apparently, they fixed it on 5.1.47 and it was declared as INNODB plugin problem.
A duplicate, but same problem, you can see here too: http://bugs.mysql.com/bug.php?id=53791 referenced to the first page mentioned here in this answer.

Insert if not exists

How to have only 3 rows in the table and only update them?
I have the settings table and at first run there is nothing so I want to insert 3 records like so:
id | label | Value | desc
--------------------------
1 start 10 0
2 middle 24 0
3 end 76 0
After this from PHP script I need to update this settings from one query.
I have researched REPLACE INTO but I end up with duplicate rows in DB.
Here is my current query:
$query_insert=" REPLACE INTO setari (`eticheta`, `valoare`, `disabled`)
VALUES ('mentenanta', '".$mentenanta."', '0'),
('nr_incercari_login', '".$nr_incercari_login."', '0'),
('timp_restrictie_login', '".$timp_restrictie_login."', '0')
";
Any ideas?
Here is the create table statement. Just so you can see in case I'm missing something.
CREATE TABLE `setari` (
`id` int(10) unsigned NOT NULL auto_increment,
`eticheta` varchar(200) NOT NULL,
`valoare` varchar(250) NOT NULL,
`disabled` tinyint(1) unsigned NOT NULL default '0',
`data` datetime default NULL,
`cod` varchar(50) default NULL,
PRIMARY KEY (`eticheta`,`id`,`valoare`),
UNIQUE KEY `id` (`eticheta`,`id`,`valoare`)
) ENGINE=MyISAM
As explained in the manual, need to create a UNIQUE index on (label,value) or (label,value,desc) for REPLACE INTO determine uniqueness.
What you want is to use 'ON DUPLICATE KEY UPDATE' syntax. Read through it for the full details but, essentially you need to have a unique or primary key for one of your fields, then start a normal insert query and add that code (along with what you want to actually update) to the end. The db engine will then try to add the information and when it comes across a duplicate key already inserted, it already knows to just update all the fields you tell it to with the new information.
I simply skip the headache and use a temporary table. Quick and clean.
SQL Server allows you to select into a non-existing temp table by creating it for you. However mysql requires you to first create the temp db and then insert into it.
1.
Create empty temp table.
CREATE TEMPORARY TABLE IF NOT EXISTS insertsetari
SELECT eticheta, valoare, disabled
FROM setari
WHERE 1=0
2.
Insert data into temp table.
INSERT INTO insertsetari
VALUES
('mentenanta', '".$mentenanta."', '0'),
('nr_incercari_login', '".$nr_incercari_login."', '0'),
('timp_restrictie_login', '".$timp_restrictie_login."', '0')
3.
Remove rows in temp table that are already found in target table.
DELETE a FROM insertsetari AS a INNER JOIN setari AS b
WHERE a.eticheta = b.eticheta
AND a.valoare = b.valoare
AND a.disabled = b.disabled
4.
Insert temp table residual rows into target table.
INSERT INTO setari
SELECT * FROM insertsetari
5.
Cleanup temp table.
DELETE insertsetari
Comments:
You should avoid replacing when the
new data and the old data is the
same. Replacing should only be for
situations where there is high
probability for detecting key values
that are the same but the non-key
values are different.
Placing data into a temp table allows
data to be massaged, transformed and modified
easily before inserting into target
table.
Deleting rows from temp table is
faster.
If anything goes wrong, temp table
gives you an additional debugging
stage to find out what went wrong.
Should consider doing it all in a single transaction.