Erreur MySQL 1093 - Impossible de spécifier la table cible pour la mise à jour dans la clause FROM

594

J'ai une table story_categorydans ma base de données avec des entrées corrompues. La requête suivante renvoie les entrées corrompues:

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

J'ai essayé de les supprimer en exécutant:

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

Mais j'obtiens l'erreur suivante:

# 1093 - Vous ne pouvez pas spécifier la table cible 'story_category' pour la mise à jour dans la clause FROM

Comment puis-je surmonter cela?

Sergio del Amo
la source
3
Connexe: stackoverflow.com/a/14302701/238419
BlueRaja - Danny Pflughoeft
2
On dirait que la demande de fonctionnalité dans le suivi des bogues MySQL est ici: ne peut pas mettre à jour une table et sélectionner à partir de la même table dans une sous
Ben Creasy

Réponses:

714

Mise à jour: cette réponse couvre la classification générale des erreurs. Pour une réponse plus spécifique sur la meilleure façon de traiter la requête exacte de l'OP, veuillez consulter les autres réponses à cette question

Dans MySQL, vous ne pouvez pas modifier la même table que vous utilisez dans la partie SELECT.
Ce comportement est documenté à: http://dev.mysql.com/doc/refman/5.6/en/update.html

Peut-être que vous pouvez simplement joindre la table à elle-même

Si la logique est suffisamment simple pour remodeler la requête, perdez la sous-requête et joignez la table à elle-même, en utilisant des critères de sélection appropriés. Cela amènera MySQL à voir la table comme deux choses différentes, permettant aux changements destructifs d'aller de l'avant.

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

Vous pouvez également essayer d'imbriquer la sous-requête plus profondément dans une clause from ...

Si vous avez absolument besoin de la sous-requête, il existe une solution de contournement, mais elle est moche pour plusieurs raisons, notamment les performances:

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

La sous-requête imbriquée dans la clause FROM crée une table temporaire implicite , donc elle ne compte pas comme la même table que vous mettez à jour.

... mais attention à l'optimiseur de requêtes

Cependant, sachez qu'à partir de MySQL 5.7.6 et versions ultérieures, l'optimiseur peut optimiser la sous-requête et toujours vous donner l'erreur. Heureusement, la optimizer_switchvariable peut être utilisée pour désactiver ce comportement; bien que je ne puisse pas recommander de faire cela comme quelque chose de plus qu'une solution à court terme ou pour de petites tâches ponctuelles.

SET optimizer_switch = 'derived_merge=off';

Merci à Peter V. Mørch pour ce conseil dans les commentaires.

L'exemple de technique provenait du baron Schwartz, initialement publié chez Nabble , paraphrasé et développé ici.

Cheekysoft
la source
1
J'ai voté pour cette réponse parce que j'ai dû supprimer des éléments et que je ne pouvais pas obtenir d'informations d'une autre table, j'ai dû sous-interroger la même table. Étant donné que c'est ce qui s'affiche en recherchant l'erreur que j'ai obtenue, ce serait la meilleure réponse pour moi et beaucoup de gens essayant de mettre à jour tout en sous-interrogeant à partir du même tableau.
HMR
2
@Cheekysoft, Pourquoi ne pas enregistrer les valeurs dans des variables à la place?
Pacerier
19
Attention, à partir de MySQL 5.7.6 , l'optimiseur peut optimiser la sous-requête et vous donner toujours l'erreur, sauf si vous SET optimizer_switch = 'derived_merge=off';:-(
Peter V. Mørch
1
@ PeterV.Mørch Après mysqlserverteam.com/derived-tables-in-mysql-5-7 , dans le cas où certaines opérations sont effectuées, la fusion ne peut pas se produire. Par exemple, fournissez la table factice dérivée avec une LIMITE (à l'inifité) et l'erreur ne se produira jamais. C'est assez hackeux cependant et il y a toujours le risque que les futures versions de MySQL prennent en charge la fusion des requêtes avec LIMIT après tout.
user2180613
Pouvez-vous, s'il vous plaît, fournir un exemple complet de cette solution de contournement? UPDATE tbl SET col = (SELECT ... FROM (SELECT .... FROM) AS x); je reçois toujours des erreurs
JoelBonetR
310

NexusRex a fourni une très bonne solution pour la suppression avec jointure de la même table.

Si tu fais ça:

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
)

vous allez obtenir une erreur.

Mais si vous enveloppez la condition dans une autre sélection:

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
)

ça ferait la bonne chose !!

Explication: L'optimiseur de requêtes effectue une optimisation de fusion dérivée pour la première requête (ce qui entraîne son échec avec l'erreur), mais la deuxième requête n'est pas admissible à l' optimisation de fusion dérivée . Par conséquent, l'optimiseur est obligé d'exécuter la sous-requête en premier.

