Você não pode especificar a tabela de destino para atualização na cláusula FROM

380

Eu tenho uma tabela mysql simples:

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

Tentei executar a atualização seguinte, mas recebo apenas o erro 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
))

Procurei o erro e encontrei no mysql a seguinte página http://dev.mysql.com/doc/refman/5.1/en/subquery-restrictions.html , mas isso não me ajuda.

O que devo fazer para corrigir a consulta sql?

CSchulz
fonte

Respostas:

769

O problema é que o MySQL, por qualquer motivo insano, não permite que você escreva consultas como esta:

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

Ou seja, se você estiver fazendo um UPDATE/ INSERT/ DELETEem uma tabela, não poderá fazer referência a essa tabela em uma consulta interna ( no entanto, você pode fazer referência a um campo dessa tabela externa ...)


A solução é substituir a instância da myTablesubconsulta por (SELECT * FROM myTable), assim:

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

Aparentemente, isso faz com que os campos necessários sejam copiados implicitamente em uma tabela temporária, portanto é permitido.

Encontrei esta solução aqui . Uma observação desse artigo:

Você não quer apenas SELECT * FROM tablena subconsulta na vida real; Eu só queria manter os exemplos simples. Na realidade, você deve selecionar apenas as colunas necessárias na consulta mais interna e adicionar uma boa WHEREcláusula para limitar os resultados também.

BlueRaja - Danny Pflughoeft
fonte
10
Não acho que o motivo seja insano. Pense na semântica. O MySQL precisa manter uma cópia da tabela antes do início da atualização, ou a consulta interna pode usar dados que já foram atualizados pela consulta à medida que ela está em andamento. Nenhum desses efeitos colaterais é necessariamente desejável; portanto, a aposta mais segura é forçar você a especificar o que acontecerá usando uma tabela extra.
siride
35
@siride: Outras bases de dados, tais como MSSQL ou Oracle, não têm essa restrição arbitrária
BlueRaja - Danny Pflughoeft
3
@ BlueRaja-DannyPflughoeft: não é arbitrário. É uma decisão de design razoável com base nos custos das alternativas. Os outros sistemas de banco de dados optaram por lidar com esses custos de qualquer maneira. Mas esses sistemas, por exemplo, não permitem incluir colunas não agregadas nas listas SELECT quando você usa GROUP BY, e o MySQL o faz. Eu diria que o MySQL está errado aqui, e posso dizer o mesmo dos outros DBMSs para instruções UPDATE.
siride
33
@siride De um ponto de vista álgebra relacional, Te (SELECT * FROM T)são completamente equivalentes. Eles são a mesma relação. Portanto, essa é uma restrição arbitrária e sem sentido. Mais especificamente, é uma solução alternativa para coagir o MySQL a fazer algo que claramente pode fazer, mas, por algum motivo, não pode analisar em sua forma mais simples.
Jania
4
No meu caso, a solução aceita não funcionou porque minha tabela era simplesmente muito grande. A consulta nunca foi concluída. Aparentemente, isso está consumindo muitos recursos internos. Em vez disso, criei uma View com a consulta interna e a usei para a seleção de dados, que funcionou absolutamente bem. DELETE FROM t WHERE tableID NOT IN (SELECT viewID FROM t_view);Também recomendo correr OPTIMIZE TABLE t;depois para reduzir o tamanho da tabela.
CodeX
53

Você pode fazer isso em três etapas:

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
fonte
16
Bem, sim, a maioria das subconsultas pode ser reescrita como várias etapas com CREATE TABLEinstruções - espero que o autor esteja ciente disso. No entanto, esta é a única solução? Ou a consulta pode ser reescrita com subconsultas ou junções? E por que (não) faz isso?
Konerak
Eu acho que você tem um erro de capitalização na sua segunda solução. Não deveria UPDATE Pers Pler UPDATE pers P?
Ubiquibacon
2
Tentei esta solução e, para um grande número de entradas na tabela temporária / segunda, a consulta pode ser muito lenta; tentar criar a segunda tabela temporária / com uma chave de índice / primário [ver dev.mysql.com/doc/refman/5.1/en/create-table-select.html ]
Alex
Como afirma @Konerak, essa não é realmente a melhor resposta. A resposta do BlueRaja abaixo me parece melhor. Upvotes parecem concordar.
ShatyUT
@ Konerak, não CREATE TABLE AS SELECTdá um desempenho horrível?
Pacerier
27

