Erro MySQL 1093 - Não é possível especificar a tabela de destino para atualização na cláusula FROM


593

Eu tenho uma tabela story_categoryno meu banco de dados com entradas corrompidas. A próxima consulta retorna as entradas corrompidas:

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

Eu tentei excluí-los executando:

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

Mas eu recebo o próximo erro:

# 1093 - Você não pode especificar a tabela de destino 'story_category' para atualização na cláusula FROM

Como posso superar isso?



2
Parece que o pedido de recurso no rastreador MySQL bug está aqui: não é possível atualizar a tabela e selecione da mesma tabela em uma subconsulta
Ben Creasy

Respostas:


713

Atualização: Esta resposta cobre a classificação geral de erros. Para obter uma resposta mais específica sobre como lidar melhor com a consulta exata do OP, consulte outras respostas a esta pergunta

No MySQL, você não pode modificar a mesma tabela que você usa na parte SELECT.
Esse comportamento está documentado em: http://dev.mysql.com/doc/refman/5.6/en/update.html

Talvez você possa simplesmente juntar a mesa a si próprio

Se a lógica for simples o suficiente para remodelar a consulta, perca a subconsulta e junte a tabela a ela mesma, empregando critérios de seleção apropriados. Isso fará com que o MySQL veja a tabela como duas coisas diferentes, permitindo que mudanças destrutivas ocorram.

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

Como alternativa, tente aninhar a subconsulta mais profundamente na cláusula from ...

Se você absolutamente precisa da subconsulta, há uma solução alternativa, mas é feia por vários motivos, incluindo desempenho:

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

A subconsulta aninhada na cláusula FROM cria uma tabela temporária implícita , portanto não conta como a mesma tabela que você está atualizando.

... mas cuidado com o otimizador de consultas

No entanto, tenha cuidado com o MySQL 5.7.6 em diante, o otimizador pode otimizar a subconsulta e ainda assim fornecer o erro. Felizmente, a optimizer_switchvariável pode ser usada para desativar esse comportamento; embora eu não possa recomendar fazer isso como algo além de uma correção de curto prazo ou para pequenas tarefas pontuais.

SET optimizer_switch = 'derived_merge=off';

Obrigado a Peter V. Mørch por esse conselho nos comentários.

A técnica de exemplo foi do Barão Schwartz, originalmente publicada em Nabble , parafraseada e estendida aqui.


1
Promoveu esta resposta porque eu tive que excluir itens e não consegui obter informações de outra tabela, tive que subconsultar da mesma tabela. Como é isso que aparece no topo ao pesquisar o erro que recebi, essa seria a resposta mais adequada para mim e para muitas pessoas que tentavam atualizar enquanto subqueriam da mesma tabela.
HMR

2
@ Cheekysoft, por que não salvar os valores em variáveis?
Pacerier

19
Cuidado, que a partir do MySQL 5.7.6 , o otimizador pode otimizar a subconsulta e ainda assim fornecer o erro, a menos que você SET optimizer_switch = 'derived_merge=off';:-(
Peter V. Mørch 6/15/15

1
@ PeterV.Mørch Seguindo mysqlserverteam.com/derived-tables-in-mysql-5-7 , no caso de certas operações serem realizadas, a mesclagem não pode acontecer. Por exemplo, forneça à tabela fictícia derivada um LIMIT (inifity) e o erro nunca ocorrerá. Isso é bastante tolo e ainda existe o risco de que versões futuras do MySQL suportem a fusão de consultas com o LIMIT, afinal.
user2180613

Você pode, por favor, fornecer um exemplo completo sobre esta solução alternativa? UPDATE tbl SET col = (SELECIONE ... DESDE (SELECIONE .... DES) COMO x); Eu ainda estou recebendo erros
JoelBonetR

310

O NexusRex forneceu uma solução muito boa para excluir com junção da mesma tabela.

Se você fizer isto:

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
)

você receberá um erro.

Mas se você agrupar a condição em mais uma, selecione:

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
)

faria a coisa certa !!

Explicação: O otimizador de consulta faz uma otimização de mesclagem derivada para a primeira consulta (o que causa falha no erro), mas a segunda consulta não se qualifica para a otimização de mesclagem derivada . Portanto, o otimizador é forçado a executar a subconsulta primeiro.


