MySQL: insérer un enregistrement s'il n'existe pas dans la table

384

J'essaie d'exécuter la requête suivante:

INSERT INTO table_listnames (name, address, tele)
VALUES ('Rupert', 'Somewhere', '022')
WHERE NOT EXISTS (
    SELECT name FROM table_listnames WHERE name='value'
);

Mais cela renvoie une erreur. Fondamentalement, je ne veux pas insérer un enregistrement si le champ "nom" de l'enregistrement existe déjà dans un autre enregistrement - comment vérifier si le nouveau nom est unique?

Rupert
la source
12
Toutes les réponses actuelles à ceci ou dupes supposent que vous pouvez ajouter un index unique. Parfois, la décision est basée sur une logique métier qui ne peut être imposée à toute la table. Par exemple, vous autorisez plusieurs lignes avec une certaine valeur dans une colonne, mais une autre valeur de la colonne ne pourra apparaître que sur une seule ligne. Comment pouvons-nous y parvenir?
Oscar

Réponses:

502

Je ne suggère pas en fait que vous le fassiez, car l' UNIQUEindex tel que suggéré par Piskvor et d'autres est une bien meilleure façon de le faire, mais vous pouvez réellement faire ce que vous tentiez:

CREATE TABLE `table_listnames` (
  `id` int(11) NOT NULL auto_increment,
  `name` varchar(255) NOT NULL,
  `address` varchar(255) NOT NULL,
  `tele` varchar(255) NOT NULL,
  PRIMARY KEY  (`id`)
) ENGINE=InnoDB;

Insérez un enregistrement:

INSERT INTO table_listnames (name, address, tele)
SELECT * FROM (SELECT 'Rupert', 'Somewhere', '022') AS tmp
WHERE NOT EXISTS (
    SELECT name FROM table_listnames WHERE name = 'Rupert'
) LIMIT 1;

Query OK, 1 row affected (0.00 sec)
Records: 1  Duplicates: 0  Warnings: 0

SELECT * FROM `table_listnames`;

+----+--------+-----------+------+
| id | name   | address   | tele |
+----+--------+-----------+------+
|  1 | Rupert | Somewhere | 022  |
+----+--------+-----------+------+

Essayez d'insérer à nouveau le même enregistrement:

INSERT INTO table_listnames (name, address, tele)
SELECT * FROM (SELECT 'Rupert', 'Somewhere', '022') AS tmp
WHERE NOT EXISTS (
    SELECT name FROM table_listnames WHERE name = 'Rupert'
) LIMIT 1;

Query OK, 0 rows affected (0.00 sec)
Records: 0  Duplicates: 0  Warnings: 0

+----+--------+-----------+------+
| id | name   | address   | tele |
+----+--------+-----------+------+
|  1 | Rupert | Somewhere | 022  |
+----+--------+-----------+------+

Insérez un enregistrement différent:

INSERT INTO table_listnames (name, address, tele)
SELECT * FROM (SELECT 'John', 'Doe', '022') AS tmp
WHERE NOT EXISTS (
    SELECT name FROM table_listnames WHERE name = 'John'
) LIMIT 1;

Query OK, 1 row affected (0.00 sec)
Records: 1  Duplicates: 0  Warnings: 0

SELECT * FROM `table_listnames`;

+----+--------+-----------+------+
| id | name   | address   | tele |
+----+--------+-----------+------+
|  1 | Rupert | Somewhere | 022  |
|  2 | John   | Doe       | 022  |
+----+--------+-----------+------+

Etc...


Mise à jour:

Pour éviter les #1060 - Duplicate column nameerreurs au cas où deux valeurs pourraient être égales, vous devez nommer les colonnes du SELECT interne:

INSERT INTO table_listnames (name, address, tele)
SELECT * FROM (SELECT 'Unknown' AS name, 'Unknown' AS address, '022' AS tele) AS tmp
WHERE NOT EXISTS (
    SELECT name FROM table_listnames WHERE name = 'Rupert'
) LIMIT 1;

