MySQL-Fehler 1093 - Zieltabelle für Aktualisierung in FROM-Klausel kann nicht angegeben werden

593

Ich habe eine Tabelle story_categoryin meiner Datenbank mit beschädigten Einträgen. Die nächste Abfrage gibt die beschädigten Einträge zurück:

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

Ich habe versucht, sie zu löschen, indem ich Folgendes ausführte:

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

Aber ich bekomme den nächsten Fehler:

# 1093 - Sie können die Zieltabelle 'story_category' für die Aktualisierung in der FROM-Klausel nicht angeben

Wie kann ich das überwinden?

Sergio del Amo
quelle
3
Siehe auch
BlueRaja - Danny Pflughoeft
2
Die Funktionsanforderung im MySQL-Bug-Tracker scheint hier zu sein: Ich kann eine Tabelle nicht aktualisieren und aus derselben Tabelle in einer Unterabfrage auswählen
Ben Creasy

Antworten:

713

Update: Diese Antwort deckt die allgemeine Fehlerklassifizierung ab. Eine genauere Antwort darauf, wie die genaue Abfrage des OP am besten behandelt werden kann, finden Sie in anderen Antworten auf diese Frage

In MySQL können Sie nicht dieselbe Tabelle ändern, die Sie im SELECT-Teil verwenden.
Dieses Verhalten ist dokumentiert unter: http://dev.mysql.com/doc/refman/5.6/en/update.html

Vielleicht können Sie den Tisch einfach mit sich selbst verbinden

Wenn die Logik einfach genug ist, um die Abfrage neu zu gestalten, verlieren Sie die Unterabfrage und verbinden Sie die Tabelle unter Verwendung geeigneter Auswahlkriterien mit sich selbst. Dies führt dazu, dass MySQL die Tabelle als zwei verschiedene Dinge betrachtet, sodass destruktive Änderungen vorgenommen werden können.

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

Versuchen Sie alternativ, die Unterabfrage tiefer in eine from-Klausel zu verschachteln ...

Wenn Sie die Unterabfrage unbedingt benötigen, gibt es eine Problemumgehung, die jedoch aus mehreren Gründen hässlich ist, einschließlich der Leistung:

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

Die verschachtelte Unterabfrage in der FROM-Klausel erstellt eine implizite temporäre Tabelle , sodass sie nicht als dieselbe Tabelle zählt, die Sie aktualisieren.

... aber achten Sie auf den Abfrageoptimierer

Beachten Sie jedoch, dass der Optimierer ab MySQL 5.7.6 möglicherweise die Unterabfrage optimiert und dennoch den Fehler ausgibt. Glücklicherweise kann die optimizer_switchVariable verwendet werden, um dieses Verhalten auszuschalten. obwohl ich nicht empfehlen kann, dies als etwas anderes als eine kurzfristige Lösung oder für kleine einmalige Aufgaben zu tun.

SET optimizer_switch = 'derived_merge=off';

Vielen Dank an Peter V. Mørch für diesen Rat in den Kommentaren.

Die Beispieltechnik stammt von Baron Schwartz, der ursprünglich bei Nabble veröffentlicht und hier umschrieben und erweitert wurde.

Cheekysoft
quelle
1
Diese Antwort wurde positiv bewertet, da ich Elemente löschen musste und keine Informationen aus einer anderen Tabelle abrufen konnte und eine Unterabfrage aus derselben Tabelle durchführen musste. Da dies beim Googeln nach dem Fehler, den ich erhalten habe, oben angezeigt wird, ist dies die beste Antwort für mich und viele Leute, die versuchen, ein Update durchzuführen, während sie von derselben Tabelle aus abfragen.
HMR
2
@Cheekysoft, warum nicht stattdessen die Werte in Variablen speichern?
Pacerier
19
Beachten Sie, dass der Optimierer ab MySQL 5.7.6 die Unterabfrage möglicherweise weiter optimiert und Ihnen trotzdem den Fehler gibt, es sei denn, Sie SET optimizer_switch = 'derived_merge=off';:-(
Peter V. Mørch
1
@ PeterV.Mørch Nach mysqlserverteam.com/derived-tables-in-mysql-5-7 kann bei bestimmten Vorgängen keine Zusammenführung erfolgen. Stellen Sie beispielsweise der abgeleiteten Dummy-Tabelle ein LIMIT (to inifity) zur Verfügung, und der Fehler tritt niemals auf. Das ist allerdings ziemlich hackisch und es besteht immer noch das Risiko, dass zukünftige Versionen von MySQL das Zusammenführen von Abfragen mit LIMIT unterstützen.
user2180613
Können Sie bitte ein vollständiges Beispiel für diese Problemumgehung geben? UPDATE tbl SET col = (SELECT ... FROM (SELECT .... FROM) AS x); Ich bekomme immer noch Fehler
JoelBonetR
310

NexusRex bot eine sehr gute Lösung zum Löschen mit Join aus derselben Tabelle.

Wenn du das tust:

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
)