Ekonoval
la source
6
C'est peut-être parce que je suis dans une impasse aujourd'hui, mais c'était la réponse la plus simple, même si ce n'est peut-être pas la "meilleure".
Tyler V.
4
Cela a très bien fonctionné, merci! Alors, quelle est la logique ici? S'il est imbriqué d'un niveau de plus, il sera exécuté avant la partie externe? Et s'il n'est pas imbriqué, mySQL essaie de l'exécuter après la suppression d'un verrou sur la table?
Flat Cat
43
Cette erreur et cette solution n'ont aucun sens logique ... mais cela fonctionne. Parfois, je me demande quels médicaments les développeurs MySQL utilisent ...
Cerin
1
D'accord avec @Cerin .. c'est complètement absurde, mais ça marche.
FastTrack
@ekonoval Merci pour la solution mais cela n'a pas le minimum de sens pour moi, on dirait que vous trompez MySQL et il l'accepte, lol
deFreitas
106

Le inner joindans votre sous-requête n'est pas nécessaire. Il semble que vous souhaitiez supprimer les entrées story_categorylà où le category_idn'est pas dans le categorytableau.

Faites ceci:

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

Au lieu de:

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
la source
5
Cela devrait être la meilleure réponse! Supprimez peut-être le premier "au lieu de".
hoyhoy
1
Je pense que DISTINCTc'est inutile ici - pour une meilleure performance;).
shA.t
1
Je dois être fou. La réponse est la même que celle indiquée dans l'original.
Jeff Lowery
C'est aussi la réponse pour moi. Ne where inle faites pas dans la colonne ID, vous n'avez donc pas besoin de sous-interroger la table principale.
Richard
@JeffLowery - le premier bloc de code est la réponse ici; c'est le deuxième bloc de code qui provient de la question.
ToolmakerSteve
95

Récemment, j'ai dû mettre à jour des enregistrements dans le même tableau que je l'ai fait ci-dessous:

UPDATE skills AS s, (SELECT id  FROM skills WHERE type = 'Programming') AS p
SET s.type = 'Development' 
WHERE s.id = p.id;
Pratik Khadloya
la source
11
Cela ne peut-il pas simplement s'écrire UPDATE skills SET type='Development' WHERE type='Programming';? Cela ne semble pas répondre à la question d'origine.
lilbyrdie
1
On dirait que c'est exagéré, @lilbyrdie est correct - cela ne peut être que UPDATE skills SET type='Development' WHERE type='Programming';. Je ne comprends pas pourquoi tant de gens ne pensent pas à ce qu'ils font ...
shadyyx
1
c'est la meilleure réponse ici, à mon humble avis. Sa syntaxe est facile à comprendre, vous pouvez réutiliser votre déclaration précédente et elle n'est pas limitée à certains cas super spécifiques.
Steffen Winkler
2
Même si l'exemple de cas est discutable, le principe est le meilleur à comprendre et à déployer dans des scénarios du monde réel. La requête affichée fonctionne car la jointure implicite dans la liste de table crée une table temporaire pour une sous-requête, fournissant ainsi des résultats stables et évitant les conflits entre la récupération et la modification de la même table.
AnrDaemon
1
indépendamment de ce que quelqu'un pourrait dire à propos de cette exagération, cela répond toujours à la question en termes de titre. L'erreur mentionnée par l'OP est également rencontrée lors d'une tentative de mise à jour à l'aide de la même table dans une requête imbriquée
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
)
NexusRex
la source
3
Pouvez-vous expliquer pourquoi cela fonctionne, pourquoi simplement imbriquer un autre niveau fonctionne? Cette question a déjà été posée en tant que commentaire à la question de @ EkoNoval mais personne n'a répondu. Vous pouvez peut-être nous aider.
Akshay Arora du
@AkshayArora, Parcourez la partie sous la rubrique «Peut-être que vous pouvez simplement joindre la table à elle-même» dans la réponse de @ Cheekysoft. Il a dit UPDATE tbl AS a INNER JOIN tbl AS b ON .... SET a.col = b.col- cela fonctionnerait comme un alias différent pour la même table est utilisée ici. De même, dans la réponse de @ NexusRex, la première SELECTrequête agit comme une table dérivée dans laquelle story_categoryest utilisée pour la deuxième fois. Donc, l'erreur mentionnée dans le PO ne devrait pas se produire ici, non?
Istiaque Ahmed
31

Si tu ne peux pas faire

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

parce que c'est la même table, vous pouvez tromper et faire:

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

[mettre à jour ou supprimer ou autre]

Sequoya
la source
Implémentation la plus compréhensible et logique de toutes les réponses ci-dessus. Simple et précis.
Clain Dsilva
Cela fait maintenant partie de mon flux de travail. Restez bloqué sur cette erreur, rendez-vous sur cette réponse et rectifiez la requête. Merci.
Vaibhav
13

C'est ce que j'ai fait pour mettre à jour une valeur de colonne Priority de 1 si elle est> = 1 dans une table et dans sa clause WHERE en utilisant une sous-requête sur la même table pour vous assurer qu'au moins une ligne contient Priority = 1 (car c'était le condition à vérifier lors de la mise à jour):


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

