MySQL DELETE FROM com subconsulta como condição

87

Estou tentando fazer uma consulta como esta:

DELETE FROM term_hierarchy AS th
WHERE th.parent = 1015 AND th.tid IN (
    SELECT DISTINCT(th1.tid)
    FROM term_hierarchy AS th1
    INNER JOIN term_hierarchy AS th2 ON (th1.tid = th2.tid AND th2.parent != 1015)
    WHERE th1.parent = 1015
);

Como você provavelmente pode dizer, quero excluir a relação dos pais com 1015 se o mesmo tid tiver outros pais. No entanto, isso me resulta em um erro de sintaxe:

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 'AS th
WHERE th.parent = 1015 AND th.tid IN (
  SELECT DISTINCT(th1.tid)
  FROM ter' at line 1

Eu verifiquei a documentação e executei a subconsulta por si só, e parece que tudo deu certo. Alguém consegue descobrir o que está errado aqui?

Atualização : conforme respondido abaixo, o MySQL não permite que a tabela da qual você está excluindo seja usada em uma subconsulta para a condição.

Mikl
fonte
3
Atenção : Boa resposta na parte inferior stackoverflow.com/a/4471359/956397 simplesmente adicione o alias da tabela apósDELETE t FROM table t ...
PiTheNumber

Respostas:

40

Você não pode especificar a tabela de destino para exclusão.

Uma solução alternativa

create table term_hierarchy_backup (tid int(10)); <- check data type

insert into term_hierarchy_backup 
SELECT DISTINCT(th1.tid)
FROM term_hierarchy AS th1
INNER JOIN term_hierarchy AS th2 ON (th1.tid = th2.tid AND th2.parent != 1015)
WHERE th1.parent = 1015;

DELETE FROM term_hierarchy AS th
WHERE th.parent = 1015 AND th.tid IN (select tid from term_hierarchy_backup);
ajreal
fonte
estamos ambos certos - veja seu comentário à minha resposta abaixo. A sintaxe e a lógica do alias eram os dois problemas :)
JNK
Sim, parece que a exclusão via subconsulta não é possível no MySQL - obrigado por dar uma olhada nisso :)
mikl
"DELETE FROM term_hierarchy AS th" na última linha não tem o mesmo problema? Recebo um erro de sintaxe igual ao OP.
malhal
Você deve adicionar Index a term_hierarchy_backup.tid.
Roman Newaza
1
Posso verificar que não é possível em 2019 no MariaDB 10.3.14 ou no MySQL Community Server 5.7.27
alpham8
291

Para outros que encontram esta questão procurando excluir ao usar uma subconsulta, deixo este exemplo para superar o MySQL (mesmo que algumas pessoas pareçam pensar que isso não pode ser feito):

DELETE e.*
FROM tableE e
WHERE id IN (SELECT id
             FROM tableE
             WHERE arg = 1 AND foo = 'bar');

apresentará um erro:

ERROR 1093 (HY000): You can't specify target table 'e' for update in FROM clause

No entanto, esta consulta:

DELETE e.*
FROM tableE e
WHERE id IN (SELECT id
             FROM (SELECT id
                   FROM tableE
                   WHERE arg = 1 AND foo = 'bar') x);

vai funcionar bem:

Query OK, 1 row affected (3.91 sec)

Envolva sua subconsulta em uma subconsulta adicional (aqui denominada x) e o MySQL fará o que você pedir.

CodeReaper
fonte
10
Demorou, mas comecei a trabalhar. Importante: 1) A primeira tabela deve ter o alias mostrado aqui com "e", 2) o "x" no final não é um espaço reservado, é o alias para a tabela temporária produzida pela subconsulta "(SELECT id FROM tableE ONDE arg = 1 AND foo = 'bar') ".
Tilman Hausherr
3
Por que isso funciona? Isso muda muito para mim, mas, além disso, não deve funcionar. Ele faz o trabalho, mas não deveria.
donatJ
1
inacreditável. isso realmente funciona! mas você não é forçado a usar o apelido da tabela e ... você pode usar qualquer apelido que desejar.
Andrei Sandulescu
1
@jakabadambalazs Meu raciocínio quando surgiu com isso, foi que a subconsulta começando com "SELECT id" termina e retorna uma lista de ids e, portanto, libera o bloqueio da tabela que você deseja excluir.
CodeReaper de
9
@jakabadambalazs: Não podemos usar a mesma tabela ( e) em um DELETE e em seu sub-SELECT. Nós podemos , no entanto usar uma sub-sub-SELECT para criar uma tabela temporária ( x) e uso que para a sub-SELECT.
Steve Almond,
40

O alias deve ser incluído após a DELETEpalavra-chave:

