No puede especificar la tabla de destino para la actualización en la cláusula FROM

382

Tengo una tabla mysql simple:

CREATE TABLE IF NOT EXISTS `pers` (
  `persID` int(11) NOT NULL AUTO_INCREMENT,
  `name` varchar(35) NOT NULL,
  `gehalt` int(11) NOT NULL,
  `chefID` int(11) DEFAULT NULL,
  PRIMARY KEY (`persID`)
) ENGINE=MyISAM  DEFAULT CHARSET=latin1 AUTO_INCREMENT=4 ;

INSERT INTO `pers` (`persID`, `name`, `gehalt`, `chefID`) VALUES
(1, 'blb', 1000, 3),
(2, 'as', 1000, 3),
(3, 'chef', 1040, NULL);

Intenté ejecutar la siguiente actualización, pero solo aparece el error 1093:

UPDATE pers P 
SET P.gehalt = P.gehalt * 1.05 
WHERE (P.chefID IS NOT NULL 
OR gehalt < 
(SELECT (
    SELECT MAX(gehalt * 1.05) 
    FROM pers MA 
    WHERE MA.chefID = MA.chefID) 
    AS _pers
))

Busqué el error y lo encontré en la siguiente página de mysql http://dev.mysql.com/doc/refman/5.1/en/subquery-restrictions.html , pero no me ayuda.

¿Qué debo hacer para corregir la consulta SQL?

CSchulz
fuente
1
Posible duplicado del error 1093
Steve Chambers

Respuestas:

772

El problema es que MySQL, por el motivo que sea, no le permite escribir consultas como esta:

UPDATE myTable
SET myTable.A =
(
    SELECT B
    FROM myTable
    INNER JOIN ...
)

Es decir, si está haciendo un UPDATE/ INSERT/ DELETEen una tabla, no puede hacer referencia a esa tabla en una consulta interna ( sin embargo, puede hacer referencia a un campo de esa tabla externa ...)


La solución es reemplazar la instancia de myTableen la subconsulta con (SELECT * FROM myTable), como este

UPDATE myTable
SET myTable.A =
(
    SELECT B
    FROM (SELECT * FROM myTable) AS something
    INNER JOIN ...
)

Aparentemente, esto hace que los campos necesarios se copien implícitamente en una tabla temporal, por lo que está permitido.

Encontré esta solución aquí . Una nota de ese artículo:

No quieres solo SELECT * FROM tableen la subconsulta en la vida real; Solo quería mantener los ejemplos simples. En realidad, solo debe seleccionar las columnas que necesita en esa consulta más interna y agregar una buena WHEREcláusula para limitar los resultados también.

BlueRaja - Danny Pflughoeft
fuente
10
No creo que la razón sea estúpida. Piensa en la semántica. MySQL tiene que conservar una copia de la tabla antes de que comience la actualización, o la consulta interna puede usar datos que ya han sido actualizados por la consulta mientras está en progreso. Ninguno de estos efectos secundarios es necesariamente deseable, por lo que la apuesta más segura es forzarlo a especificar qué sucederá con una tabla adicional.
siride
35
@siride: Otras bases de datos, como MSSQL u Oracle, no tienen esta restricción arbitraria
BlueRaja - Danny Pflughoeft
3
@ BlueRaja-DannyPflughoeft: no es arbitrario. Es una decisión de diseño razonable basada en los costos de las alternativas. Los otros sistemas de base de datos decidieron lidiar con esos costos de todos modos. Pero esos sistemas, por ejemplo, no le permiten incluir columnas no agregadas en las listas SELECT cuando usa GROUP BY, y MySQL sí. Yo diría que MySQL está equivocado aquí, y podría decir lo mismo de los otros DBMS para las declaraciones de ACTUALIZACIÓN.
siride
33
@siride Desde un punto de vista de álgebra relacional, Ty (SELECT * FROM T)son completamente equivalentes. Son la misma relación. Por lo tanto, esta es una restricción arbitraria e inútil. Más específicamente, es una solución para obligar a MySQL a hacer algo que claramente puede hacer, pero por alguna razón no puede analizar en su forma más simple.
Tobia
44
En mi caso, la solución aceptada no funcionó porque mi tabla era simplemente demasiado grande. La consulta nunca se completó. Aparentemente, esto está tomando demasiados recursos internos. En su lugar, creé una vista con la consulta interna y la usé para la selección de datos, que funcionó absolutamente bien. DELETE FROM t WHERE tableID NOT IN (SELECT viewID FROM t_view);También recomiendo correr OPTIMIZE TABLE t;después para reducir el tamaño de la tabla.
CodeX
53