Query OK, 1 row affected (0.00 sec)
Records: 1  Duplicates: 0  Warnings: 0

SELECT * FROM `table_listnames`;

+----+---------+-----------+------+
| id | name    | address   | tele |
+----+---------+-----------+------+
|  1 | Rupert  | Somewhere | 022  |
|  2 | John    | Doe       | 022  |
|  3 | Unknown | Unknown   | 022  |
+----+---------+-----------+------+
Mike
la source
18
Merci qui a aidé. Mon problème réel est beaucoup plus complexe et la colonne ne peut tout simplement pas être unique et je ne peux pas dépendre de la clé primaire. Mais c'est exactement ce que je cherchais.
Rupert
2
@Piskovar: D'accord. @Rupert: vous devez indexer la colonne mentionnée dans l'instruction de sélection interne ( name, dans ce cas), si cela est possible. Notez également que vous pouvez le faire SELECT 'John', 'Doe', '022' FROM table_listnamesau lieu de SELECT * FROM (SELECT 'John', 'Doe', '022') AS tmp- mais cela ne fonctionnera que s'il table_listnamescontient déjà une ou plusieurs lignes. Je doute que la vitesse soit différente, donc ce n'est probablement pas un problème.
Mike
3
@VijayRamamurthy: Cela fonctionne car vous insérez le résultat d'une instruction Select. Lisez attentivement la requête -> L' WHEREinstruction appartient à la SELECTrequête. La requête Select renvoie soit une seule ligne de données (les données sont insérées), soit aucune donnée du tout (rien n'est inséré)
Philipp
13
Cela ne semble pas fonctionner si vous souhaitez insérer deux fois la même valeur dans des champs différents (par exemple SELECT 'Humbert', 'Humbert', '1'dans la sélection interne). Je reçois unERROR 1060 (42S21): Duplicate column name
cburgmer
28
@cburgmer J'ai eu le même problème #1060 - Duplicate column name. Avez-vous trouvé une solution? Edit: Je l'ai trouvé. Ajoutez derrière chaque valeur un AS:SELECT * FROM (SELECT 'Rupert' as name, 'Rupert' as friend, '022' as number) AS tmp
Kai Noack
292

INSERT ne permet pas WHEREdans la syntaxe .

Ce que vous pouvez faire: créez un UNIQUE INDEXsur le champ qui devrait être unique ( name), puis utilisez:

  • normal INSERT(et gérer l'erreur si le nom existe déjà)
  • INSERT IGNORE(qui échouera en silence provoquera un avertissement (au lieu d'une erreur) si le nom existe déjà)
  • INSERT ... ON DUPLICATE KEY UPDATE(qui exécutera le UPDATEà la fin si le nom existe déjà, voir la documentation )
Piskvor a quitté le bâtiment
la source
1
si vous avez besoin d'un message d'avertissement, vous pouvez le faire par la suitemysql> show warnings\G
fiorentinoing
2
ime, le message d'avertissement, dans le cas de INSERT IGNORE, correspond à l'expression ^Duplicate entry '.+' for key '.+'$
régulière
1
si vous utilisez l'insertion ignorer, ignorera-t-il d'autres erreurs qui peuvent se produire qui ne sont pas dues à des doublons?
gepex
1
@gepex Oui, ce sera le cas. C'est un gros problème avec l'utilisation de l' INSERT IGNOREOMI.
shmosel
1
Remarque: cette réponse pourrait ne pas être le meilleur choix si l'une des colonnes de la contrainte de clé unique est nullable! La contrainte fonctionne, mais vous pouvez vous attendre à d'autres résultats :) L'insertion de ("John Doe", NULL), ("John Doe", NULL) donne deux lignes bien que les deux champs ensemble soient dans une contrainte de clé unique. Voir également stackoverflow.com/questions/4081783/unique-key-with-nulls
Piemol
57

Travaillé :

INSERT INTO users (full_name, login, password) 
  SELECT 'Mahbub Tito','tito',SHA1('12345') FROM DUAL
WHERE NOT EXISTS 
  (SELECT login FROM users WHERE login='tito');
Mahbub Tito
la source
Cela ne fonctionne que pour les bases de données Oracle, non? en.wikipedia.org/wiki/DUAL_table Cette question concerne spécifiquement MySQL!
Très irrégulier
1
Je ne teste pas dans la base de données Oracle. Il fonctionne bien sur MySQL et testé.
Mahbub Tito
9
J'ai appris depuis que "dual" est disponible dans MySQL mais pas dans MariaDB, la branche open source de MySQL
Highly Irregular
3
Du lien wikipedia de @ HighlyIrregular "Plusieurs autres bases de données (y compris Microsoft SQL Server, MySQL, PostgreSQL, SQLite et Teradata) permettent d'omettre complètement la clause FROM si aucune table n'est nécessaire. Cela évite d'avoir besoin d'une table factice." La même requête semble fonctionner dans MySql (5.7, si cela importe) sans le FROM DUAL.
stannius
1
Testé et fonctionne bien dans MariaDB! Merci @MahbubTito
Jeff Monteiro
28

MySQL fournit une solution très mignonne:

REPLACE INTO `table` VALUES (5, 'John', 'Doe', SHA1('password'));

Très facile à utiliser puisque vous avez déclaré une clé primaire unique (ici avec la valeur 5).

sdesvergez
la source
pour l'empêcher, vous devez créer un index unique, plus d'informations ici: lien
Azmeer
Très dangereux. N'essayez pas à moins de savoir ce que vous faites. En fait, n'essayez pas du tout. Vérifiez les autres réponses.
Jhourlad Estrella
3
Remarque, cela SUPPRIME toutes les lignes correspondantes, puis les réinsère, une version plus sûre de ce même comportement consiste à utiliser ON DUPLICTE KEY UPDATE, qui met à jour les lignes de calcul plutôt que de les supprimer et de les réinsérer: dev.mysql.com/doc/ refman / 5.7 / en / replace.html
Omn
22
INSERT IGNORE INTO `mytable`
SET `field0` = '2',
`field1` = 12345,
`field2` = 12678;

Voici la requête mysql, qui insère des enregistrements si elle n'existe pas et ignorera les enregistrements similaires existants.

----Untested----
Montaser El-sawy
la source
4
cela ne fonctionne pas pour moi, INSERT IGNORE INTO emAssignedTagsForEvent SET eventId = '2', defaultTagId = '1';
pitu
8
On dirait un mélange de syntaxes d'insertion et de mise à jour. Tu veux dire INSERT IGNORE INTO `mytable` (`field0`, `field1`, `field2`) values ('2', 12345, 12678);?
Hobo
@Hobo From MySQL Manual dev.mysql.com/doc/refman/5.7/en/insert.html INSERT syntaxe: Cela peut être INSERT [...] IGNORE INTO mytable VALUES ...OrINSERT [...] IGNORE INTO mytable SET col_name={expr | DEFAULT},...
Shirkam
"Si vous utilisez le modificateur IGNORE, les erreurs qui se produisent lors de l'exécution de l'instruction INSERT sont ignorées.". Donc, fondamentalement, vous n'avez rien résolu.
Marcelo Agimóvel
18

Vous pouvez facilement utiliser la méthode suivante:

INSERT INTO ... ON DUPLICATE KEY UPDATE ...

De cette façon, vous pouvez insérer n'importe quel nouveau brut et si vous avez des données en double, remplacer une colonne spécifique (les meilleures colonnes sont des horodatages).
Par exemple :

CREATE TABLE IF NOT EXISTS Devices (
  id         INT(6)       NOT NULL AUTO_INCREMENT,
  unique_id  VARCHAR(100) NOT NULL PRIMARY KEY,
  created_at VARCHAR(100) NOT NULL,
  UNIQUE KEY unique_id (unique_id),
  UNIQUE KEY id (id)
)
  CHARACTER SET utf8
  COLLATE utf8_unicode_ci;

INSERT INTO Devices(unique_id, time) 
VALUES('$device_id', '$current_time') 
ON DUPLICATE KEY UPDATE time = '$current_time';
Arash Hatami
la source
13

Si vous ne pouvez vraiment pas obtenir un index unique sur la table, vous pouvez essayer ...

INSERT INTO table_listnames (name, address, tele)
    SELECT 'Rupert', 'Somewhere', '022'
        FROM some_other_table
        WHERE NOT EXISTS (SELECT name
                              FROM table_listnames
                              WHERE name='Rupert')
        LIMIT 1;
Brian Hooper
la source
12

Pour surmonter un problème similaire, j'ai modifié la table pour avoir une colonne unique. En utilisant votre exemple, lors de la création, j'aurais quelque chose comme:

name VARCHAR(20),
UNIQUE (name)

puis utilisez la requête suivante lors de l'insertion:

INSERT IGNORE INTO train
set table_listnames='Rupert'
obsessiveCookie
la source
Je préfère cette solution.
Ilyas karim
9

Cette requête fonctionne bien:

INSERT INTO `user` ( `username` , `password` )
    SELECT * FROM (SELECT 'ersks', md5( 'Nepal' )) AS tmp
    WHERE NOT EXISTS (SELECT `username` FROM `user` WHERE `username` = 'ersks' 
    AND `password` = md5( 'Nepal' )) LIMIT 1

Et vous pouvez créer la table en utilisant la requête suivante:

CREATE TABLE IF NOT EXISTS `user` (
    `id` int(11) NOT NULL AUTO_INCREMENT,
    `username` varchar(30) NOT NULL,
    `password` varchar(32) NOT NULL,
    `status` tinyint(1) DEFAULT '0',
    PRIMARY KEY (`id`)
) ENGINE=InnoDB  DEFAULT CHARSET=latin1 AUTO_INCREMENT=1 ;

Remarque: créez une table à l'aide de la deuxième requête avant d'essayer d'utiliser la première requête.

dakab
la source
hachage de mot de passe md5 .... yikes!
Chad Grant
4

Brian Hooper: Vous avez presque atteint le point mais vous avez une erreur dans votre synatx. Votre insert ne fonctionnera jamais. J'ai testé sur ma base de données et voici la bonne réponse:

INSERT INTO podatki (datum,ura,meritev1,meritev1_sunki,impulzi1,meritev2,meritev2_sunki,impulzi2)
            SELECT '$datum', '$ura', '$meritve1','$sunki1','$impulzi1','$meritve2','$sunki2','$impulzi2'
                FROM dual
                WHERE NOT EXISTS (SELECT datum,ura
                                      FROM podatki
                                      WHERE datum='$datum' and ura='$ura'

Je vous donne mon exemple de table y. L'insertion est presque la même que celle de Bian Hooper, sauf que je mets la sélection FROM DUAL ont d'une autre table. Cordialement, Ivan

Ivan Laharnar
la source
2
Quel est ce "double"? Un mot clé intégré? Je ne vois tout simplement pas la différence avec ce que Brian a dit ... EDIT: Une enquête plus approfondie a donné des résultats mitigés et contradictoires (?). Alors que la page de table SQL DUAL indique que MySQL ne prend pas en charge les tables DUAL, le manuel MySQL le dit. Mon propre test montre que non, car il donne des unknown table status: TABLE_TYPEmessages, bien que la requête ait donné un résultat. Probablement parce que MySQL ne nécessite pas la FROM DUALclause?
not2qubit
4
insert into customer_keyskill(customerID, keySkillID)
select  2,1 from dual
where not exists ( 
    select  customerID  from customer_keyskill 
    where customerID = 2 
    and keySkillID = 1 )
Oms G
la source
dual est une table Oracle et non MySQL
Highly Irregular
3

Vous insérez pas la mise à jour du résultat. Vous pouvez définir la colonne de nom dans la colonne principale ou définir qu'elle est unique.

Utilisateur 1034
la source
3

J'ai eu un problème et la méthode conseillée par Mike a fonctionné en partie, j'ai eu une erreur Dublate Column name = '0', et j'ai changé la syntaxe de votre requête comme ceci`

     $tQ = "INSERT  INTO names (name_id, surname_id, sum, sum2, sum3,sum4,sum5) 
                SELECT '$name', '$surname', '$sum', '$sum2', '$sum3','$sum4','$sum5' 
FROM DUAL
                WHERE NOT EXISTS (
                SELECT sum FROM names WHERE name_id = '$name' 
AND surname_id = '$surname') LIMIT 1;";

Le problème était avec les noms des colonnes. sum3 était égal à sum4 et mysql a lancé des noms de colonnes en double, et j'ai écrit le code dans cette syntaxe et cela a parfaitement fonctionné,

Hovhannes Babayan
la source
1
Le propriétaire de DUAL est SYS (SYS possède le dictionnaire de données, donc DUAL fait partie du dictionnaire de données.) Mais DUAL est accessible à tout utilisateur. La table a une seule colonne VARCHAR2 (1) appelée DUMMY qui a une valeur de «X». MySQL permet de spécifier DUAL en tant que table dans les requêtes qui ne nécessitent pas de données provenant de tables.La table DUAL est une table spéciale à une ligne et à une colonne présente par défaut dans Oracle et d'autres installations de bases de données. Dans Oracle, la table a une seule colonne VARCHAR2 (1) appelée DUMMY qui a une valeur de «X». Il peut être utilisé pour sélectionner une pseudo-colonne telle que SYSDATE ou USER.
Adnan haider le
3

J'ai eu un problème similaire et j'ai dû en insérer plusieurs si ce n'est pas le cas. Donc, à partir des exemples ci-dessus, je suis arrivé à cette combinaison ... c'est ici juste au cas où quelqu'un en aurait besoin.

Remarque: j'ai dû définir un nom partout car MSSQL l'exigeait ... MySQL fonctionne aussi avec *.

INSERT INTO names (name)
SELECT name
FROM
(
  SELECT name
  FROM
  (
     SELECT 'Test 4' as name
  ) AS tmp_single
  WHERE NOT EXISTS
  (
     SELECT name FROM names WHERE name = 'Test 4'
  )
  UNION ALL
  SELECT name
  FROM
  (
     SELECT 'Test 5' as name
  ) AS tmp_single
  WHERE NOT EXISTS
  (
     SELECT name FROM names WHERE name = 'Test 5'
  )
) tmp_all;

MySQL: CREATE TABLE names( OIDint (11) NOT NULL AUTO_INCREMENT, namevarchar (32) COLLATE utf8_unicode_ci NOT NULL, PRIMARY KEY ( OID), UNIQUE KEY name_UNIQUE( name)) ENGINE = InnoDB AUTO_INCREMENT = 1;

ou

MSSQL: CREATE TABLE [names] ([OID] INT IDENTITY (1, 1) NOT NULL, [name] NVARCHAR (32) NOT NULL, PRIMARY KEY CLUSTERED ([OID] ASC)); CRÉER UN INDEX UNCLUSTERED UNIQUE [Index_Names_Name] ON [noms] ([nom] ASC);

Waldemar
la source
2

Cette requête peut être utilisée en code PHP.

J'ai une colonne ID dans ce tableau, j'ai donc besoin de vérifier la duplication pour toutes les colonnes sauf cette colonne ID:

#need to change values
SET @goodsType = 1, @sybType=5, @deviceId = asdf12345SDFasdf2345;    


INSERT INTO `devices` (`goodsTypeId`, `goodsId`, `deviceId`) #need to change tablename and columnsnames
SELECT * FROM (SELECT @goodsType, @sybType, @deviceId) AS tmp
WHERE NOT EXISTS (
    SELECT 'goodsTypeId' FROM `devices` #need to change tablename and columns names
    WHERE `goodsTypeId` = @goodsType
        AND `goodsId` = @sybType
        AND `deviceId` = @deviceId
) LIMIT 1;

et maintenant un nouvel élément sera ajouté uniquement s'il n'y a pas de ligne avec des valeurs configurées dans la SETchaîne

Andrew
la source