DELETE th
FROM term_hierarchy AS th
WHERE th.parent = 1015 AND th.tid IN 
(
    SELECT DISTINCT(th1.tid)
    FROM term_hierarchy AS th1
    INNER JOIN term_hierarchy AS th2 ON (th1.tid = th2.tid AND th2.parent != 1015)
    WHERE th1.parent = 1015
);
James Wiseman
fonte
3
Esta é uma boa resposta. O aliasing adequado irá percorrer um longo caminho para resolver problemas semelhantes aos da postagem original. (como o meu.)
usumoio
11

Você precisa se referir ao alias novamente na instrução delete, como:

DELETE th FROM term_hierarchy AS th
....

Conforme descrito aqui na documentação do MySQL.

JNK
fonte
não se trata de alias, verifique o OP novamente
ajreal
@ajreal - Sim, e observe que o erro começa na definição do alias e a documentação do MySQL afirma explicitamente que você precisa usar o alias na instrução DELETE, bem como na cláusula FROM. Obrigado pelo voto negativo, no entanto.
JNK
simplesmente faça isso delete from your_table as t1 where t1.id in(select t2.id from your_table t2);o que você conseguiu?
ajreal
4
A documentação afirma claramente; Currently, you cannot delete from a table and select from the same table in a subquery. dev.mysql.com/doc/refman/5.5/en/delete.html
Björn
1
você não precisa corrigir o alias, apenas não especifique a tabela de destino para selecionar na exclusão ... este é o verdadeiro problema
ajreal
7

Abordei isso de uma maneira um pouco diferente e funcionou para mim;

Eu precisava remover secure_linksda minha tabela que fazia referência à conditionstabela onde não havia mais linhas de condição restantes. Um script de manutenção basicamente. Isso me deu o erro - Você não pode especificar a tabela de destino para exclusão.

Procurando aqui por inspiração, eu vim com a consulta abaixo e ela funciona muito bem. Isso ocorre porque ele cria uma tabela temporária sl1que é usada como referência para o DELETE.

DELETE FROM `secure_links` WHERE `secure_links`.`link_id` IN 
            (
            SELECT
                `sl1`.`link_id` 
            FROM 
                (
                SELECT 

                    `sl2`.`link_id` 

                FROM 
                    `secure_links` AS `sl2` 
                    LEFT JOIN `conditions` ON `conditions`.`job` = `sl2`.`job` 

                WHERE 

                    `sl2`.`action` = 'something' AND 
                    `conditions`.`ref` IS NULL 
                ) AS `sl1`
            )

Funciona para mim.

Darren Edwards
fonte
Duplicado daquele do @CodeReaper acima ... boa chamada, tho ...;)
jrypkahauer
5

A cláusula "in" na exclusão não é ... onde, extremamente ineficiente, se houver um grande número de valores retornados da subconsulta? Não tenho certeza por que você não faria apenas a junção interna (ou direita) de volta à tabela original da subconsulta no ID para excluir, em vez de usar o "in (subconsulta)".?

DELETE T FROM Target AS T
RIGHT JOIN (full subquery already listed for the in() clause in answers above) ` AS TT ON (TT.ID = T.ID)

E talvez seja respondido no "MySQL não permite", no entanto, está funcionando bem para mim, desde que eu tenha certeza de esclarecer completamente o que excluir (DELETE T FROM Target AS T). Excluir com Join no MySQL esclarece o problema DELETE / JOIN.

Jeff
fonte
2

Se você quiser fazer isso com 2 consultas, você sempre pode fazer algo semelhante a isto:

1) pegar ids da mesa com:

SELECT group_concat(id) as csv_result FROM your_table WHERE whatever = 'test' ...

Em seguida, copie o resultado com mouse / teclado ou linguagem de programação para XXX abaixo:

2) DELETE FROM your_table WHERE id IN ( XXX )

Talvez você pudesse fazer isso em uma consulta, mas é o que eu prefiro.

TomoMiha
fonte
0

@CodeReaper, @BennyHill: Funciona conforme o esperado.

No entanto, eu me pergunto a complexidade de tempo para ter milhões de linhas na tabela? Aparentemente, demorou 5mspara ser executado por ter 5k registros em uma tabela indexada corretamente.

Minha consulta:

SET status = '1'
WHERE id IN (
    SELECT id
    FROM (
      SELECT c2.id FROM clusters as c2
      WHERE c2.assign_to_user_id IS NOT NULL
        AND c2.id NOT IN (
         SELECT c1.id FROM clusters AS c1
           LEFT JOIN cluster_flags as cf on c1.last_flag_id = cf.id
           LEFT JOIN flag_types as ft on ft.id = cf.flag_type_id
         WHERE ft.slug = 'closed'
         )
      ) x)```

Or is there something we can improve on my query above?
rc.adhikari
fonte
0

você pode usar o alias desta forma na instrução delete

DELETE  th.*
FROM term_hierarchy th
INNER JOIN term_hierarchy th2 ON (th1.tid = th2.tid AND th2.parent != 1015)
WHERE th.parent = 1015;
YakovGdl35
fonte