Errore MySQL 1093 - Impossibile specificare la tabella di destinazione per l'aggiornamento nella clausola FROM


594

Ho una tabella story_categorynel mio database con voci danneggiate. La query successiva restituisce le voci danneggiate:

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

Ho provato a eliminarli eseguendo:

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

Ma ricevo il prossimo errore:

# 1093 - Non è possibile specificare la tabella di destinazione 'story_category' per l'aggiornamento nella clausola FROM

Come posso superare questo?



2
Sembra che la richiesta di funzionalità nel tracker dei bug di MySQL sia qui: impossibile aggiornare una tabella e selezionare dalla stessa tabella in una subquery
Ben Creasy,

Risposte:


714

Aggiornamento: questa risposta copre la classificazione generale dell'errore. Per una risposta più specifica su come gestire al meglio la query esatta del PO, consultare le altre risposte a questa domanda

In MySQL, non è possibile modificare la stessa tabella utilizzata nella parte SELECT.
Questo comportamento è documentato su: http://dev.mysql.com/doc/refman/5.6/en/update.html

Forse puoi semplicemente unirti al tavolo

Se la logica è abbastanza semplice per riformare la query, perdere la sottoquery e unire la tabella a se stessa, utilizzando criteri di selezione appropriati. Ciò farà sì che MySQL consideri la tabella come due cose diverse, consentendo di apportare modifiche distruttive.

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

In alternativa, prova a nidificare la subquery più in profondità in una clausola from ...

Se hai assolutamente bisogno della subquery, c'è una soluzione alternativa, ma è brutta per diversi motivi, tra cui le prestazioni:

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

La subquery nidificata nella clausola FROM crea una tabella temporanea implicita , quindi non conta come la stessa tabella che stai aggiornando.

... ma fai attenzione al Query Optimizer

Tuttavia, attenzione che da MySQL 5.7.6 e versioni successive, l'ottimizzatore può ottimizzare la subquery e comunque restituire l'errore. Fortunatamente, la optimizer_switchvariabile può essere utilizzata per disattivare questo comportamento; anche se non potrei raccomandare di farlo come qualcosa di più di una soluzione a breve termine o per piccole attività una tantum.

SET optimizer_switch = 'derived_merge=off';

Grazie a Peter V. Mørch per questo consiglio nei commenti.

La tecnica di esempio è stata del barone Schwartz, originariamente pubblicata su Nabble , qui parafrasata ed estesa.


1
Ho annullato questa risposta perché ho dovuto eliminare elementi e non ho potuto ottenere informazioni da un'altra tabella, ho dovuto eseguire una subquery dalla stessa tabella. Dal momento che questo è ciò che appare in cima mentre googling per l'errore che ho ricevuto, questa sarebbe la risposta più adatta per me e molte persone che cercano di aggiornare mentre eseguono la subquery dalla stessa tabella.
HMR,

2
@Cheekysoft, Perché non salvare i valori in variabili invece?
Pacerier,

19
Attenzione, da MySQL 5.7.6 in poi , l'ottimizzatore può ottimizzare la sottoquery e continuare a darti l'errore, a meno che tu SET optimizer_switch = 'derived_merge=off';:-(
Peter V. Mørch,

1
@ PeterV.Mørch Seguendo mysqlserverteam.com/derived-tables-in-mysql-5-7 , nel caso in cui vengano eseguite determinate operazioni, l'unione non può avvenire. Ad esempio, fornire alla tabella fittizia derivata un LIMIT (a inifity) e l'errore non si verificherà mai. È comunque piuttosto hacker e sussiste il rischio che le versioni future di MySQL supporteranno la fusione di query con LIMIT.
user2180613

Potete, per favore, fornire un esempio completo di questa soluzione alternativa? AGGIORNA tbl SET col = (SELEZIONA ... DA (SELEZIONA .... DA) COME x);
Ricevo

310

NexusRex ha fornito un'ottima soluzione per l'eliminazione con join dalla stessa tabella.

Se lo fai:

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
)

stai per ricevere un errore.

Ma se avvolgi la condizione in un'altra, seleziona:

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
)

farebbe la cosa giusta !!

Spiegazione: Query Optimizer esegue un'ottimizzazione della fusione derivata per la prima query (che causa l'errore con l'errore), ma la seconda query non è idonea per l' ottimizzazione della fusione derivata . Quindi l'ottimizzatore è costretto a eseguire prima la sottoquery.


6
Forse è perché sono in difficoltà oggi, ma questa è stata la risposta più semplice, anche se forse non è la "migliore".
Tyler V.