Sie werden eine Fehlermeldung erhalten.

Wenn Sie die Bedingung jedoch noch einmal einpacken, wählen Sie:

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
)

es würde das Richtige tun !!

Erläuterung: Das Abfrageoptimierungsprogramm führt eine abgeleitete Zusammenführungsoptimierung für die erste Abfrage durch (wodurch der Fehler fehlschlägt), die zweite Abfrage ist jedoch nicht für die abgeleitete Zusammenführungsoptimierung geeignet . Daher ist der Optimierer gezwungen, zuerst die Unterabfrage auszuführen.

Ekonoval
quelle
6
Vielleicht liegt es daran, dass ich heute in Schwierigkeiten bin, aber dies war die einfachste Antwort, auch wenn es vielleicht nicht die "beste" ist.
Tyler V.
4
Das hat super geklappt, danke! Was ist die Logik hier? Wenn es noch eine Ebene verschachtelt ist, wird es vor dem äußeren Teil ausgeführt? Und wenn es nicht verschachtelt ist, versucht mySQL es auszuführen, nachdem das Löschen eine Sperre für die Tabelle hat?
Flat Cat
43
Dieser Fehler und diese Lösung machen keinen logischen Sinn ... aber es funktioniert. Manchmal frage ich mich, welche Drogen die MySQL-Entwickler nehmen ...
Cerin
1
Stimmen Sie mit @Cerin überein .. das ist völlig absurd, aber es funktioniert.
FastTrack
@ekonoval Vielen Dank für die Lösung, aber es macht nicht den geringsten Sinn für mich, sieht aus wie Sie MySQL täuschen und er akzeptiert es, lol
deFreitas
106

Das inner joinin Ihrer Unterabfrage ist nicht erforderlich. Es sieht so aus, als ob Sie die Einträge löschen möchten, in story_categorydenen sich das category_idnicht in der categoryTabelle befindet.

Mach das:

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

Stattdessen:

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.
quelle
5
Dies sollte die beste Antwort sein! Löschen Sie möglicherweise das erste "statt".
Hoyhoy
1
Ich halte DISTINCTdas hier für unnötig - für eine bessere Leistung;).
ShA.t
1
Ich muss verrückt sein. Die Antwort ist die gleiche wie im Original.
Jeff Lowery
Das ist auch für mich die Antwort. Nicht where inin der ID-Spalte, damit Sie die Primärtabelle nicht abfragen müssen.
Richard
@ JeffLowery - der erste Codeblock ist hier die Antwort; Es ist der zweite Codeblock, der aus der Frage stammt.
ToolmakerSteve
95

Vor kurzem musste ich Datensätze in derselben Tabelle aktualisieren, in der ich es wie folgt gemacht habe:

UPDATE skills AS s, (SELECT id  FROM skills WHERE type = 'Programming') AS p
SET s.type = 'Development' 
WHERE s.id = p.id;
Pratik Khadloya
quelle
11
Kann das nicht einfach so geschrieben werden UPDATE skills SET type='Development' WHERE type='Programming';? Dies scheint die ursprüngliche Frage nicht zu beantworten.
Lilbyrdie
1
Scheint übertrieben, @lilbyrdie ist richtig - es könnte nur sein UPDATE skills SET type='Development' WHERE type='Programming';. Ich verstehe nicht, warum so viele Leute nicht darüber nachdenken, was sie tun ...
Shadyyx
1
Dies ist die beste Antwort hier, IMHO. Die Syntax ist leicht zu verstehen, Sie können Ihre vorherige Anweisung wiederverwenden und sie ist nicht auf einen bestimmten Fall beschränkt.
Steffen Winkler
2
Selbst wenn der Beispielfall fraglich ist, ist das Prinzip am besten zu verstehen und in realen Szenarien einzusetzen. Die angezeigte Abfrage funktioniert, weil durch implizite Verknüpfung in der Tabellenliste eine temporäre Tabelle für eine Unterabfrage erstellt wird, wodurch stabile Ergebnisse erzielt werden und Konflikte zwischen dem Abrufen und der Änderung derselben Tabelle vermieden werden.
AnrDaemon
1
Unabhängig davon, was jemand über dieses Overkill sagen könnte, beantwortet es immer noch die Frage nach dem Titel. Der Fehler, den das OP erwähnte, tritt auch auf, wenn versucht wird, eine Aktualisierung unter Verwendung derselben Tabelle in einer verschachtelten Abfrage zu
versuchen
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
quelle
3
Können Sie erklären, warum dies funktioniert und warum nur das Verschachteln einer weiteren Ebene funktioniert? Diese Frage wurde bereits als Kommentar zu @ EkoNovals Frage gestellt, aber niemand hat geantwortet. Vielleicht kannst du helfen.
Akshay Arora
@AkshayArora, Gehen Sie den Teil unter der Überschrift "Vielleicht können Sie den Tisch einfach mit sich selbst verbinden" in der Antwort von @ Cheekysoft durch. Er sagte UPDATE tbl AS a INNER JOIN tbl AS b ON .... SET a.col = b.col- dies würde funktionieren, da hier ein anderer Alias ​​für dieselbe Tabelle verwendet wird. In ähnlicher Weise fungiert in der Antwort von @ NexusRex die erste SELECTAbfrage als abgeleitete Tabelle, in die story_categoryzum zweiten Mal verwendet wird. Der im OP erwähnte Fehler sollte hier also nicht auftreten, oder?
Istiaque Ahmed
31

Wenn du nicht kannst

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

Da es sich um dieselbe Tabelle handelt, können Sie Folgendes tun:

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

[aktualisieren oder löschen oder was auch immer]

Sequoya
quelle
Die verständlichste und logischste Umsetzung aller oben genannten Antworten. Einfach und auf den Punkt.
Clain Dsilva
Dies ist jetzt Teil meines Workflows geworden. Bleiben Sie bei diesem Fehler hängen, gehen Sie zu dieser Antwort und korrigieren Sie die Abfrage. Vielen Dank.
Vaibhav
13

Dies habe ich getan, um einen Prioritätsspaltenwert um 1 zu aktualisieren, wenn er in einer Tabelle> = 1 ist und in seiner WHERE-Klausel eine Unterabfrage in derselben Tabelle verwendet, um sicherzustellen, dass mindestens eine Zeile Priorität = 1 enthält (da dies der war Bedingung, die während der Aktualisierung überprüft werden muss):


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

Ich weiß, dass es ein bisschen hässlich ist, aber es funktioniert gut.

sactiw
quelle
1
@anonymous_reviewer: Wenn Sie einem Kommentar [-1] oder sogar [+1] geben, erwähnen Sie bitte auch, warum Sie ihn gegeben haben. Vielen Dank!!!
Sactiw
1
-1 weil das falsch ist. Sie können nicht dieselbe Tabelle ändern, die Sie in der SELECT-Anweisung verwenden.
Chris
1
@Chris Ich habe es auf MySQL überprüft und es funktioniert gut für mich, daher würde ich Sie bitten, es an Ihrem Ende zu überprüfen und dann zu behaupten, dass es richtig oder falsch ist. Vielen Dank!!!
Sactiw
1
Am Ende dieser Seite steht "Derzeit können Sie eine Tabelle nicht aktualisieren und in einer Unterabfrage aus derselben Tabelle auswählen." - und ich habe dies bei vielen Gelegenheiten als wahr erlebt. dev.mysql.com/doc/refman/5.0/en/update.html
Chris
19
@ Chris Ich weiß das, aber es gibt eine Problemumgehung dafür und genau das habe ich versucht, mit meiner 'UPDATE'-Abfrage zu zeigen und glaube mir, dass es gut funktioniert. Ich glaube nicht, dass Sie wirklich versucht haben, meine Anfrage zu überprüfen.
Sactiw
6

