Sie können in der FROM-Klausel keine Zieltabelle für die Aktualisierung angeben

380

Ich habe eine einfache MySQL-Tabelle:

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);

Ich habe versucht, das folgende Update auszuführen, erhalte jedoch nur den Fehler 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
))

Ich habe nach dem Fehler gesucht und auf MySQL die folgende Seite gefunden: http://dev.mysql.com/doc/refman/5.1/en/subquery-restrictions.html , aber es hilft mir nicht.

Was soll ich tun, um die SQL-Abfrage zu korrigieren?

CSchulz
quelle

Antworten:

769

Das Problem ist, dass Sie mit MySQL aus irgendeinem Grund keine Abfragen wie diese schreiben können:

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

Das heißt, wenn Sie eine UPDATE/ INSERT/ DELETEfür eine Tabelle ausführen, können Sie diese Tabelle nicht in einer inneren Abfrage referenzieren (Sie können jedoch auf ein Feld aus dieser äußeren Tabelle verweisen ...)


Die Lösung besteht darin, die Instanz von myTablein der Unterabfrage (SELECT * FROM myTable)wie folgt zu ersetzen

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

Dies führt anscheinend dazu, dass die erforderlichen Felder implizit in eine temporäre Tabelle kopiert werden, sodass dies zulässig ist.

Ich habe diese Lösung hier gefunden . Ein Hinweis aus diesem Artikel:

Sie wollen nicht nur SELECT * FROM tablein der Unterabfrage im wirklichen Leben; Ich wollte nur die Beispiele einfach halten. In Wirklichkeit sollten Sie nur die Spalten auswählen, die Sie in dieser innersten Abfrage benötigen, und eine gute WHEREKlausel hinzufügen , um auch die Ergebnisse einzuschränken.

BlueRaja - Danny Pflughoeft
quelle
10
Ich denke nicht, dass der Grund verrückt ist. Denken Sie an die Semantik. Entweder muss MySQL vor Beginn des Updates eine Kopie der Tabelle aufbewahren, oder die innere Abfrage verwendet möglicherweise Daten, die bereits von der Abfrage aktualisiert wurden, während sie ausgeführt wird. Keine dieser Nebenwirkungen ist notwendigerweise wünschenswert. Daher ist es am sichersten, Sie zu zwingen, mithilfe einer zusätzlichen Tabelle anzugeben, was passieren wird.
Siride
35
@siride: Andere Datenbanken, wie MSSQL oder Oracle, haben diese willkürliche Einschränkung nicht
BlueRaja - Danny Pflughoeft
3
@ BlueRaja-DannyPflughoeft: Es ist nicht willkürlich. Es ist eine vernünftige Entwurfsentscheidung, die auf den Kosten der Alternativen basiert. Die anderen DB-Systeme haben sich ohnehin für diese Kosten entschieden. Mit diesen Systemen können Sie jedoch nicht aggregierte Spalten in SELECT-Listen aufnehmen, wenn Sie GROUP BY verwenden, und MySQL tut dies. Ich würde argumentieren, dass MySQL hier falsch ist, und ich könnte dasselbe von den anderen DBMS für UPDATE-Anweisungen sagen.
Siride
33
@siride aus einer relationalen Algebra Sicht Tund (SELECT * FROM T)sind völlig gleichwertig. Sie sind die gleiche Beziehung. Daher ist dies eine willkürliche, inane Einschränkung. Insbesondere ist es eine Problemumgehung, MySQL dazu zu zwingen, etwas zu tun, was es eindeutig kann, aber aus irgendeinem Grund kann es nicht in seiner einfacheren Form analysiert werden.
Tobia
4
In meinem Fall hat die akzeptierte Lösung nicht funktioniert, weil mein Tisch einfach zu groß war. Die Abfrage wurde nie abgeschlossen. Anscheinend beansprucht dies zu viele interne Ressourcen. Stattdessen habe ich eine Ansicht mit der inneren Abfrage erstellt und sie für die Datenauswahl verwendet, was absolut einwandfrei funktioniert hat. DELETE FROM t WHERE tableID NOT IN (SELECT viewID FROM t_view);Ich empfehle auch, OPTIMIZE TABLE t;danach zu laufen , um die Größe der Tabelle zu reduzieren.
CodeX
53

Sie können dies in drei Schritten durchführen:

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;

oder

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
quelle
16
Nun ja, die meisten Unterabfragen können in mehreren Schritten mit CREATE TABLEAnweisungen umgeschrieben werden - ich hoffe, der Autor war sich dessen bewusst. Ist dies jedoch die einzige Lösung? Oder kann die Abfrage mit Unterabfragen oder Verknüpfungen neu geschrieben werden? Und warum (nicht) das tun?
Konerak
Ich denke, Sie haben einen Großschreibfehler in Ihrer zweiten Lösung. Sollte nicht UPDATE Pers Plesen UPDATE pers P?
Ubiquibacon
2
Versuchte diese Lösung und für eine große Anzahl von Einträgen in temporären / zweiten Tabellen kann die Abfrage sehr langsam sein; Versuchen Sie, eine temporäre / zweite Tabelle mit einem Index / Primärschlüssel zu erstellen [siehe dev.mysql.com/doc/refman/5.1/en/create-table-select.html ]
Alex
Wie @Konerak feststellt, ist dies nicht wirklich die beste Antwort. Die Antwort von BlueRaja unten scheint mir am besten zu sein. Upvotes scheinen zuzustimmen.
ShatyUT
@Konerak, gibt keine CREATE TABLE AS SELECTschreckliche Leistung?
Pacerier
27

