Vous ne pouvez pas spécifier la table cible pour la mise à jour dans la clause FROM

382

J'ai une simple table 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);

J'ai essayé d'exécuter la mise à jour suivante, mais je n'obtiens que l'erreur 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
))

J'ai recherché l'erreur et trouvé à partir de la page suivante de mysql http://dev.mysql.com/doc/refman/5.1/en/subquery-restrictions.html , mais cela ne m'aide pas.

Que dois-je faire pour corriger la requête SQL?

CSchulz
la source

Réponses:

772

Le problème est que MySQL, pour une raison quelconque, ne vous permet pas d'écrire des requêtes comme celle-ci:

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

Autrement dit, si vous faites un UPDATE/ INSERT/ DELETEsur une table, vous ne pouvez pas référencer cette table dans une requête interne (vous pouvez cependant référencer un champ de cette table externe ...)


La solution consiste à remplacer l'instance de myTabledans la sous-requête par (SELECT * FROM myTable), comme ceci

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

Cela provoque apparemment la copie implicite des champs nécessaires dans une table temporaire, donc c'est autorisé.

J'ai trouvé cette solution ici . Une note de cet article:

Vous ne voulez pas seulement SELECT * FROM tabledans la sous-requête dans la vraie vie; Je voulais juste garder les exemples simples. En réalité, vous ne devez sélectionner que les colonnes dont vous avez besoin dans cette requête la plus profonde et ajouter une bonne WHEREclause pour limiter les résultats également.

BlueRaja - Danny Pflughoeft
la source
10
Je ne pense pas que la raison soit stupide. Pensez à la sémantique. Soit MySQL doit conserver une copie de la table avant le démarrage de la mise à jour, soit la requête interne peut utiliser des données qui ont déjà été mises à jour par la requête pendant son déroulement. Aucun de ces effets secondaires n'est nécessairement souhaitable, donc le pari le plus sûr est de vous forcer à spécifier ce qui se passera en utilisant une table supplémentaire.
siride
35
@siride: D'autres bases de données, telles que MSSQL ou Oracle, n'ont pas cette restriction arbitraire
BlueRaja - Danny Pflughoeft
3
@ BlueRaja-DannyPflughoeft: ce n'est pas arbitraire. C'est une décision de conception raisonnable basée sur les coûts des alternatives. Les autres systèmes DB ont choisi de faire face à ces coûts de toute façon. Mais ces systèmes ne vous permettent pas, par exemple, d'inclure des colonnes non agrégées dans les listes SELECT lorsque vous utilisez GROUP BY, et MySQL le fait. Je dirais que MySQL est dans l'erreur ici, et je pourrais dire la même chose des autres SGBD pour les instructions UPDATE.
siride
33
@siride Du point de vue de l'algèbre relationnelle, Tet (SELECT * FROM T)sont complètement équivalents. Ils sont la même relation. C'est donc une restriction arbitraire et stupide. Plus précisément, c'est une solution de contournement pour contraindre MySQL à faire quelque chose qu'il peut clairement faire, mais pour une raison quelconque, il ne peut pas analyser sous sa forme plus simple.
Tobia
4
Dans mon cas, la solution acceptée n'a pas fonctionné car ma table était tout simplement trop grande. La requête n'est jamais terminée. Apparemment, cela prend trop de ressources internes. Au lieu de cela, j'ai créé une vue avec la requête interne et l'ai utilisée pour la sélection des données, ce qui a très bien fonctionné. DELETE FROM t WHERE tableID NOT IN (SELECT viewID FROM t_view);Je recommande également de courir OPTIMIZE TABLE t;après pour réduire la taille de la table.
CodeX
53

Vous pouvez le faire en trois étapes:

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;

ou

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
la source
16
Eh bien oui, la plupart des sous-requêtes peuvent être réécrites en plusieurs étapes avec des CREATE TABLEdéclarations - j'espère que l'auteur en était conscient. Mais est-ce la seule solution? Ou la requête peut-elle être réécrite avec des sous-requêtes ou des jointures? Et pourquoi (pas) faire ça?
Konerak
Je pense que vous avez une erreur de capitalisation dans votre deuxième solution. Ne devrait pas UPDATE Pers Plire UPDATE pers P?
ubiquibacon
2
J'ai essayé cette solution et pour un grand nombre d'entrées dans une table temporaire / seconde, la requête peut être très lente; essayez de créer une table temporaire / seconde avec un index / clé primaire [voir dev.mysql.com/doc/refman/5.1/en/create-table-select.html ]
Alex
Comme le dit @Konerak, ce n'est pas vraiment la meilleure réponse. La réponse de BlueRaja ci-dessous me semble la meilleure. Les votes positifs semblent d'accord.
ShatyUT
@Konerak, ne CREATE TABLE AS SELECTdonne pas des performances horribles?
Pacerier
27

