Error 1093 de MySQL: no se puede especificar la tabla de destino para la actualización en la cláusula FROM

594

Tengo una tabla story_categoryen mi base de datos con entradas corruptas. La siguiente consulta devuelve las entradas corruptas:

SELECT * 
FROM  story_category 
WHERE category_id NOT IN (
    SELECT DISTINCT category.id 
    FROM category INNER JOIN 
       story_category ON category_id=category.id);

Traté de eliminarlos ejecutando:

DELETE FROM story_category 
WHERE category_id NOT IN (
    SELECT DISTINCT category.id 
    FROM category 
      INNER JOIN story_category ON category_id=category.id);

Pero me sale el siguiente error:

# 1093 - No se puede especificar la tabla de destino 'story_category' para actualizar en la cláusula FROM

¿Cómo puedo superar esto?

Sergio del Amo
fuente
3
Relacionado: stackoverflow.com/a/14302701/238419
BlueRaja - Danny Pflughoeft
2
Parece que la solicitud de función en el rastreador de errores de MySQL está aquí: no se puede actualizar una tabla y seleccionar de la misma tabla en una subconsulta
Ben Creasy

Respuestas:

714

Actualización: esta respuesta cubre la clasificación de error general. Para obtener una respuesta más específica sobre cómo manejar mejor la consulta exacta del OP, consulte otras respuestas a esta pregunta

En MySQL, no puede modificar la misma tabla que usa en la parte SELECT.
Este comportamiento está documentado en: http://dev.mysql.com/doc/refman/5.6/en/update.html

Quizás puedas unirte a la mesa

Si la lógica es lo suficientemente simple como para cambiar la forma de la consulta, pierda la subconsulta y una la tabla a sí misma, empleando los criterios de selección adecuados. Esto hará que MySQL vea la tabla como dos cosas diferentes, permitiendo que se produzcan cambios destructivos.

UPDATE tbl AS a
INNER JOIN tbl AS b ON ....
SET a.col = b.col

Alternativamente, intente anidar la subconsulta más profundamente en una cláusula from ...

Si realmente necesita la subconsulta, hay una solución alternativa, pero es fea por varias razones, incluido el rendimiento:

UPDATE tbl SET col = (
  SELECT ... FROM (SELECT.... FROM) AS x);

La subconsulta anidada en la cláusula FROM crea una tabla temporal implícita , por lo que no cuenta como la misma tabla que está actualizando.

... pero ten cuidado con el optimizador de consultas

Sin embargo, tenga en cuenta que desde MySQL 5.7.6 y posteriores, el optimizador puede optimizar la subconsulta y aún así le dará el error. Afortunadamente, la optimizer_switchvariable se puede usar para desactivar este comportamiento; aunque no podría recomendar hacer esto como algo más que una solución a corto plazo, o para pequeñas tareas puntuales.

SET optimizer_switch = 'derived_merge=off';

Gracias a Peter V. Mørch por este consejo en los comentarios.

La técnica de ejemplo fue del barón Schwartz, originalmente publicado en Nabble , parafraseado y extendido aquí.