6
Talvez seja porque eu estou com problemas hoje, mas essa foi a resposta mais fácil, mesmo que talvez não seja a "melhor".
Tyler V.

4
Isso funcionou muito bem, obrigado! Então, qual é a lógica aqui? Se estiver aninhado mais um nível, será executado antes da parte externa? E se não estiver aninhado, o mySQL tentará executá-lo após a exclusão ter um bloqueio na tabela?
Flat Cat

43
Este erro e solução não fazem sentido lógico ... mas funciona. Às vezes me pergunto o que as drogas os devs MySQL estão em ...
Cerin

1
Concordo com @Cerin .. isso é completamente absurdo, mas funciona.
FastTrack 28/06

@ekonoval Obrigado pela solução, mas isso não faz o mínimo sentido para mim, parece que você está enganando o MySQL e ele aceita isso, rsrs
deFreitas

106

A inner joinsubconsulta é desnecessária. Parece que você deseja excluir as entradas em story_categoryque category_idnão estão na categorytabela.

Faça isso:

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

Ao invés disso:

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
Essa deve ser a melhor resposta! Talvez exclua o primeiro "em vez de".
hoyhoy

1
Eu acho que DISTINCTé desnecessário aqui - para um melhor desempenho;).
shA.t

1
Eu devo estar louco. A resposta é a mesma que foi declarada no original.
Jeff Lowery

Esta é a resposta para mim também. Não where inna coluna ID, para que você não precise subconsultar a tabela principal.
Richard

@ Jeffffower - o primeiro bloco de código é a resposta aqui; é o segundo bloco de código que é da pergunta.
Home

95

Recentemente, tive que atualizar registros na mesma tabela que fiz como abaixo:

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

11
Isso não pode ser apenas escrito como UPDATE skills SET type='Development' WHERE type='Programming';? Isso não parece estar respondendo à pergunta original.
Lilbyrdie

1
Parece exagero, @lilbyrdie está correto - só poderia ser UPDATE skills SET type='Development' WHERE type='Programming';. Eu não entendo por que tantas pessoas não está pensando sobre o que fazer ...
shadyyx

1
esta é a melhor resposta aqui, IMHO. Sua sintaxe é fácil de entender, você pode reutilizar sua declaração anterior e não se limita a alguns casos super específicos.
Steffen Winkler

2
Mesmo que o caso de exemplo seja questionável, o princípio é o melhor para entender e implantar em cenários do mundo real. A consulta mostrada funciona porque a junção implícita na lista de tabelas cria uma tabela temporária para uma subconsulta, fornecendo resultados estáveis ​​e evitando conflitos entre recuperação e modificação da mesma tabela.
AnrDaemon

1
independentemente do que alguém possa dizer sobre esse exagero, ele ainda responde à pergunta em termos de título. O erro mencionado pelo OP também é encontrado quando uma atualização é tentada usando a mesma tabela em uma consulta aninhada
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
Você pode explicar por que isso funciona, por que apenas aninhar mais um nível funciona? Esta pergunta já foi feita como um comentário à pergunta de @ EkoNoval, mas ninguém respondeu. Talvez você possa ajudar.
Akshay Arora 27/01

@AkshayArora, consulte a parte sob o título 'Talvez você possa se unir à mesa' na resposta da @ Cheekysoft. Ele disse UPDATE tbl AS a INNER JOIN tbl AS b ON .... SET a.col = b.col- isso funcionaria como um apelido diferente para a mesma tabela que é usada aqui. Da mesma forma, na resposta do @ NexusRex, a primeira SELECTconsulta atua como uma tabela derivada na qual story_categoryé usada pela segunda vez. Portanto, o erro mencionado no OP não deve ocorrer aqui, certo?
Istiaque Ahmed

31

Se você não pode fazer

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

porque é a mesma tabela, você pode enganar e fazer:

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

[atualizar ou excluir ou o que for]


Implementação mais compreensível e lógica de todas as respostas acima. Simples e direto ao ponto.
Clain Dsilva

Isso se tornou parte do meu fluxo de trabalho agora. Fique preso neste erro, vá para esta resposta e retifique a consulta. Obrigado.
Vaibhav 4/03

13

Foi o que fiz para atualizar um valor da coluna Priority por 1 se for> = 1 em uma tabela e em sua cláusula WHERE usando uma subconsulta na mesma tabela para garantir que pelo menos uma linha contenha Priority = 1 (porque esse era o condição a ser verificada durante a atualização):


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

