MySQL DELETE FROM con subconsulta como condición

85

Estoy tratando de hacer una 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 probablemente pueda ver, quiero eliminar la relación principal con 1015 si el mismo tid tiene otros padres. Sin embargo, eso me produce un error de sintaxis:

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

Verifiqué la documentación y ejecuté la subconsulta por sí misma, y ​​todo parece verificar. ¿Alguien puede averiguar qué está mal aquí?

Actualización : como se responde a continuación, MySQL no permite que la tabla de la que está eliminando se use en una subconsulta para la condición.

mikl
fuente
2
Atención : buena respuesta en la parte inferior stackoverflow.com/a/4471359/956397 simplemente agregue el alias de la tabla después deDELETE t FROM table t ...
PiTheNumber

Respuestas:

38

No puede especificar la tabla de destino para eliminar.

Una solución

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
fuente
ambos tenemos razón: vea su comentario a mi respuesta a continuación. La sintaxis y la lógica del alias eran problemas :)
JNK
Sí, parece que la eliminación a través de subconsultas no es posible actualmente en MySQL; gracias por echarle un vistazo :)
mikl
¿No tiene el mismo problema el "DELETE FROM term_hierarchy AS th" en esa última línea? Recibo un error de sintaxis igual que el OP.
Malhal
Debe agregar Index a term_hierarchy_backup.tid.
Roman Newaza
1
Puedo verificar que no es posible en 2019 en MariaDB 10.3.14 o MySQL Community Server 5.7.27
alpham8
283

Para otros que encuentran esta pregunta buscando eliminar mientras usan una subconsulta, les dejo este ejemplo para burlar a MySQL (incluso si algunas personas parecen pensar que no se puede hacer):

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

te dará un error:

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

Sin embargo esta consulta:

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

funcionará bien:

Query OK, 1 row affected (3.91 sec)

Envuelva su subconsulta en una subconsulta adicional (aquí llamada x) y MySQL hará felizmente lo que le pida.

CodeReaper
fuente
10
Tomó algo de tiempo, pero lo logré funcionar. Importante: 1) La primera tabla debe tener un alias como se muestra aquí con "e", 2) la "x" al final no es un marcador de posición, es el alias de la tabla temporal producida por la subconsulta "(SELECT id FROM tableE DONDE arg = 1 AND foo = 'bar') ".
Tilman Hausherr
3
¿Por qué funciona esto? Esto cambia mucho para mí, pero además, no debería funcionar. Se hace el trabajo, pero no debería.
donatJ
1
increíble. ¡esto realmente funciona! pero no está obligado a asignar un alias a la tabla con e ... puede usar cualquier alias que desee.
Andrei Sandulescu
1
@jakabadambalazs Mi razonamiento cuando se le ocurrió fue que la subconsulta que comienza con "SELECT id" termina y devuelve una lista de identificadores y, por lo tanto, libera el bloqueo de la tabla de la que desea eliminar.
CodeReaper
9
@jakabadambalazs: No podemos usar la misma tabla ( e) en un DELETE y en su sub-SELECT. Nosotros podemos , sin embargo, utilizar un sub-sub-SELECT para crear una tabla temporal ( x), y el uso que para el sub-SELECT.
Steve Almond
39

El alias debe incluirse después de la DELETEpalabra clave:

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
fuente
3
Esta es una buena respuesta. El aliasing adecuado será de gran ayuda para resolver problemas similares a los de la publicación original. (como el mío)
usumoio
10

Debe hacer referencia al alias nuevamente en la declaración de eliminación, como:

DELETE th FROM term_hierarchy AS th
....

Como se describe aquí en los documentos de MySQL.

JNK
fuente
no se trata de alias, por favor revise el OP nuevamente
ajreal
@ajreal: lo hice, y tenga en cuenta que el error comienza en la definición del alias, y la documentación de MySQL indica explícitamente que debe usar el alias en la declaración DELETE, así como en la cláusula FROM. Sin embargo, gracias por el voto negativo.
JNK
simplemente haz esto, delete from your_table as t1 where t1.id in(select t2.id from your_table t2);¿qué obtuviste?
ajreal
4
La documentación dice 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
no tiene que arreglar el alias, simplemente no especifique la tabla de destino para seleccionar en eliminar ... este es el problema real
ajreal
7

Me acerqué a esto de una manera ligeramente diferente y funcionó para mí;

Necesitaba eliminar secure_linksde mi tabla que hacía referencia a la conditionstabla donde ya no quedaban filas de condición. Básicamente, un guión de limpieza. Esto me dio el error: no puede especificar la tabla de destino para eliminar.

Entonces, buscando inspiración aquí, se me ocurrió la siguiente consulta y funciona bien. Esto se debe a que crea una tabla temporal sl1que se usa como referencia para 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 mi.

Darren Edwards
fuente
5

¿No es la cláusula "in" en la eliminación ... donde, extremadamente ineficaz, si va a haber una gran cantidad de valores devueltos desde la subconsulta? No estoy seguro de por qué no se uniría simplemente interno (o derecho) contra la tabla original de la subconsulta en el ID para eliminar, en lugar de nosotros el "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)

Y tal vez se responda en "MySQL no lo permite", sin embargo, funciona bien para mí SIEMPRE que me asegure de aclarar completamente qué eliminar (ELIMINAR T DE Target AS T). Eliminar con Join en MySQL aclara el problema DELETE / JOIN.

Jeff
fuente
2

Si desea hacer esto con 2 consultas, siempre puede hacer algo similar a esto:

1) agarre los identificadores de la mesa con:

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

Luego copie el resultado con el mouse / teclado o el lenguaje de programación a XXX a continuación:

2) DELETE FROM your_table WHERE id IN ( XXX )

Tal vez podrías hacer esto en una consulta, pero esto es lo que prefiero.

TomoMiha
fuente
0

@CodeReaper, @BennyHill: Funciona como se esperaba.

Sin embargo, me pregunto cuál es la complejidad del tiempo para tener millones de filas en la tabla. Aparentemente, tardó 5msen ejecutarse para tener 5k registros en una tabla indexada correctamente.

Mi 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
fuente
0

puede usar el alias de esta manera en la declaración de eliminación

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
fuente