Cheekysoft
fuente
1
Elevé esta respuesta porque tuve que eliminar elementos y no pude obtener información de otra tabla, tuve que realizar una subconsulta desde la misma tabla. Dado que esto es lo que aparece en la parte superior mientras busco en Google el error que obtuve, esta sería la mejor respuesta para mí y para muchas personas que intentan actualizar mientras consultan desde la misma tabla.
HMR
2
@Cheekysoft, ¿por qué no guardar los valores en variables?
Pacerier
19
Tenga en cuenta que desde MySQL 5.7.6 en adelante , el optimizador puede optimizar la subconsulta y aún así le dará el error, a menos que usted SET optimizer_switch = 'derived_merge=off';:-(
Peter V. Mørch
1
@ PeterV.Mørch Siguiendo mysqlserverteam.com/derived-tables-in-mysql-5-7 , en caso de que se realicen ciertas operaciones, la fusión no puede ocurrir. Por ejemplo, proporcione a la tabla ficticia derivada un LÍMITE (hasta el inicio) y el error nunca ocurrirá. Sin embargo, eso es bastante hack y todavía existe el riesgo de que las futuras versiones de MySQL admitan la fusión de consultas con LIMIT después de todo.
user2180613
¿Puede, por favor, proporcionar un ejemplo completo sobre esta solución? ACTUALIZAR tbl SET col = (SELECCIONAR ... DESDE (SELECCIONAR .... DESDE) COMO x); sigo recibiendo errores
JoelBonetR
310

NexusRex proporcionó una muy buena solución para eliminar con join de la misma tabla.

Si haces esto:

DELETE FROM story_category
WHERE category_id NOT IN (
        SELECT DISTINCT category.id AS cid FROM category 
        INNER JOIN story_category ON category_id=category.id
)

vas a obtener un error

Pero si envuelve la condición en una selección más:

DELETE FROM story_category
WHERE category_id NOT IN (
    SELECT cid FROM (
        SELECT DISTINCT category.id AS cid FROM category 
        INNER JOIN story_category ON category_id=category.id
    ) AS c
)

¡Haría lo correcto!

Explicación: El optimizador de consultas realiza una optimización de fusión derivada para la primera consulta (lo que hace que falle con el error), pero la segunda consulta no califica para la optimización de fusión derivada . Por lo tanto, el optimizador se ve obligado a ejecutar la subconsulta primero.

Ekonoval
fuente
66
Tal vez sea porque hoy estoy en apuros, pero esta fue la respuesta más fácil, incluso si tal vez no es la "mejor".
Tyler V.
44
Esto funcionó muy bien, gracias! Entonces, ¿cuál es la lógica aquí? Si está anidado un nivel más, ¿se ejecutará antes de la parte exterior? ¿Y si no está anidado, entonces mySQL intenta ejecutarlo después de que la eliminación tiene un bloqueo en la tabla?
Flat Cat
43
Este error y esta solución no tienen sentido lógico ... pero funciona. A veces me pregunto qué drogas toman los desarrolladores de MySQL ...
Cerin
1
De acuerdo con @Cerin ... esto es completamente absurdo, pero funciona.
FastTrack
@ekonoval Gracias por la solución, pero no tiene el mínimo sentido para mí, parece que estás engañando a MySQL y él lo acepta, lol
deFreitas
106

El inner joinen su subconsulta es innecesario. Parece que desea eliminar las entradas en story_categorydonde category_idno está en la categorytabla.

Hacer esto:

DELETE FROM story_category 
WHERE category_id NOT IN (
    SELECT DISTINCT category.id 
    FROM category);

En lugar de eso:

DELETE FROM story_category 
WHERE category_id NOT IN (
    SELECT DISTINCT category.id 
    FROM category INNER JOIN
         story_category ON category_id=category.id);
shA.t
fuente
55
¡Esta debería ser la mejor respuesta! Tal vez elimine el primer "en lugar de".
hoyhoy
1
Creo que DISTINCTes innecesario aquí, para un mejor rendimiento;).
shA.t
1
Debo estar loco. La respuesta es la misma que se indicó en el original.
Jeff Lowery
Esta es la respuesta para mí también. No lo haga where inen la columna ID, por lo que no necesita consultar la tabla primaria.
Richard
@JeffLowery: el primer bloque de código es la respuesta aquí; es el segundo bloque de código que proviene de la pregunta.
ToolmakerSteve
95

Recientemente tuve que actualizar registros en la misma tabla que hice a continuación:

UPDATE skills AS s, (SELECT id  FROM skills WHERE type = 'Programming') AS p
SET s.type = 'Development' 
WHERE s.id = p.id;
Pratik Khadloya
fuente
11
¿No puede esto simplemente escribirse como UPDATE skills SET type='Development' WHERE type='Programming';? Esto no parece estar respondiendo la pregunta original.
lilbyrdie
1
Parece excesivo, @lilbyrdie es correcto, solo podría serlo UPDATE skills SET type='Development' WHERE type='Programming';. No entiendo por qué tanta gente no está pensando en lo que hacen ...
shadyyx
1
Esta es la mejor respuesta aquí, en mi humilde opinión. Su sintaxis es fácil de entender, puede reutilizar su declaración anterior y no se limita a un caso súper específico.
Steffen Winkler
2
Incluso si el caso de ejemplo es cuestionable, el principio es el mejor para comprender y desplegar en escenarios del mundo real. La consulta que se muestra funciona porque la unión implícita en la lista de tablas crea una tabla temporal para una subconsulta, lo que proporciona resultados estables y evita el choque entre la recuperación y la modificación de la misma tabla.
AnrDaemon
1
independientemente de lo que alguien pueda decir sobre esta exageración, aún responde a la pregunta en términos del título. El error que mencionó el OP también se encuentra cuando se intenta una actualización utilizando la misma tabla en una consulta anidada
smac89
35
DELETE FROM story_category
WHERE category_id NOT IN (
    SELECT cid FROM (
        SELECT DISTINCT category.id AS cid FROM category INNER JOIN story_category ON category_id=category.id
    ) AS c
)
NexusRex
fuente
3
¿Puedes explicar por qué esto funciona, por qué solo anidar un nivel más funciona? Esta pregunta ya se ha formulado como un comentario a la pregunta de @ EkoNoval, pero nadie respondió. Quizás puedas ayudar.
Akshay Arora
@AkshayArora, ve a través de la parte bajo el encabezado 'Quizás puedas unir la mesa a sí mismo' en la respuesta de @ Cheekysoft. Él dijo UPDATE tbl AS a INNER JOIN tbl AS b ON .... SET a.col = b.col: esto funcionaría como un alias diferente para la misma tabla que se usa aquí. De manera similar, en la respuesta de @ NexusRex, la primera SELECTconsulta actúa como una tabla derivada en la que story_categoryse usa por segunda vez. Entonces, el error mencionado en el OP no debería tener lugar aquí, ¿verdad?
Istiaque Ahmed
31

