Inserção condicional de MySQL

98

Estou tendo dificuldades para formar um INSERT condicional

Eu tenho x_table com colunas (instância, usuário, item) onde o ID da instância é único. Quero inserir uma nova linha apenas se o usuário já não tiver um determinado item.

Por exemplo, tentando inserir 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 

Qualquer ajuda ou orientação na direção certa seria muito apreciada.

O desconhecido
fonte

Respostas:

115

Se o seu DBMS não impõe limitações na tabela que você seleciona ao executar uma inserção, tente:

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)

Neste, dualé uma tabela com apenas uma linha (encontrada originalmente no Oracle, agora também no mysql). A lógica é que a instrução SELECT gera uma única linha de dados com os valores necessários, mas apenas quando os valores ainda não foram encontrados.

Alternativamente, veja a instrução MERGE.

Jonathan Leffler
fonte
1
Será dualnecessidade de ser criada antes da mão, neste caso, ou é algum tipo de especial mesa "temporário" criado pela consulta para o uso da consulta sozinho?
itsmequinn
8
Se ainda não o fez dual, você precisa criá-lo. 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
Obrigado, é incrível saber
itsmequinn
11
Observe que, no MySQL, você realmente não precisa ter uma tabela chamada 'dual' para existir: é um nome de tabela especial que pode ser usado para selecionar qualquer coisa dela.
Christopher Schultz
3
O MySQL "respeita" o conceito de "dual", mas na verdade ele não existe. Por exemplo, se você SELECT COUNT(*) FROM dualsempre consegue 1, e se SELECT 'foo' FROM dualvocê sempre consegue foo. Mas você não pode SELECT * FROM duale você não pode DESCRIBE dualou algo parecido. Não verifiquei, mas também não acho que você possa revogar as permissões no dual. Portanto, é importante ressaltar que funciona como você espera ... exceto quando não;)
Christopher Schultz
52

Você também pode usar o INSERT IGNOREque ignora silenciosamente a inserção em vez de atualizar ou inserir uma linha quando você tem um índice exclusivo ativado (usuário, item).

A consulta será semelhante a esta:

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

Você pode adicionar o índice exclusivo com CREATE UNIQUE INDEX user_item ON x_table (user, item).

MrD
fonte
2
De acordo com a documentação do MySQL, INSERT IGNORE é a contraparte de REPLACE ... INSERT IGNORE: mantém as linhas antigas. REPLACE: manter novas linhas. Ambos operam em teclas exclusivas.
Paul Kenjora
A melhor resposta de todas
jmojico
30

Você já tentou algo assim?

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;
têmpora
fonte
Ai sim ! solução inteligente e agradável com apenas uma instrução SELECT
java acm
Agradável. Isso funciona para o postgresql na primeira tentativa, onde a resposta aceita não pareceu funcionar para mim.
mightypile,
10

Com um UNIQUE(user, item), faça:

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

o user=123bit é "no-op" para corresponder à sintaxe da ON DUPLICATEcláusula sem realmente fazer nada quando há duplicatas.

Alex Martelli
fonte
1
Não consigo configurar um Unique (usuário, item) porque pode haver instâncias quando várias instâncias com o mesmo usuário e item ... mas não quando estou fazendo esta inserção.
The Unknown
2

Caso você não queira definir uma restrição exclusiva, isso funciona perfeitamente:

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

Espero que ajude !

Gew
fonte
2

O que você quer é INSERT INTO table (...) SELECT ... WHERE .... do manual do MySQL 5.6 .

No seu caso é:

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 talvez, já que você não está usando nenhuma lógica complicada para determinar se deve INSERTou não executar o, você poderia apenas definir uma UNIQUEchave na combinação dessas duas colunas e, em seguida, usar INSERT IGNORE.

Martin
fonte
Você executou sua resposta? Eu continuo recebendo 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...(executando 5.6.34)
hlin117
Eu acho que você precisa dizer from dualantes where.
hlin117 de
@ hlin117 No MariaDB FROM DUALé o padrão se nenhuma tabela for referenciada ( consulte esta documentação ).
Yeti
1

Se você adicionar uma restrição que (x_table.user, x_table.item) seja única, a inserção de outra linha com o mesmo usuário e item falhará.

por exemplo:

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
fonte
Isso não está certo. Pode haver várias linhas com o mesmo usuário, mas não várias linhas com um par de item de usuário.
Matthew Flaschen
Sim Sim. Eu interpretei mal. Editado para fazer a restrição no par.
NickZoic
Bom comentário ... eu executo a consulta em PHP usando mysql_query ("INSERT") e deixo a parte or morrer, portanto, ela dosent me avisa sobre o erro, então eu não tenho que fazer nenhuma verificação
Angel.King.47
1

Embora seja bom verificar se há duplicação antes de inserir seus dados, sugiro que você coloque uma restrição / índice exclusivo em suas colunas para que nenhum dado duplicado possa ser inserido por engano.

Beatles1692
fonte
1

Você pode usar a seguinte solução para resolver seu 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
fonte
Embora este código possa responder à pergunta, fornecer contexto adicional sobre como e / ou por que ele resolve o problema melhoraria o valor da resposta a longo prazo.
Nic3500 de
0

Ligeira modificação na resposta de Alex, você também pode apenas fazer referência ao valor da coluna existente:

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

Então, este significa 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
fonte
-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);

A sintaxe pode variar dependendo do seu banco de dados ...

Rick J
fonte
2
Para que serve isso? MySQL?
Umair A.