Dans Mysql, vous ne pouvez pas mettre à jour une table en sous-interrogeant la même table.

Vous pouvez séparer la requête en deux parties, ou faire

 UPDATE TABLE_A AS A
 INNER JOIN TABLE_A AS B ON A.field1 = B.field1
 SET champ2 =? 
Yuantao
la source
5
SELECT ... SET? Je n'en ai jamais entendu parler.
Serge S.
@grisson Merci pour la clarification. Maintenant, je comprends pourquoi ma clause IN ne fonctionne pas - je visais la même table.
Anthony
2
... cela ne semble pas vraiment fonctionner. Ça me donne toujours la même erreur.
BlueRaja - Danny Pflughoeft
2
cette réponse fait en fait la chose la plus correcte et la plus efficace, qui utilise AS Bla deuxième référence à TABLE_A. la réponse dans l'exemple le plus voté pourrait être simplifiée en utilisant AS Tau lieu de potentiellement inefficace FROM (SELECT * FROM myTable) AS something, ce qui heureusement l'optimiseur de requête élimine généralement mais ne le fait pas toujours.
natbro
23

Créer une table temporaire (tempP) à partir d'une sous-requête

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
)

J'ai introduit un nom séparé (alias) et donne un nouveau nom à la colonne 'persID' pour la table temporaire

Budda
la source
Pourquoi ne pas sélectionner les valeurs en variables au lieu de faire des sélections internes internes internes?
Pacerier
SELECT ( SELECT MAX(gehalt * 1.05)..- le premier SELECTne sélectionne aucune colonne.
Istiaque Ahmed
18

C'est assez simple. Par exemple, au lieu d'écrire:

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

tu devrais écrire

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

ou similaire.

Côté obscur
la source
13

L'approche publiée par BlueRaja est lente, je l'ai modifiée car j'utilisais pour supprimer les doublons du tableau. Au cas où cela aiderait quiconque avec de grandes tables Original Query

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

Cela prend plus de temps:

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

Solution plus rapide

DELETE FROM table where ID NOT IN(
   SELECT x.Id from (SELECT MIN(Id) as Id from table GROUP BY field2) AS t)
Ajak6
la source
Ajoutez un commentaire si vous votez en aval.
Ajak6
3

Si vous essayez de lire fieldA à partir de tableA et de l'enregistrer sur fieldB sur la même table, lorsque fieldc = fieldd vous voudrez peut-être considérer cela.

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

Le code ci-dessus copie la valeur de fieldA vers fieldB lorsque condition-field a rencontré votre condition. cela fonctionne également dans ADO (par exemple accès)

source: moi-même essayé

Krish
la source
3

MariaDB a levé cela à partir de 10.3.x (pour DELETEet UPDATE):

MISE À JOUR - Relevés de même source et cible

Depuis MariaDB 10.3.2, les instructions UPDATE peuvent avoir la même source et la même cible.

Jusqu'à MariaDB 10.3.1, l'instruction UPDATE suivante ne fonctionnerait pas:

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

Depuis MariaDB 10.3.2, l'instruction s'exécute avec succès:

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

SUPPRIMER - Même source et table cible

Jusqu'à MariaDB 10.3.1, la suppression d'une table avec la même source et la même cible n'était pas possible. Depuis MariaDB 10.3.1, cela est désormais possible. Par exemple:

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

DBFiddle MariaDB 10.2 - Erreur

DBFiddle MariaDB 10.3 - Succès

Lukasz Szozda
la source
0

D'autres solutions de contournement incluent l'utilisation de SELECT DISTINCT ou LIMIT dans la sous-requête, bien que leur effet sur la matérialisation ne soit pas aussi explicite. cela a fonctionné pour moi

comme mentionné dans MySql Doc

PITU
la source
0

MySQL ne permet pas de sélectionner dans une table et de mettre à jour dans la même table en même temps. Mais il y a toujours une solution :)

Cela ne fonctionne pas >>>>

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

Mais cela fonctionne >>>>

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