Inserción condicional de MySQL

98

Estoy teniendo dificultades para formar un INSERT condicional

Tengo x_table con columnas (instancia, usuario, elemento) donde el ID de instancia es único. Quiero insertar una nueva fila solo si el usuario ya no tiene un elemento determinado.

Por ejemplo, intentando insertar instancia = 919191 usuario = 123 elemento = 456

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

Cualquier ayuda u orientación en la dirección correcta será muy apreciada.

El desconocido
fuente

Respuestas:

115

Si su DBMS no impone limitaciones sobre la tabla que selecciona cuando ejecuta una inserción, intente:

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 esto, dualhay una tabla con una sola fila (que se encuentra originalmente en Oracle, ahora también en mysql). La lógica es que la instrucción SELECT genera una única fila de datos con los valores requeridos, pero solo cuando los valores aún no se encuentran.

Alternativamente, mire la declaración MERGE.

Jonathan Leffler
fuente
1
¿ dualDebe crearse de antemano en este caso o es algún tipo de tabla "temporal" especial creada por la consulta para el uso de la consulta solamente?
itsmequinn
8
Si aún no lo ha dualhecho, debe crearlo. 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
Gracias, es increíble saberlo
itsmequinn
11
Tenga en cuenta que, en MySQL, realmente no necesita tener una tabla llamada 'dual' para existir: es un nombre de tabla especial que se puede usar para seleccionar cualquier cosa de ella.
Christopher Schultz
3
MySQL "respeta" el concepto de "dual" pero en realidad no existe. Por ejemplo, si SELECT COUNT(*) FROM dualsiempre obtienes 1y si SELECT 'foo' FROM dualsiempre obtienes foo. Pero no puedes SELECT * FROM dualy no puedes DESCRIBE dualni nada por el estilo. No lo he comprobado, pero tampoco creo que puedas revocar los permisos dual. Así que vale la pena señalar que funciona como esperas ... excepto cuando no lo hace;)
Christopher Schultz
52

También puede usar INSERT IGNOREque ignora silenciosamente la inserción en lugar de actualizar o insertar una fila cuando tiene un índice único en (usuario, elemento).

La consulta se verá así:

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

Puede agregar el índice único con CREATE UNIQUE INDEX user_item ON x_table (user, item).

MrD
fuente
2
Según la documentación de MySQL, INSERT IGNORE es la contraparte de REPLACE ... INSERT IGNORE: conserva las filas antiguas. REEMPLAZAR: mantener nuevas filas. Ambos funcionan con claves únicas.
Paul Kenjora
La mejor respuesta de todos los tiempos
jmojico
30

¿Alguna vez has probado algo así?

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;
templo
fuente
Oh si ! solución inteligente y agradable con solo una declaración SELECT
java acm
Agradable. Esto funciona para postgresql en el primer intento, donde la respuesta aceptada no parecía para mí.
mightypile
10

Con a UNIQUE(user, item), haz:

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

el user=123bit es un "no-op" para que coincida con la sintaxis de la ON DUPLICATEcláusula sin hacer nada cuando hay duplicados.

Alex Martelli
fuente
1
No puedo configurar un único (usuario, elemento) porque puede haber instancias en las que hay varias instancias con el mismo usuario y elemento ... pero no cuando estoy haciendo esta inserción.
The Unknown
3

En caso de que no desee establecer una restricción única, esto funciona como un encanto:

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

Espero eso ayude !

Gew
fuente
¡Esto fue perfecto, gracias!
AndyGaskell hace
2

Lo que quieres es INSERT INTO table (...) SELECT ... WHERE .... del manual de MySQL 5.6 .

En tu caso es:

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

O tal vez, dado que no está utilizando ninguna lógica complicada para determinar si ejecutar INSERTo no, podría simplemente establecer una UNIQUEclave en la combinación de estas dos columnas y luego usar INSERT IGNORE.

martín
fuente
¿Ejecutó su respuesta? Sigo recibiendo 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...(ejecutando 5.6.34)
hlin117
Creo que tienes que decirlo from dualantes where.
hlin117
@ hlin117 En MariaDB FROM DUALes el valor predeterminado si no se hizo referencia a tablas ( consulte esta documentación ).
Yeti
1

Si agrega una restricción que (x_table.user, x_table.item) es única, la inserción de otra fila con el mismo usuario y elemento fallará.

p.ej:

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
fuente
Eso no está bien. Puede haber varias filas con el mismo usuario, pero no varias filas con un par usuario-elemento.
Matthew Flaschen
Sí, sí. Lo leí mal. Editado para hacer la restricción en el par.
NickZoic
Buen comentario ... ejecuto la consulta en PHP usando mysql_query ("INSERT") y dejo la parte or die, por lo tanto, no me advierte sobre el error, así que no tengo que hacer ninguna verificación
Angel.King.47
1

Aunque es bueno verificar la duplicación antes de insertar sus datos, le sugiero que coloque una restricción / índice único en sus columnas para que no se puedan insertar datos duplicados por error.

Beatles1692
fuente
1

Puede utilizar la siguiente solución para resolver su problema:

INSERT INTO x_table(instance, user, item) 
    SELECT 919191, 123, 456
        FROM dual
        WHERE 123 NOT IN (SELECT user FROM x_table)
Keith Becker
fuente
Si bien este código puede responder a la pregunta, proporcionar un contexto adicional sobre cómo y / o por qué resuelve el problema mejoraría el valor de la respuesta a largo plazo.
Nic3500
0

Ligera modificación a la respuesta de Alex, también puede hacer referencia al valor de columna existente:

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

Entonces este representa 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
fuente
-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 sintaxis puede variar dependiendo de su base de datos ...

Rick J
fuente
2
¿Para qué es esto? MySQL?
Umair A.