Insertion conditionnelle MySQL

98

J'ai du mal à former un INSERT conditionnel

J'ai x_table avec des colonnes (instance, utilisateur, élément) où l'ID d'instance est unique. Je souhaite insérer une nouvelle ligne uniquement si l'utilisateur ne dispose déjà pas d'un élément donné.

Par exemple en essayant d'insérer instance = 919191 user = 123 item = 456

Insert into x_table (instance, user, item) values (919191, 123, 456) 
    ONLY IF there are no rows where user=123 and item=456 

Toute aide ou orientation dans la bonne direction serait très appréciée.

L'inconnu
la source

Réponses:

115

Si votre SGBD n'impose pas de limitations sur la table dans laquelle vous sélectionnez lorsque vous exécutez une insertion, essayez:

INSERT INTO x_table(instance, user, item) 
    SELECT 919191, 123, 456
        FROM dual
        WHERE NOT EXISTS (SELECT * FROM x_table
                             WHERE user = 123 
                               AND item = 456)

En cela, il dualy a une table avec une seule ligne (trouvée à l'origine dans Oracle, maintenant aussi dans mysql). La logique est que l'instruction SELECT génère une seule ligne de données avec les valeurs requises, mais uniquement lorsque les valeurs ne sont pas déjà trouvées.

Vous pouvez également consulter l'instruction MERGE.

Jonathan Leffler
la source
1
Doit- dualil être créé au préalable dans ce cas ou s'agit-il d'une sorte de table "temporaire" spéciale créée par la requête pour l'utilisation de la requête seule?
itsmequinn
8
Si vous ne l'avez pas dualdéjà, vous devez le créer. CREATE TABLE dual ( dummy CHAR(1) DEFAULT 'x' NOT NULL CHECK (dummy = 'x') PRIMARY KEY ); INSERT INTO dual VALUES('x'); REVOKE ALL ON dual FROM PUBLIC; GRANT SELECT ON dual TO PUBLIC;
Jonathan Leffler
Merci c'est génial à savoir
itsmequinn
11
Notez que dans MySQL, vous n'avez pas vraiment besoin d'avoir une table appelée 'dual' pour exister: c'est un nom de table spécial qui peut être utilisé pour en sélectionner n'importe quoi.
Christopher Schultz
3
MySQL "respecte" le concept de "double" mais il n'existe pas réellement. Par exemple, si vous SELECT COUNT(*) FROM dualobtenez toujours 1, et si vous SELECT 'foo' FROM dualobtenez toujours foo. Mais vous ne pouvez pas SELECT * FROM dualet vous ne pouvez pas DESCRIBE dualou quelque chose comme ça. Je n'ai pas vérifié, mais je ne pense pas non plus que vous puissiez révoquer les autorisations sur dual. Il convient donc de souligner que cela fonctionne comme prévu ... sauf quand ce n'est pas le cas;)
Christopher Schultz
52

Vous pouvez aussi utiliser INSERT IGNORE qui ignore silencieusement l'insertion au lieu de mettre à jour ou d'insérer une ligne lorsque vous avez un index unique sur (utilisateur, élément).

La requête ressemblera à ceci:

INSERT IGNORE INTO x_table(instance, user, item) VALUES (919191, 123, 456)

Vous pouvez ajouter l'index unique avec CREATE UNIQUE INDEX user_item ON x_table (user, item).

MrD
la source
2
Par documentation MySQL INSERT IGNORE est l'équivalent de REPLACE ... INSERT IGNORE: conserve les anciennes lignes. REMPLACER: conserver les nouvelles lignes. Les deux fonctionnent sur des clés uniques.
Paul Kenjora du
La meilleure réponse de tous les temps
jmojico
30

Avez-vous déjà essayé quelque chose comme ça?

INSERT INTO x_table
SELECT 919191 as instance, 123 as user, 456 as item
FROM x_table
WHERE (user=123 and item=456)
HAVING COUNT(*) = 0;
temple
la source
Oh oui ! solution intelligente et agréable avec une seule instruction SELECT
java acm
Agréable. Cela fonctionne pour postgresql du premier coup, où la réponse acceptée ne me semblait pas.
mightypile
10

Avec a UNIQUE(user, item), faites:

Insert into x_table (instance, user, item) values (919191, 123, 456) 
  ON DUPLICATE KEY UPDATE user=123

le user=123bit est un "no-op" pour correspondre à la syntaxe de la ON DUPLICATEclause sans rien faire quand il y a des doublons.

Alex Martelli
la source
1
Je ne peux pas configurer un Unique (utilisateur, élément) car il peut y avoir des instances lorsque plusieurs instances avec le même utilisateur et élément ... mais pas lorsque je fais cette insertion.
L'inconnu
3

Au cas où vous ne voudriez pas définir une contrainte unique, cela fonctionne comme un charme:

INSERT INTO `table` (`column1`, `column2`) SELECT 'value1', 'value2' FROM `table` WHERE `column1` = 'value1' AND `column2` = 'value2' HAVING COUNT(`column1`) = 0

J'espère que ça aide !

Gew
la source
C'était parfait, merci!
AndyGaskell il y a
2

Ce que tu veux, c'est INSERT INTO table (...) SELECT ... WHERE .... à partir du manuel MySQL 5.6 .

Dans votre cas c'est:

INSERT INTO x_table (instance, user, item) SELECT 919191, 123, 456 
WHERE (SELECT COUNT(*) FROM x_table WHERE user=123 AND item=456) = 0

Ou peut-être que vous n'utilisez pas de logique compliquée pour déterminer s'il faut exécuter le INSERTou non, vous pouvez simplement définir une UNIQUEclé sur la combinaison de ces deux colonnes, puis l'utiliser INSERT IGNORE.

Martin
la source
Avez-vous exécuté votre réponse? Je continue à recevoir ERROR 1064 (42000): You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'where...(en cours d'exécution 5.6.34)
hlin117
Je pense que vous devez le dire from dualavant where.
hlin117
@ hlin117 In MariaDB FROM DUALest la valeur par défaut si aucune table n'a été référencée ( voir cette documentation ).
Yeti
1