4
Ha funzionato alla grande, grazie! Quindi qual è la logica qui? Se è nidificato di un altro livello, verrà eseguito prima della parte esterna? E se non è nidificato, mySQL tenta di eseguirlo dopo che l'eliminazione ha un blocco sulla tabella?
Flat Cat,

43
Questo errore e questa soluzione non hanno alcun senso logico ... ma funziona. A volte mi chiedo quali droghe siano gli sviluppatori MySQL ...
Cerin,

1
Concordo con @Cerin .. questo è completamente assurdo, ma funziona.
FastTrack,

@ekonoval Grazie per la soluzione ma non ha il minimo senso per me, sembra che tu stia
prendendo in giro

106

Il inner joinnella tua sottoquery non è necessario. Sembra che tu voglia eliminare le voci in story_categorycui category_idnon è presente nella categorytabella.

Fai questo:

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

Invece di quello:

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

5
Questa dovrebbe essere la risposta migliore! Forse cancella il primo "invece di".
hoyhoy,

1
Penso che DISTINCTnon sia necessario qui - per una prestazione migliore;).
shA.t

1
Devo essere pazzo La risposta è la stessa di quella dichiarata nell'originale.
Jeff Lowery,

Questa è la risposta anche per me. Non where innella colonna ID, quindi non è necessario sottoporre a query la tabella principale.
Richard,

@JeffLowery - il primo blocco di codice è la risposta qui; è il secondo blocco di codice che proviene dalla domanda.
ToolmakerSteve

95

Recentemente ho dovuto aggiornare i record nella stessa tabella, l'ho fatto come di seguito:

UPDATE skills AS s, (SELECT id  FROM skills WHERE type = 'Programming') AS p
SET s.type = 'Development' 
WHERE s.id = p.id;

11
Non si può semplicemente scrivere come UPDATE skills SET type='Development' WHERE type='Programming';? Questo non sembra rispondere alla domanda originale.
lilbyrdie,

1
Sembra eccessivo, @lilbyrdie è corretto - potrebbe essere solo UPDATE skills SET type='Development' WHERE type='Programming';. Non capisco perché così tante persone non
stiano

1
questa è la risposta migliore qui, IMHO. La sua sintassi è facile da capire, puoi riutilizzare la tua precedente dichiarazione e non si limita ad alcuni casi super specifici.
Steffen Winkler,

2
Anche se il caso di esempio è discutibile, il principio è il migliore da comprendere e distribuire in scenari del mondo reale. La query mostrata funziona perché il join implicito nell'elenco delle tabelle crea una tabella temporanea per una sottoquery, fornendo così risultati stabili ed evitando lo scontro tra il recupero e la modifica della stessa tabella.
AnrDaemon,

1
indipendentemente da ciò che qualcuno potrebbe dire su questo essere eccessivo, risponde ancora alla domanda in termini di titolo. L'errore menzionato dall'OP viene riscontrato anche quando si tenta un aggiornamento utilizzando la stessa tabella in una query nidificata
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
)

3
Puoi spiegare perché funziona, perché funziona solo l'annidamento di un altro livello? Questa domanda è già stata posta come commento alla domanda di @ EkoNoval ma nessuno ha risposto. Forse puoi aiutare.
Akshay Arora,

@AkshayArora, passa attraverso la parte sotto l'intestazione "Forse puoi semplicemente unirti al tavolo" nella risposta di @ Cheekysoft. Ha detto UPDATE tbl AS a INNER JOIN tbl AS b ON .... SET a.col = b.col: questo avrebbe funzionato come alias diverso per lo stesso tavolo utilizzato qui. Analogamente nella risposta di @ NexusRex la prima SELECTquery funge da tabella derivata nella quale story_categoryviene utilizzata per la seconda volta. Quindi l'errore menzionato nel PO non dovrebbe avvenire qui, giusto?
Istiaque Ahmed,

31

Se non puoi farlo

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

perché è lo stesso tavolo, puoi ingannare e fare:

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

[aggiorna o elimina o altro]


Implementazione più comprensibile e logica di tutte le risposte di cui sopra. Semplice e al punto.
Clain Dsilva,

Questo è diventato parte del mio flusso di lavoro ora. Rimani bloccato su questo errore, vai a questa risposta e correggi la query. Grazie.
Vaibhav

13

Questo è quello che ho fatto per aggiornare un valore della colonna Priorità di 1 se è> = 1 in una tabella e nella sua clausola WHERE utilizzando una subquery sulla stessa tabella per assicurarsi che almeno una riga contenga Priority = 1 (perché quello era il condizione da verificare durante l'esecuzione dell'aggiornamento):


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