Si no puedes hacer

UPDATE table SET a=value WHERE x IN
    (SELECT x FROM table WHERE condition);

porque es la misma tabla, puedes engañar y hacer:

UPDATE table SET a=value WHERE x IN
    (SELECT * FROM (SELECT x FROM table WHERE condition) as t)

[actualizar o eliminar o lo que sea]

Sequoya
fuente
La implementación más comprensible y lógica de todas las respuestas anteriores. Simple y al grano.
Clain Dsilva el
Esto se ha convertido en parte de mi flujo de trabajo ahora. Atascarse en este error, diríjase a esta respuesta y rectifique la consulta. Gracias.
Vaibhav
13

Esto es lo que hice para actualizar un valor de columna de Prioridad en 1 si es> = 1 en una tabla y en su cláusula WHERE utilizando una subconsulta en la misma tabla para asegurarse de que al menos una fila contenga Prioridad = 1 (porque esa fue la condición a comprobar mientras se realiza la actualización):


UPDATE My_Table
SET Priority=Priority + 1
WHERE Priority >= 1
AND (SELECT TRUE FROM (SELECT * FROM My_Table WHERE Priority=1 LIMIT 1) as t);

Sé que es un poco feo, pero funciona bien.

sactiw
fuente
1
@anonymous_reviewer: En caso de dar [-1] o incluso [+1] al comentario de alguien, mencione también por qué lo ha hecho. ¡¡¡Gracias!!!
sactiw
1
-1 porque esto es incorrecto. No puede modificar la misma tabla que usa en la instrucción SELECT.
Chris
1
@Chris Lo he verificado en MySQL y funciona bien para mí, por lo que le pediría que lo verifique al final y luego afirme que es correcto o incorrecto. ¡¡¡Gracias!!!
Sactiw
1
Al final de esta página dice "Actualmente, no puede actualizar una tabla y seleccionarla desde una misma tabla en una subconsulta". - Y he experimentado que esto es cierto en muchas ocasiones. dev.mysql.com/doc/refman/5.0/en/update.html
Chris
19
@ Chris Lo sé, pero hay una solución para eso y que es exactamente lo que he intentado mostrar con mi consulta 'ACTUALIZAR' y créanme que funciona bien. No creo que haya intentado realmente verificar mi consulta en absoluto.
Sactiw
6

La forma más sencilla de hacer esto es usar un alias de tabla cuando se refiere a una tabla de consulta principal dentro de la subconsulta.

Ejemplo:

insert into xxx_tab (trans_id) values ((select max(trans_id)+1 from xxx_tab));

Cámbielo a:

insert into xxx_tab (trans_id) values ((select max(P.trans_id)+1 from xxx_tab P));
S.Roshanth
fuente
5

Puede insertar los identificadores de las filas deseadas en una tabla temporal y luego eliminar todas las filas que se encuentran en esa tabla.

que puede ser lo que @Cheekysoft quiso decir al hacerlo en dos pasos.

YonahW
fuente
3

De acuerdo con la sintaxis de ACTUALIZACIÓN de Mysql vinculada por @CheekySoft, dice justo en la parte inferior.

Actualmente, no puede actualizar una tabla y seleccionar de la misma tabla en una subconsulta.

Supongo que está eliminando de store_category mientras sigue seleccionando en la unión.

briankip
fuente
3

Para la consulta específica que el OP está tratando de lograr, la forma ideal y más eficiente de hacer esto NO es utilizar una subconsulta.

Estas son las LEFT JOINversiones de las dos consultas del OP:

SELECT s.* 
FROM story_category s 
LEFT JOIN category c 
ON c.id=s.category_id 
WHERE c.id IS NULL;