Puedes hacer esto en tres pasos:

CREATE TABLE test2 AS
SELECT PersId 
FROM pers p
WHERE (
  chefID IS NOT NULL 
  OR gehalt < (
    SELECT MAX (
      gehalt * 1.05
    )
    FROM pers MA
    WHERE MA.chefID = p.chefID
  )
)

...

UPDATE pers P
SET P.gehalt = P.gehalt * 1.05
WHERE PersId
IN (
  SELECT PersId
  FROM test2
)
DROP TABLE test2;

o

UPDATE Pers P, (
  SELECT PersId
  FROM pers p
  WHERE (
   chefID IS NOT NULL 
   OR gehalt < (
     SELECT MAX (
       gehalt * 1.05
     )
     FROM pers MA
     WHERE MA.chefID = p.chefID
   )
 )
) t
SET P.gehalt = P.gehalt * 1.05
WHERE p.PersId = t.PersId
Michael Pakhantsov
fuente
16
Bueno, sí, la mayoría de las subconsultas pueden reescribirse como pasos múltiples con CREATE TABLEdeclaraciones; espero que el autor lo sepa. Sin embargo, ¿es esta la única solución? ¿O se puede reescribir la consulta con subconsultas o combinaciones? ¿Y por qué (no) hacer eso?
Konerak
Creo que tiene un error de capitalización en su segunda solución. ¿No debería UPDATE Pers Pleer UPDATE pers P?
ubiquibacon
2
Probé esta solución y para una gran cantidad de entradas en la tabla temporal / segunda la consulta puede ser muy lenta; intente crear una tabla temporal / segunda con una clave índice / primaria [vea dev.mysql.com/doc/refman/5.1/en/create-table-select.html ]
Alex
Como dice @Konerak, esta no es realmente la mejor respuesta. La respuesta de BlueRaja a continuación me parece mejor. Los votos a favor parecen estar de acuerdo.
ShatyUT
@Konerak, ¿No CREATE TABLE AS SELECTda un desempeño horrible?
Pacerier
27

En Mysql, no puede actualizar una tabla subconsultando la misma tabla.

Puede separar la consulta en dos partes, o hacer

 ACTUALIZAR TABLA_A COMO A
 TABLA DE UNIÓN INTERNA_A COMO B EN A.field1 = B.field1
 SET field2 =? 
Yuantao
fuente
55
SELECT ... SET? Nunca he escuchado sobre esto.
Serge S.
@grisson Gracias por la aclaración. Ahora entiendo por qué mi cláusula IN no funciona: estaba apuntando a la misma tabla.
Anthony
2
... esto no parece funcionar realmente. Todavía me está dando el mismo error.
BlueRaja - Danny Pflughoeft
2
esta respuesta en realidad hace lo más correcto y eficiente, que está utilizando AS Ben la segunda referencia a TABLE_A. la respuesta en el ejemplo más votado podría simplificarse usando en AS Tlugar de la potencialmente ineficiente FROM (SELECT * FROM myTable) AS something, que afortunadamente el optimizador de consultas generalmente elimina pero no siempre puede hacerlo.
natbro
23

Hacer una tabla temporal (tempP) desde una subconsulta

UPDATE pers P 
SET P.gehalt = P.gehalt * 1.05 
WHERE P.persID IN (
    SELECT tempP.tempId
    FROM (
        SELECT persID as tempId
        FROM pers P
        WHERE
            P.chefID IS NOT NULL OR gehalt < 
                (SELECT (
                    SELECT MAX(gehalt * 1.05) 
                    FROM pers MA 
                    WHERE MA.chefID = MA.chefID) 
                    AS _pers
                )
    ) AS tempP
)