Je sais que c'est un peu moche mais ça marche bien.

sactiw
la source
1
@anonymous_reviewer: En cas de donner [-1] ou même [+1] au commentaire de quelqu'un, veuillez également mentionner pourquoi vous l'avez donné. Merci!!!
sactiw
1
-1 car c'est incorrect. Vous ne pouvez pas modifier la même table que vous utilisez dans l'instruction SELECT.
Chris
1
@Chris Je l'ai vérifié sur MySQL et cela fonctionne très bien pour moi, donc je vous demanderais de le vérifier de votre côté, puis de le déclarer correct ou incorrect. Merci!!!
sactiw
1
Au bas de cette page, il est écrit "Actuellement, vous ne pouvez pas mettre à jour une table et sélectionner à partir de la même table dans une sous-requête". - et j'ai vécu cela à plusieurs reprises. dev.mysql.com/doc/refman/5.0/en/update.html
Chris
19
@Chris Je le sais, mais il existe une solution pour cela et c'est exactement ce que j'ai essayé de montrer avec ma requête "UPDATE" et croyez-moi, cela fonctionne très bien. Je ne pense pas que vous ayez vraiment essayé de vérifier ma requête.
sactiw
6

La façon la plus simple de procéder consiste à utiliser un alias de table lorsque vous faites référence à une table de requête parent dans la sous-requête.

Exemple :

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

Changez-le en:

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

Vous pouvez insérer les identifiants des lignes souhaitées dans une table temporaire, puis supprimer toutes les lignes trouvées dans cette table.

ce qui peut être ce que @Cheekysoft voulait dire en le faisant en deux étapes.

YonahW
la source
3

Selon la syntaxe de mise à jour Mysql liée par @CheekySoft, il est indiqué tout en bas.

Actuellement, vous ne pouvez pas mettre à jour une table et sélectionner à partir de la même table dans une sous-requête.

Je suppose que vous supprimez de store_category tout en le sélectionnant dans l'union.

briankip
la source
3

Pour la requête spécifique que l'OP tente de réaliser, le moyen idéal et le plus efficace de le faire est de ne PAS utiliser de sous-requête.

Voici les LEFT JOINversions des deux requêtes de l'OP:

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

Remarque: DELETE slimite les opérations de suppression à la story_categorytable.
Documentation

DELETE s 
FROM story_category s 
LEFT JOIN category c 
ON c.id=s.category_id 
WHERE c.id IS NULL;
Doin
la source
1
Surpris, cela n'a pas plus de votes positifs. Il convient également de noter que la syntaxe multi-table fonctionne également avec les UPDATEinstructions et les sous-requêtes jointes. Vous permettant d'effectuer LEFT JOIN ( SELECT ... )plutôt que de WHERE IN( SELECT ... )rendre l'implémentation utile dans de nombreux cas d'utilisation.
fyrye
2

Si quelque chose ne fonctionne pas, en passant par la porte d'entrée, prenez la porte arrière:

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;

C'est rapide. Plus les données sont volumineuses, mieux c'est.

Tom Schaefer
la source
11
Et vous venez de perdre toutes vos clés étrangères, et vous avez peut-être aussi des suppressions en cascade.
Walf
2

Essayez d'enregistrer le résultat de l'instruction Select dans une variable distincte, puis utilisez-le pour supprimer la requête.

lipika chakraborty
la source
2

essaye ça

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

que diriez-vous de cette requête j'espère que cela aide

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
la source
Le résultat montre: '# 1064 - Vous avez une erreur dans votre syntaxe SQL; consultez le manuel qui correspond à la version de votre serveur MariaDB pour la bonne syntaxe à utiliser près de 'LEFT JOIN (SELECT categories.id FROM categories) cat ON story_category.id = cat.' à la ligne 1 '
Istiaque Ahmed
Lorsque vous utilisez la suppression de plusieurs tables, vous devez spécifier les tables concernées. DELETE story_category FROM ...cependant, la sous-requête jointe n'est pas nécessaire dans ce contexte et peut être effectuée à l'aide de LEFT JOIN category AS cat ON cat.id = story_category.category_id WHERE cat.id IS NULL, Notez que les critères de jointure dans la réponse font référence de manière incorrectestory_category.id = cat.id
fyrye
0

En ce qui concerne les préoccupations, vous souhaitez supprimer les lignes story_categoryqui n'existent pas dans category.

Voici votre requête d'origine pour identifier les lignes à supprimer:

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 combinaison NOT INavec une sous-requête qui JOINest la table d'origine semble inutilement alambiquée. Cela peut être exprimé de manière plus directe avec not existset une sous-requête corrélée:

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

Maintenant, il est facile de transformer cela en une deletedéclaration:

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

Cette requête s'exécuterait sur n'importe quelle version de MySQL, ainsi que dans la plupart des autres bases de données que je connais.

Démo sur 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
la source