Vous pouvez vérifier la fonction LAST_INSERT_ID() et INSERT IGNORE.
Si INSERT IGNORE a réussi, vous obtenez la clé primaire renvoyée. Créons une table avec une clé primaire à incrémentation automatique et une clé unique sur un nom.
use test
DROP TABLE IF EXISTS nametable;
CREATE TABLE nametable
(
id int not null auto_increment,
name varchar(20) not null,
primary key (id),
unique key (name)
);
DELIMITER $$
DROP FUNCTION IF EXISTS `test`.`InsertName` $$
CREATE FUNCTION `test`.`InsertName` (newname VARCHAR(20)) RETURNS INT
BEGIN
INSERT IGNORE INTO test.nametable (name) VALUES (newname);
RETURN LAST_INSERT_ID();
END $$
DELIMITER ;
SELECT InsertName('rolando');
SELECT InsertName('rolando');
SELECT InsertName('pamela');
SELECT InsertName('pamela');
SHOW CREATE TABLE test.nametable\G
SELECT * FROM test.nametable;
Voici l'exemple en cours d'exécution :
mysql> use test
Database changed
mysql> DROP TABLE IF EXISTS nametable;
Query OK, 0 rows affected (0.04 sec)
mysql> CREATE TABLE nametable
-> (
-> id int not null auto_increment,
-> name varchar(20) not null,
-> primary key (id),
-> unique key (name)
-> );
Query OK, 0 rows affected (0.07 sec)
mysql> DELIMITER $$
mysql> DROP FUNCTION IF EXISTS `test`.`InsertName` $$
Query OK, 0 rows affected (0.00 sec)
mysql> CREATE FUNCTION `test`.`InsertName` (newname VARCHAR(20)) RETURNS INT
-> BEGIN
-> INSERT IGNORE INTO test.nametable (name) VALUES (newname);
-> RETURN LAST_INSERT_ID();
-> END $$
Query OK, 0 rows affected (0.00 sec)
mysql> DELIMITER ;
mysql> SELECT InsertName('rolando');
+-----------------------+
| InsertName('rolando') |
+-----------------------+
| 1 |
+-----------------------+
1 row in set (0.03 sec)
mysql> SELECT InsertName('rolando');
+-----------------------+
| InsertName('rolando') |
+-----------------------+
| 0 |
+-----------------------+
1 row in set (0.02 sec)
mysql> SELECT InsertName('pamela');
+----------------------+
| InsertName('pamela') |
+----------------------+
| 3 |
+----------------------+
1 row in set (0.02 sec)
mysql> SELECT InsertName('pamela');
+----------------------+
| InsertName('pamela') |
+----------------------+
| 0 |
+----------------------+
1 row in set (0.03 sec)
mysql> SHOW CREATE TABLE test.nametable\G
*************************** 1. row ***************************
Table: nametable
Create Table: CREATE TABLE `nametable` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(20) NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `name` (`name`)
) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=latin1
1 row in set (0.00 sec)
mysql> SELECT * FROM test.nametable;
+----+---------+
| id | name |
+----+---------+
| 3 | pamela |
| 1 | rolando |
+----+---------+
2 rows in set (0.00 sec)
mysql>
Comme indiqué dans l'exemple précédent, vous pouvez vérifier la valeur de retour de la fonction. Une valeur de retour différente de zéro signifie que l'INSERT IGNORE s'est bien passé. Une valeur de retour nulle indique une clé en double sans introduire de numéro d'erreur dans mysqld.
L'inconvénient de cette approche est que vous ne pouvez pas revenir en arrière et utiliser les identifiants 2 et 4 en raison des tentatives infructueuses d'INSERT IGNORE en cas de clé en double.
Essayons un autre exemple avec une configuration de fonction stockée différente en utilisant INSERT et sans utiliser LAST_INSERT_ID() :
use test
DROP TABLE IF EXISTS nametable;
CREATE TABLE nametable
(
id int not null auto_increment,
name varchar(20) not null,
primary key (id),
unique key (name)
);
DELIMITER $$
DROP FUNCTION IF EXISTS `test`.`InsertName` $$
CREATE FUNCTION `test`.`InsertName` (newname VARCHAR(20)) RETURNS INT
BEGIN
DECLARE rv INT;
SELECT COUNT(1) INTO rv FROM test.nametable WHERE name = newname;
IF rv = 0 THEN
INSERT INTO test.nametable (name) VALUES (newname);
END IF;
RETURN rv;
END $$
DELIMITER ;
SELECT InsertName('rolando');
SELECT InsertName('rolando');
SELECT InsertName('pamela');
SELECT InsertName('pamela');
SHOW CREATE TABLE test.nametable\G
SELECT * FROM test.nametable;
Voici le résultat :
mysql> use test
Database changed
mysql> DROP TABLE IF EXISTS nametable;
Query OK, 0 rows affected, 1 warning (0.00 sec)
mysql> CREATE TABLE nametable
-> (
-> id int not null auto_increment,
-> name varchar(20) not null,
-> primary key (id),
-> unique key (name)
-> );
Query OK, 0 rows affected (0.10 sec)
mysql> DELIMITER $$
mysql> DROP FUNCTION IF EXISTS `test`.`InsertName` $$
Query OK, 0 rows affected (0.00 sec)
mysql> CREATE FUNCTION `test`.`InsertName` (newname VARCHAR(20)) RETURNS INT
-> BEGIN
-> DECLARE rv INT;
-> SELECT COUNT(1) INTO rv FROM test.nametable WHERE name = newname;
-> IF rv = 0 THEN
-> INSERT INTO test.nametable (name) VALUES (newname);
-> END IF;
-> RETURN rv;
-> END $$
Query OK, 0 rows affected (0.00 sec)
mysql> DELIMITER ;
mysql> SELECT InsertName('rolando');
+-----------------------+
| InsertName('rolando') |
+-----------------------+
| 0 |
+-----------------------+
1 row in set (0.04 sec)
mysql> SELECT InsertName('rolando');
+-----------------------+
| InsertName('rolando') |
+-----------------------+
| 1 |
+-----------------------+
1 row in set (0.00 sec)
mysql> SELECT InsertName('pamela');
+----------------------+
| InsertName('pamela') |
+----------------------+
| 0 |
+----------------------+
1 row in set (0.03 sec)
mysql> SELECT InsertName('pamela');
+----------------------+
| InsertName('pamela') |
+----------------------+
| 1 |
+----------------------+
1 row in set (0.00 sec)
mysql> SHOW CREATE TABLE test.nametable\G
*************************** 1. row ***************************
Table: nametable
Create Table: CREATE TABLE `nametable` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(20) NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `name` (`name`)
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=latin1
1 row in set (0.00 sec)
mysql> SELECT * FROM test.nametable;
+----+---------+
| id | name |
+----+---------+
| 2 | pamela |
| 1 | rolando |
+----+---------+
2 rows in set (0.00 sec)
mysql>
Dans cet exemple, la fonction stockée renvoie 0 si l'INSERT était OK, et renvoie 1 avec une clé en double sur le nom. L'avantage? Pas de numéros d'identification gaspillés pour auto_increment. Le désavantage? Faire une instruction SELECT à chaque fois pour vérifier si le nom est déjà présent dans la table.
Vous avez le choix de la manière dont vous souhaitez gérer les clés en double. La première méthode laisse mysqld gérer la condition de l'INSERT IGNORE. La deuxième méthode a la fonction stockée vérifiant la clé en double avant l'INSERT.