Der einfachste Weg, dies zu tun, besteht darin, einen Tabellenalias zu verwenden, wenn Sie auf eine übergeordnete Abfragetabelle innerhalb der Unterabfrage verweisen.

Beispiel:

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

Ändern Sie es in:

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

Sie können die IDs der gewünschten Zeilen in eine temporäre Tabelle einfügen und dann alle in dieser Tabelle gefundenen Zeilen löschen.

Das könnte das sein, was @Cheekysoft damit gemeint hat, es in zwei Schritten zu tun.

YonahW
quelle
3

Laut der von @CheekySoft verknüpften MySQL-UPDATE-Syntax steht ganz unten.

Derzeit können Sie eine Tabelle nicht aktualisieren und in einer Unterabfrage aus derselben Tabelle auswählen.

Ich denke, Sie löschen aus store_category, während Sie noch in der Union daraus auswählen.

Briankip
quelle
3

Für die spezifische Abfrage, die das OP zu erreichen versucht, besteht der ideale und effizienteste Weg, dies zu tun, darin, überhaupt keine Unterabfrage zu verwenden.

Hier sind die LEFT JOINVersionen der beiden Abfragen des OP:

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

Hinweis: DELETE sBeschränkt Löschvorgänge auf die story_categoryTabelle.
Dokumentation

DELETE s 
FROM story_category s 
LEFT JOIN category c 
ON c.id=s.category_id 
WHERE c.id IS NULL;
Doin
quelle
1
Überrascht hat dies nicht mehr Up-Votes. Es sollte auch beachtet werden, dass die Multi-Table-Syntax auch mit UPDATEAnweisungen und verknüpften Unterabfragen funktioniert . So können Sie LEFT JOIN ( SELECT ... )im Gegensatz zu durchführen WHERE IN( SELECT ... )und die Implementierung für viele Anwendungsfälle nützlich machen.
Fyrye
2

Wenn etwas nicht funktioniert, wenn Sie durch die Vordertür kommen, nehmen Sie die Hintertür:

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 ist schnell. Je größer die Daten, desto besser.

Tom Schaefer
quelle
11
Und Sie haben gerade alle Ihre Fremdschlüssel verloren, und vielleicht haben Sie auch einige kaskadierende Löschvorgänge.
Walf
2

Versuchen Sie, das Ergebnis der Select-Anweisung in einer separaten Variablen zu speichern, und verwenden Sie diese dann zum Löschen der Abfrage.

Lipika Chakraborty
quelle
2

Versuche dies

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

Wie wäre es mit dieser Abfrage, hoffe es hilft

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
quelle
Das Ergebnis zeigt: '# 1064 - Sie haben einen Fehler in Ihrer SQL-Syntax; Überprüfen Sie das Handbuch, das Ihrer MariaDB-Serverversion entspricht, auf die richtige Syntax für "LEFT JOIN (SELECT Kategorien.id FROM Kategorien) cat ON story_category.id = cat". in Zeile 1 '
Istiaque Ahmed
Wenn Sie das Löschen mehrerer Tabellen verwenden, müssen Sie die betroffenen Tabellen angeben. DELETE story_category FROM ...Die LEFT JOIN category AS cat ON cat.id = story_category.category_id WHERE cat.id IS NULLstory_category.id = cat.id
verknüpfte Unterabfrage
0

Sie möchten Zeilen löschen, in story_categorydenen keine vorhanden sind category.

Hier ist Ihre ursprüngliche Abfrage, um die zu löschenden Zeilen zu identifizieren:

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

Die Kombination NOT INmit einer Unterabfrage, die JOINdie ursprüngliche Tabelle enthält, scheint unnötig kompliziert zu sein. Dies kann mit not existsund einer korrelierten Unterabfrage einfacher ausgedrückt werden:

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

Jetzt ist es einfach, dies in eine deleteAussage umzuwandeln:

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

Diese Abfrage würde auf jeder MySQL-Version sowie in den meisten anderen mir bekannten Datenbanken ausgeführt.

Demo zu 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);
| category_id |
| ----------: |
| 1 |
| 2 |
| 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)
| category_id |
| ----------: |
| 1 |
| 2 |
| 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;
| category_id |
| ----------: |
| 4 |
| 5 |
GMB
quelle