Eu sei que é um pouco feio, mas funciona bem.


1
@anonymous_reviewer: no caso de dar [-1] ou até [+1] ao comentário de alguém, mencione também por que você o deu. Obrigado!!!
sactiw

1
-1 porque isso está incorreto. Você não pode modificar a mesma tabela que você usa na instrução SELECT.
Chris

1
@ Chris Eu verifiquei no MySQL e ele funciona muito bem para mim, portanto, solicite que você verifique no final e depois afirme que está correto ou incorreto. Obrigado!!!
sactiw

1
Na parte inferior desta página, diz 'Atualmente, você não pode atualizar uma tabela e selecionar a mesma tabela em uma subconsulta.' - e já experimentei isso em muitas ocasiões. dev.mysql.com/doc/refman/5.0/en/update.html
Chris

19
@ Chris, eu sei disso, mas há uma solução alternativa para isso e é exatamente o que eu tentei mostrar com minha consulta 'UPDATE' e, acredite, funciona bem. Acho que você realmente não tentou verificar minha consulta.
sactiw

6

A maneira mais simples de fazer isso é usar um alias de tabela quando você estiver consultando a tabela de consulta pai dentro da subconsulta.

Exemplo:

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

Altere para:

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

5

Você pode inserir os IDs das linhas desejadas em uma tabela temporária e excluir todas as linhas encontradas nessa tabela.

que pode ser o que a @Cheekysoft quis dizer com isso em duas etapas.


3

De acordo com o Mysql UPDATE Syntax, vinculado por @CheekySoft, está escrito na parte inferior.

Atualmente, você não pode atualizar uma tabela e selecionar a mesma tabela em uma subconsulta.

Eu acho que você está excluindo da store_category enquanto ainda a seleciona na união.


3

Para a consulta específica que o OP está tentando realizar, a maneira ideal e mais eficiente de fazer isso é NÃO usar uma subconsulta.

Aqui estão as LEFT JOINversões das duas consultas do OP:

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

Nota: DELETE srestringe as operações de exclusão à story_categorytabela.
Documentação

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

1
Surpreendeu que isso não tenha mais votos positivos. Também deve ser observado que a sintaxe de várias tabelas também funciona com UPDATEinstruções e subconsultas associadas. Permitindo que você execute LEFT JOIN ( SELECT ... )o contrário WHERE IN( SELECT ... ), tornando a implementação útil em muitos casos de uso.
fyrye 9/04

2

Se algo não funcionar, ao passar pela porta da frente, pegue a porta dos fundos:

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;

É rápido. Quanto maiores os dados, melhor.


11
E você acabou de perder todas as suas chaves estrangeiras e talvez também tenha algumas exclusões em cascata.
Walf

2

Tente salvar o resultado da instrução Select em variável separada e use-o para excluir a consulta.


2

tente isso

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

1

que tal esta consulta espero que ajude

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

O resultado mostra: '# 1064 - Você tem um erro na sintaxe do SQL; verifique o manual que corresponde à versão do servidor MariaDB para obter a sintaxe correta perto de 'LEFT JOIN (SELECIONAR categories.id FROM categorias) cat ON story_category.id = cat.' na linha 1 '
Istiaque Ahmed 9/11

Ao usar a exclusão de várias tabelas, você deve especificar as tabelas afetadas. DELETE story_category FROM ...no entanto, a subconsulta associada não é necessária nesse contexto e pode ser executada usando LEFT JOIN category AS cat ON cat.id = story_category.category_id WHERE cat.id IS NULL: Observe os critérios de associação na resposta incorretamente referenciastory_category.id = cat.id
fyrye

0

No que diz respeito às preocupações, você deseja excluir as linhas story_categoryque não existem category.

Aqui está sua consulta original para identificar as linhas a serem excluídas:

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

Combinar NOT INcom uma subconsulta que JOINé a tabela original parece involuntariamente complicado. Isso pode ser expresso de uma maneira mais direta e com not existsuma subconsulta correlacionada:

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

Agora é fácil transformar isso em uma deletedeclaração:

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

Essa consulta seria executada em qualquer versão do MySQL, assim como na maioria dos outros bancos de dados que eu conheço.

Demo no 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
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.