Non è possibile specificare la tabella di destinazione per l'aggiornamento nella clausola FROM


382

Ho una semplice tabella mysql:

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

Ho provato a eseguire il seguente aggiornamento, ma ottengo solo l'errore 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
))

Ho cercato l'errore e ho trovato da mysql la pagina seguente http://dev.mysql.com/doc/refman/5.1/en/subquery-rest restrizioni.html , ma non mi aiuta.

Cosa devo fare per correggere la query sql?


Risposte:


772

Il problema è che MySQL, per qualsiasi motivo insano, non ti consente di scrivere query come questa:

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

Cioè, se stai facendo un UPDATE/ INSERT/ DELETEsu una tabella, non puoi fare riferimento a quella tabella in una query interna ( puoi comunque fare riferimento a un campo da quella tabella esterna ...)


La soluzione è sostituire l'istanza di myTablenella sottoquery con (SELECT * FROM myTable), in questo modo

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

Ciò apparentemente causa la copia implicita dei campi necessari in una tabella temporanea, quindi è consentito.

Ho trovato questa soluzione qui . Una nota da quell'articolo:

Non vuoi solo SELECT * FROM tablenella sottoquery nella vita reale; Volevo solo mantenere semplici gli esempi. In realtà, dovresti solo selezionare le colonne necessarie in quella query più interna e aggiungere una buona WHEREclausola per limitare anche i risultati.


10
Non penso che la ragione sia insensata. Pensa alla semantica. MySQL deve conservare una copia della tabella prima dell'inizio dell'aggiornamento oppure la query interna potrebbe utilizzare i dati che sono già stati aggiornati dalla query mentre è in corso. Nessuno di questi effetti collaterali è necessariamente desiderabile, quindi la scommessa più sicura è costringerti a specificare cosa accadrà usando un tavolo extra.
siride,

35
@siride: Altri database, come MSSQL o Oracle, non hanno questa restrizione arbitraria
BlueRaja - Danny Pflughoeft

3
@ BlueRaja-DannyPflughoeft: non è arbitrario. È una decisione di progettazione ragionevole basata sui costi delle alternative. Gli altri sistemi DB hanno scelto di gestire tali costi comunque. Tuttavia, tali sistemi non consentono, ad esempio, di includere colonne non aggregate negli elenchi SELECT quando si utilizza GROUP BY, e MySQL lo fa. Direi che qui MySQL ha torto, e potrei dire lo stesso degli altri DBMS per le dichiarazioni UPDATE.
siride,

33
@siride Dal punto di vista dell'algebra relazionale, Te (SELECT * FROM T)sono completamente equivalenti. Sono la stessa relazione. Pertanto questa è una restrizione arbitraria e insensata. Più specificamente, è una soluzione alternativa costringere MySQL a fare qualcosa che chiaramente può fare, ma per qualche ragione non può analizzare nella sua forma più semplice.
Tobia,

4
Nel mio caso la soluzione accettata non ha funzionato perché il mio tavolo era semplicemente troppo grande. La query non è mai stata completata. Apparentemente questo richiede troppe risorse interne. Invece ho creato una vista con la query interna e l'ho usata per la selezione dei dati, che ha funzionato perfettamente. DELETE FROM t WHERE tableID NOT IN (SELECT viewID FROM t_view);Inoltre raccomando di correre in OPTIMIZE TABLE t;seguito per ridurre le dimensioni della tabella.
CodeX,

53

Puoi farlo in tre passaggi:

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

16
Bene sì, la maggior parte delle sottoquery può essere riscritta in più passaggi con CREATE TABLEdichiarazioni - spero che l'autore ne sia consapevole. Tuttavia, questa è l'unica soluzione? Oppure la query può essere riscritta con sottoquery o join? E perché (non) farlo?
Konerak,

Penso che tu abbia un errore di capitalizzazione nella tua seconda soluzione. Non dovresti UPDATE Pers Pleggere UPDATE pers P?
ubiquibacon,

2
Ho provato questa soluzione e per un gran numero di voci nella tabella temporanea / seconda la query può essere molto lenta; prova a creare una tabella temporanea / seconda con un indice / chiave primaria [vedi dev.mysql.com/doc/refman/5.1/en/create-table-select.html ]
Alex,