In MySQL können Sie eine Tabelle nicht aktualisieren, indem Sie dieselbe Tabelle abfragen.

Sie können die Abfrage in zwei Teile trennen oder tun

 UPDATE TABLE_A AS A.
 INNER JOIN TABLE_A AS B ON A.field1 = B.field1
 SET field2 =? 
Yuantao
quelle
5
SELECT ... SET? Ich habe noch nie davon gehört.
Serge S.
@ Grisson Danke für die Klarstellung. Jetzt verstehe ich, warum meine IN-Klausel nicht funktioniert - ich habe auf dieselbe Tabelle abgezielt.
Anthony
2
... das scheint nicht wirklich zu funktionieren. Es gibt mir immer noch den gleichen Fehler.
BlueRaja - Danny Pflughoeft
2
Diese Antwort macht tatsächlich die korrektere und effizientere Sache, die AS Bbeim zweiten Verweis auf verwendet wird TABLE_A. Die Antwort im am besten bewerteten Beispiel könnte vereinfacht werden, indem AS Tanstelle des potenziell ineffizienten verwendet wird FROM (SELECT * FROM myTable) AS something, was glücklicherweise vom Abfrageoptimierer normalerweise eliminiert wird, dies jedoch möglicherweise nicht immer tut.
Natbro
23

Erstellen Sie aus einer Unterabfrage eine temporäre Tabelle (tempP)

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
)

Ich habe einen separaten Namen (Alias) eingeführt und der Spalte 'persID' für die temporäre Tabelle einen neuen Namen gegeben

Budda
quelle
Warum nicht die Werte in Variablen auswählen, anstatt innere innere innere Auswahlen vorzunehmen?
Pacerier
SELECT ( SELECT MAX(gehalt * 1.05)..- Der erste SELECTwählt keine Spalte aus.
Istiaque Ahmed
18

Es ist ganz einfach. Zum Beispiel anstatt zu schreiben:

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

du solltest schreiben

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.ä.

Dunkle Seite
quelle
13

Der von BlueRaja veröffentlichte Ansatz ist langsam. Ich habe ihn geändert, als ich Duplikate aus der Tabelle löschte. Falls es jemandem mit großen Tabellen hilft Originalabfrage

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

Das braucht mehr Zeit:

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

Schnellere Lösung

DELETE FROM table where ID NOT IN(
   SELECT x.Id from (SELECT MIN(Id) as Id from table GROUP BY field2) AS t)
Ajak6
quelle
Fügen Sie einen Kommentar hinzu, wenn Sie abstimmen.
Ajak6
3

Wenn Sie versuchen, FeldA aus TabelleA zu lesen und es in FeldB in derselben Tabelle zu speichern, sollten Sie dies bei fieldc = fieldd berücksichtigen.

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

Der obige Code kopiert den Wert von FeldA nach FeldB, wenn das Bedingungsfeld Ihre Bedingung erfüllt. Dies funktioniert auch in ADO (zB Zugriff)

Quelle: habe es selbst versucht

Krish
quelle
3

MariaDB hat dies ab 10.3.x aufgehoben (sowohl für DELETEals auch UPDATE):

UPDATE - Anweisungen mit derselben Quelle und demselben Ziel

Ab MariaDB 10.3.2 können UPDATE-Anweisungen dieselbe Quelle und dasselbe Ziel haben.

Bis MariaDB 10.3.1 würde die folgende UPDATE-Anweisung nicht funktionieren:

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

Ab MariaDB 10.3.2 wird die Anweisung erfolgreich ausgeführt:

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

LÖSCHEN - Gleiche Quell- und Zieltabelle

Bis MariaDB 10.3.1 war das Löschen aus einer Tabelle mit derselben Quelle und demselben Ziel nicht möglich. Ab MariaDB 10.3.1 ist dies nun möglich. Zum Beispiel:

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

DBFiddle MariaDB 10.2 - Fehler

DBFiddle MariaDB 10.3 - Erfolg

Lukasz Szozda
quelle
0

Andere Problemumgehungen umfassen die Verwendung von SELECT DISTINCT oder LIMIT in der Unterabfrage, obwohl diese in ihrer Auswirkung auf die Materialisierung nicht so explizit sind. das hat bei mir funktioniert

wie in MySql Doc erwähnt

PITU
quelle
0

MySQL erlaubt es nicht, aus einer Tabelle auszuwählen und gleichzeitig in derselben Tabelle zu aktualisieren. Aber es gibt immer eine Problemumgehung :)

Das funktioniert nicht >>>>

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

Aber das funktioniert >>>>

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