No Mysql, você não pode atualizar uma tabela subconsultando a mesma tabela.

Você pode separar a consulta em duas partes ou fazer

 ATUALIZAR TABLE_A COMO A
 TABELA DE JUNÇÃO INTERNA_A COMO B ON A.field1 = B.field1
 SET campo2 =? 
Yuantao
fonte
5
SELECT ... SET? Eu nunca ouvi falar disso.
Serge S.
@grisson Obrigado pelo esclarecimento. Agora entendo por que minha cláusula IN não funciona - eu estava segmentando a mesma tabela.
Anthony
2
... isso não parece realmente funcionar. Ainda está me dando o mesmo erro.
precisa saber é o seguinte
2
Essa resposta realmente faz a coisa mais correta e eficiente, que está sendo usada AS Bna segunda referência a TABLE_A. a resposta no exemplo mais votado pode ser simplificada usando em AS Tvez do potencialmente ineficiente FROM (SELECT * FROM myTable) AS something, que felizmente o otimizador de consultas normalmente elimina, mas nem sempre o faz.
natbro
23

Criar uma tabela temporária (tempP) a partir de uma subconsulta

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
)

Introduzi um nome separado (alias) e atribui um novo nome à coluna 'persID' da tabela temporária

Budda
fonte
Por que não selecionar os valores em variáveis ​​em vez de fazer seleções internas internas internas?
Pacerier
SELECT ( SELECT MAX(gehalt * 1.05)..- o primeiro SELECTnão seleciona nenhuma coluna.
Istiaque Ahmed
18

É bem simples Por exemplo, em vez de escrever:

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

você deveria escrever

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

Lado escuro
fonte
13

A Abordagem publicada por BlueRaja é lenta. Modifiquei-a como estava usando para excluir duplicatas da tabela. Caso ajude qualquer pessoa com tabelas grandes Consulta Original

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

Isso está levando mais tempo:

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

Solução mais rápida

DELETE FROM table where ID NOT IN(
   SELECT x.Id from (SELECT MIN(Id) as Id from table GROUP BY field2) AS t)
Ajak6
fonte
Adicione um comentário se você estiver com voto negativo.
Ajak6 02/02
3

Se você estiver tentando ler o campoA da tabelaA e salvá-lo no campoB na mesma tabela, quando o campoc = campod, convém considerar isso.

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

O código acima copia o valor do campo A para o campo B quando o campo de condição atendeu à sua condição. isso também funciona no ADO (por exemplo, acesso)

fonte: tentei-me

Krish
fonte
3

O MariaDB levantou isso a partir do 10.3.x (para DELETEe UPDATE):

UPDATE - Instruções com a mesma fonte e destino

No MariaDB 10.3.2, as instruções UPDATE podem ter a mesma fonte e destino.

Até o MariaDB 10.3.1, a seguinte instrução UPDATE não funcionava:

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

No MariaDB 10.3.2, a instrução é executada com êxito:

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

DELETE - Mesma tabela de origem e destino

Até o MariaDB 10.3.1, a exclusão de uma tabela com a mesma origem e destino não era possível. A partir do MariaDB 10.3.1, isso agora é possível. Por exemplo:

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

DBFiddle MariaDB 10.2 - Erro

DBFiddle MariaDB 10.3 - Sucesso

Lukasz Szozda
fonte
0

Outras soluções alternativas incluem o uso de SELECT DISTINCT ou LIMIT na subconsulta, embora não sejam tão explícitos em seus efeitos na materialização. isso funcionou para mim

como mencionado no MySql Doc

PITU
fonte
0

O MySQL não permite selecionar de uma tabela e atualizar na mesma tabela ao mesmo tempo. Mas sempre há uma solução alternativa :)

Isso não funciona >>>>

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

Mas isso funciona >>>>

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