Come afferma @Konerak, questa non è davvero la risposta migliore. La risposta di BlueRaja qui sotto mi sembra la migliore. Le valutazioni sembrano essere d'accordo.
ShatyUT,

@Konerak, Non CREATE TABLE AS SELECTdà prestazioni orribili?
Pacerier,

27

In Mysql, non è possibile aggiornare una tabella sottoponendo una query alla stessa tabella.

È possibile separare la query in due parti oppure fare

 TABELLA AGGIORNAMENTI_A COME A
 TABELLA DI PARTECIPAZIONE INTERNA_A COME B SU A.field1 = B.field1
 SET field2 =? 

5
SELECT ... SET? Non ne ho mai sentito parlare.
Serge S.,

@grisson Grazie per il chiarimento. Ora capisco perché la mia clausola IN non funziona: stavo prendendo di mira la stessa tabella.
Anthony,

2
... questo non sembra funzionare davvero. Mi sta ancora dando lo stesso errore.
BlueRaja - Danny Pflughoeft il

2
questa risposta fa effettivamente la cosa più corretta ed efficiente, che sta usando AS Bnel secondo riferimento a TABLE_A. la risposta nell'esempio più votato potrebbe essere semplificata usando al AS Tposto del potenzialmente inefficiente FROM (SELECT * FROM myTable) AS something, che fortunatamente Query Optimizer elimina in genere, ma potrebbe non farlo sempre.
natbro,

23

Crea una tabella temporanea (tempP) da una sottoquery

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
)

Ho introdotto un nome separato (alias) e ho assegnato un nuovo nome alla colonna "persID" per la tabella temporanea


Perché non selezionare i valori in variabili invece di effettuare selezioni interne interne interne?
Pacerier,

SELECT ( SELECT MAX(gehalt * 1.05)..- il primo SELECTnon seleziona alcuna colonna.
Istiaque Ahmed,

18

È abbastanza semplice. Ad esempio, invece di scrivere:

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

dovresti scrivere

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


13

L'approccio pubblicato da BlueRaja è lento L'ho modificato mentre stavo usando per eliminare i duplicati dalla tabella. Nel caso in cui sia utile a chiunque abbia grandi tabelle Query originale

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

Questo richiede più tempo:

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

Soluzione più veloce

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

Aggiungi un commento se stai effettuando il downvoting.
Ajak6,


3

Se stai provando a leggere il campo A dalla tabella A e salvarlo sul campo B sulla stessa tabella, quando fieldc = fieldd potresti prendere in considerazione questo.

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

Il codice sopra copia il valore dal campo A al campo B quando condizione-campo ha soddisfatto la tua condizione. questo funziona anche in ADO (es. accesso)

fonte: ho provato me stesso


3

MariaDB ha sollevato questo a partire da 10.3.x (sia per DELETEche UPDATE):

AGGIORNAMENTO - Dichiarazioni con la stessa origine e destinazione

Da MariaDB 10.3.2, le istruzioni UPDATE possono avere la stessa origine e destinazione.

Fino a MariaDB 10.3.1, la seguente istruzione UPDATE non funzionava:

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

Da MariaDB 10.3.2, l'istruzione viene eseguita correttamente:

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

ELIMINA - Stessa tabella di origine e destinazione

Fino a MariaDB 10.3.1, non era possibile eliminare da una tabella con la stessa origine e destinazione. Da MariaDB 10.3.1, questo è ora possibile. Per esempio:

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

DBFiddle MariaDB 10.2 - Errore

DBFiddle MariaDB 10.3 - Successo


0

Altre soluzioni alternative includono l'utilizzo di SELECT DISTINCT o LIMIT nella sottoquery, sebbene questi non siano così espliciti nel loro effetto sulla materializzazione. questo ha funzionato per me

come menzionato in MySql Doc


0

MySQL non consente la selezione da una tabella e l'aggiornamento nella stessa tabella contemporaneamente. Ma c'è sempre una soluzione alternativa :)

Questo non funziona >>>>

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

Ma questo funziona >>>>

UPDATE table1 SET col1 = (SELECT MAX(col1) FROM (SELECT * FROM table1) AS table1_new) WHERE col1 IS NULL;
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.