Nota: DELETE srestringe las operaciones de eliminación a la story_categorytabla.
Documentación

DELETE s 
FROM story_category s 
LEFT JOIN category c 
ON c.id=s.category_id 
WHERE c.id IS NULL;
Doin
fuente
1
Sorprendido, esto no tiene más votos positivos. También se debe tener en cuenta que la sintaxis de varias tablas también funciona con UPDATEdeclaraciones y subconsultas unidas. Permitiéndole rendir LEFT JOIN ( SELECT ... )en lugar de hacerlo WHERE IN( SELECT ... ), haciendo que la implementación sea útil en muchos casos de uso.
fyrye
2

Si algo no funciona, al pasar por la puerta principal, tome la puerta trasera:

drop table if exists apples;
create table if not exists apples(variety char(10) primary key, price int);

insert into apples values('fuji', 5), ('gala', 6);

drop table if exists apples_new;
create table if not exists apples_new like apples;
insert into apples_new select * from apples;

update apples_new
    set price = (select price from apples where variety = 'gala')
    where variety = 'fuji';
rename table apples to apples_orig;
rename table apples_new to apples;
drop table apples_orig;

Es rápido. Cuanto más grandes sean los datos, mejor.

Tom Schaefer
fuente
11
Y acaba de perder todas sus claves foráneas, y quizás también tenga algunas eliminaciones en cascada.
Walf
2

Intente guardar el resultado de la instrucción Select en una variable separada y luego utilícelo para eliminar la consulta.

lipika chakraborty
fuente
2

prueba esto

DELETE FROM story_category 
WHERE category_id NOT IN (
SELECT DISTINCT category.id 
FROM (SELECT * FROM STORY_CATEGORY) sc;
Sameer Khanal
fuente
1

¿qué tal esta consulta espero que ayude

DELETE FROM story_category LEFT JOIN (SELECT category.id FROM category) cat ON story_category.id = cat.id WHERE cat.id IS NULL
Melvin Angelo Jabonillo
fuente
El resultado muestra: '# 1064 - Tiene un error en su sintaxis SQL; consulte el manual que corresponde a la versión de su servidor MariaDB para obtener la sintaxis correcta para usar cerca de 'LEFT JOIN (SELECT categories.id FROM categories) cat ON story_category.id = cat'. en la línea 1 '
Istiaque Ahmed
Cuando utilice la eliminación de varias tablas, debe especificar las tablas afectadas. DELETE story_category FROM ...sin embargo, la subconsulta unida no es necesaria en este contexto y se puede realizar utilizando LEFT JOIN category AS cat ON cat.id = story_category.category_id WHERE cat.id IS NULL, tenga en cuenta que los criterios de unión en la respuesta hacen referencia incorrectamentestory_category.id = cat.id
fyrye
0

En lo que respecta a las preocupaciones, desea eliminar filas story_categoryque no existen en category.

Aquí está su consulta original para identificar las filas a eliminar:

SELECT * 
FROM  story_category 
WHERE category_id NOT IN (
    SELECT DISTINCT category.id 
    FROM category INNER JOIN 
       story_category ON category_id=category.id
);

La combinación NOT INcon una subconsulta que JOINes la tabla original parece complicada de manera innecesaria. Esto se puede expresar de una manera más directa con not existsuna subconsulta correlacionada:

select sc.*
from story_category sc
where not exists (select 1 from category c where c.id = sc.category_id);

Ahora es fácil convertir esto en una deletedeclaración:

delete from story_category
where not exists (select 1 from category c where c.id = story_category.category_id);    

Este quer se ejecutaría en cualquier versión de MySQL, así como en la mayoría de las otras bases de datos que conozco.

Demostración en DB Fiddle :

-- set-up
create table story_category(category_id int);
create table category (id int);
insert into story_category values (1), (2), (3), (4), (5);
insert into category values (4), (5), (6), (7);

-- your original query to identify offending rows
SELECT * 
FROM  story_category 
WHERE category_id NOT IN (
    SELECT DISTINCT category.id 
    FROM category INNER JOIN 
       story_category ON category_id=category.id);
El | category_id |
El | ----------: |
El | 1 |
El | 2 |
El | 3 |
-- a functionally-equivalent, simpler query for this
select sc.*
from story_category sc
where not exists (select 1 from category c where c.id = sc.category_id)
El | category_id |
El | ----------: |
El | 1 |
El | 2 |
El | 3 |
-- the delete query
delete from story_category
where not exists (select 1 from category c where c.id = story_category.category_id);

-- outcome
select * from story_category;
El | category_id |
El | ----------: |
El | 4 |
El | 5 |
GMB
fuente