So che è un po 'brutto ma funziona benissimo.


1
@anonymous_reviewer: in caso di dare [-1] o anche [+1] al commento di qualcuno, si prega di menzionare anche il motivo per cui l'hai dato. Grazie!!!
Sactiw,

1
-1 perché questo non è corretto. Non è possibile modificare la stessa tabella utilizzata nell'istruzione SELECT.
Chris,

1
@Chris L'ho verificato su MySQL e funziona bene per me, quindi ti chiedo di verificarlo alla tua fine e quindi dichiararlo corretto o errato. Grazie!!!
Sactiw,

1
Nella parte inferiore di questa pagina si dice "Attualmente, non è possibile aggiornare una tabella e selezionare dalla stessa tabella in una sottoquery". - e ho sperimentato che ciò è vero in molte occasioni. dev.mysql.com/doc/refman/5.0/en/update.html
Chris

19
@Chris Lo so, ma c'è una soluzione per questo e che è esattamente quello che ho provato a mostrare con la mia query 'UPDATE' e credimi che funziona bene. Non credo che tu abbia davvero provato a verificare la mia domanda.
Sactiw,

6

Il modo più semplice per farlo è utilizzare un alias di tabella quando si fa riferimento alla tabella di query principale all'interno della query secondaria.

Esempio :

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

Modificalo in:

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

5

È possibile inserire gli ID delle righe desiderate in una tabella temporanea e quindi eliminare tutte le righe che si trovano in quella tabella.

che può essere quello che intendeva @Cheekysoft facendolo in due passaggi.


3

Secondo la sintassi di Mysql UPDATE collegata da @CheekySoft, dice proprio in fondo.

Attualmente, non è possibile aggiornare una tabella e selezionare dalla stessa tabella in una sottoquery.

Immagino che stai eliminando da store_category mentre selezioni ancora da esso nell'unione.


3

Per la query specifica che l'OP sta cercando di raggiungere, il modo ideale ed efficiente per farlo NON è affatto utilizzare una subquery.

Ecco le LEFT JOINversioni delle due query del PO:

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

Nota: DELETE slimita le operazioni di eliminazione alla story_categorytabella.
Documentazione

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

1
Sorpreso, questo non ha più voti positivi. Va inoltre notato che la sintassi multi-tabella funziona anche con UPDATEistruzioni e sottoquery unite. Consentire all'utente di eseguire LEFT JOIN ( SELECT ... )le operazioni al contrario WHERE IN( SELECT ... ), rendendo l'implementazione utile in molti casi d'uso.
fyrye

2

Se qualcosa non funziona, entrando dalla porta principale, prendi la porta posteriore:

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;

È veloce. Più grandi sono i dati, meglio è.


11
E hai appena perso tutte le tue chiavi esterne e forse hai anche alcune eliminazioni a cascata.
Walf,

2

Prova a salvare il risultato dell'istruzione Select in una variabile separata, quindi utilizzalo per eliminare la query.


2

prova questo

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

1

che ne dite di questa query spero che sia d'aiuto

DELETE FROM story_category LEFT JOIN (SELECT category.id FROM category) cat ON story_category.id = cat.id WHERE cat.id IS NULL

Il risultato mostra: '# 1064 - Si è verificato un errore nella sintassi SQL; controlla il manuale che corrisponde alla versione del tuo server MariaDB per la sintassi corretta da usare vicino a "LEFT JOIN (SELEZIONA categorie.id DA categorie) cat ON story_category.id = cat." alla linea 1 '
Istiaque Ahmed,

Quando si utilizza l'eliminazione di più tabelle, è necessario specificare le tabelle interessate. DELETE story_category FROM ...tuttavia, la sottoquery unita non è necessaria in questo contesto e può essere eseguita utilizzando LEFT JOIN category AS cat ON cat.id = story_category.category_id WHERE cat.id IS NULL, Nota i criteri di unione nella risposta erroneamente riferimentistory_category.id = cat.id
fyrye

0

Per quanto riguarda, si desidera eliminare le righe in story_categorycui non esistono category.

Ecco la tua query originale per identificare le righe da eliminare:

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 combinazione NOT INcon una sottoquery che JOINè la tabella originale sembra inutilmente contorta. Questo può essere espresso in modo più diretto not existse con una sottoquery correlata:

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

Ora è facile trasformarlo in una deletedichiarazione:

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

Questa query verrebbe eseguita su qualsiasi versione di MySQL, così come nella maggior parte degli altri database che conosco.

Demo su 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);
| categoria_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)
| categoria_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;
| categoria_id |
| ----------: |
| 4 |
| 5 |
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.