Si vous ajoutez une contrainte qui (x_table.user, x_table.item) est unique, l'insertion d'une autre ligne avec le même utilisateur et élément échouera.

par exemple:

mysql> create table x_table ( instance integer primary key auto_increment, user integer, item integer, unique (user, item));
Query OK, 0 rows affected (0.00 sec)

mysql> insert into x_table (user, item) values (1,2),(3,4);
Query OK, 2 rows affected (0.00 sec)
Records: 2  Duplicates: 0  Warnings: 0

mysql> insert into x_table (user, item) values (1,6);
Query OK, 1 row affected (0.00 sec)

mysql> insert into x_table (user, item) values (1,2);
ERROR 1062 (23000): Duplicate entry '1-2' for key 2
NickZoic
la source
Ce n'est pas juste. Il peut y avoir plusieurs lignes avec le même utilisateur, mais pas plusieurs lignes avec une paire utilisateur-élément.
Matthew Flaschen
Yeah Yeah. Je l'ai mal lu. Modifié pour rendre la contrainte sur la paire.
NickZoic
Joli commentaire ... j'exécute la requête en PHP en utilisant mysql_query ("INSERT") et laisse la partie or die donc il ne me prévient pas de l'erreur donc je n'ai pas à faire de vérifications
Angel.King.47
1

Bien qu'il soit bon de vérifier la duplication avant d'insérer vos données, je vous suggère de mettre une contrainte / un index unique sur vos colonnes afin qu'aucune donnée en double ne puisse être insérée par erreur.

Beatles1692
la source
1

Vous pouvez utiliser la solution suivante pour résoudre votre problème:

INSERT INTO x_table(instance, user, item) 
    SELECT 919191, 123, 456
        FROM dual
        WHERE 123 NOT IN (SELECT user FROM x_table)
Keith Becker
la source
Bien que ce code puisse répondre à la question, fournir un contexte supplémentaire sur la façon et / ou pourquoi il résout le problème améliorerait la valeur à long terme de la réponse.
Nic3500
0

Légère modification de la réponse d'Alex, vous pouvez également simplement référencer la valeur de colonne existante:

Insert into x_table (instance, user, item) values (919191, 123, 456) 
  ON DUPLICATE KEY UPDATE user=user
Danny
la source
0

Donc celui-ci signifie PostgreSQL

INSERT INTO x_table
SELECT NewRow.*
FROM (SELECT 919191 as instance, 123 as user, 456 as item) AS NewRow
LEFT JOIN x_table
ON x_table.user = NewRow.user AND x_table.item = NewRow.item
WHERE x_table.instance IS NULL
xsubira
la source
-1
Insert into x_table (instance, user, item) values (919191, 123, 456) 
    where ((select count(*) from x_table where user=123 and item=456) = 0);

La syntaxe peut varier en fonction de votre base de données ...

Rick J
la source
2
A quoi ça sert? MySQL?
Umair A.