He introducido un nombre separado (alias) y le doy un nuevo nombre a la columna 'persID' para la tabla temporal

Budda
fuente
¿Por qué no seleccionar los valores en variables en lugar de hacer selecciones internas internas internas?
Pacerier
SELECT ( SELECT MAX(gehalt * 1.05)..- el primero SELECTno selecciona ninguna columna.
Istiaque Ahmed
18

Es bastante simple. Por ejemplo, en lugar de escribir:

INSERT INTO x (id, parent_id, code) VALUES (
    NULL,
    (SELECT id FROM x WHERE code='AAA'),
    'BBB'
);

Deberías escribir

INSERT INTO x (id, parent_id, code)
VALUES (
    NULL,
    (SELECT t.id FROM (SELECT id, code FROM x) t WHERE t.code='AAA'),
    'BBB'
);

o similar.

Lado oscuro
fuente
13

El Enfoque publicado por BlueRaja es lento. Lo modifiqué porque estaba usando para eliminar duplicados de la tabla. En caso de que ayude a alguien con tablas grandes Consulta original

delete from table where id not in (select min(id) from table group by field 2)

Esto lleva más tiempo:

DELETE FROM table where ID NOT IN(
  SELECT MIN(t.Id) from (select Id,field2 from table) AS t GROUP BY field2)

Solución más rápida

DELETE FROM table where ID NOT IN(
   SELECT x.Id from (SELECT MIN(Id) as Id from table GROUP BY field2) AS t)
Ajak6
fuente
Agregue un comentario si está votando en contra.
Ajak6
3

Si está intentando leer el campo A de la tabla A y guardarlo en el campo B de la misma tabla, cuando fieldc = fieldd puede considerar esto.

UPDATE tableA,
    tableA AS tableA_1 
SET 
    tableA.fieldB= tableA_1.filedA
WHERE
    (((tableA.conditionFild) = 'condition')
        AND ((tableA.fieldc) = tableA_1.fieldd));

El código anterior copia el valor del campo A al campo B cuando condición-campo cumple con su condición. esto también funciona en ADO (por ejemplo, acceso)

fuente: lo intenté

Krish
fuente
3

MariaDB ha levantado esto a partir de 10.3.x (tanto para DELETEcomo UPDATE):

ACTUALIZACIÓN - Declaraciones con la misma fuente y destino

De MariaDB 10.3.2, las declaraciones de ACTUALIZACIÓN pueden tener el mismo origen y destino.

Hasta MariaDB 10.3.1, la siguiente instrucción UPDATE no funcionaría:

UPDATE t1 SET c1=c1+1 WHERE c2=(SELECT MAX(c2) FROM t1);
  ERROR 1093 (HY000): Table 't1' is specified twice, 
  both as a target for 'UPDATE' and as a separate source for data

Desde MariaDB 10.3.2, la declaración se ejecuta con éxito:

UPDATE t1 SET c1=c1+1 WHERE c2=(SELECT MAX(c2) FROM t1);

DELETE - Misma tabla de origen y destino

Hasta MariaDB 10.3.1, no era posible eliminar de una tabla con el mismo origen y destino. Desde MariaDB 10.3.1, esto ahora es posible. Por ejemplo:

DELETE FROM t1 WHERE c1 IN (SELECT b.c1 FROM t1 b WHERE b.c2=0);

DBFiddle MariaDB 10.2 - Error

DBFiddle MariaDB 10.3 - Éxito

Lukasz Szozda
fuente
0

Otras soluciones incluyen el uso de SELECT DISTINCT o LIMIT en la subconsulta, aunque no tienen un efecto tan explícito en la materialización. esto funcionó para mí

como se menciona en MySql Doc

PITU
fuente
0

MySQL no permite seleccionar de una tabla y actualizar en la misma tabla al mismo tiempo. Pero siempre hay una solución :)

Esto no funciona >>>>

UPDATE table1 SET col1 = (SELECT MAX(col1) from table1) WHERE col1 IS NULL;

Pero esto funciona >>>>

UPDATE table1 SET col1 = (SELECT MAX(col1) FROM (SELECT * FROM table1) AS table1_new) WHERE col1 IS NULL;
Hari Das
fuente