您不能在FROM子句中指定要更新的目标表


379

我有一个简单的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);

我尝试运行以下更新,但仅收到错误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
))

我搜索了错误,然后从mysql http://dev.mysql.com/doc/refman/5.1/en/subquery-restrictions.html页面找到了,但这对我没有帮助。

我该怎么做才能更正sql查询?


Answers:


768

问题在于,无论出于何种原因,MySQL都不允许您编写如下查询:

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

也就是说,如果你正在做一个UPDATE/ INSERT/ DELETE桌子上,你不能引用在内部查询表(您可以但是从外部表引用一个字段...)


解决方案是myTable将子查询中的实例替换为(SELECT * FROM myTable),如下所示

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

显然,这会导致将必需的字段隐式复制到临时表中,因此可以使用。

我在这里找到了这个解决方案。该文章的注释:

您不想SELECT * FROM table在现实生活中只进入子查询;我只是想保持示例简单。实际上,您只应选择该最内部查询中所需的列,并添加一个良好的WHERE子句以限制结果。


10
我不认为原因是疯狂的。考虑一下语义。MySQL必须在更新开始前保留表的副本,否则内部查询可能会使用查询进行时已经更新的数据。这些副作用都不是必须的,因此最安全的选择是迫使您使用额外的表格来指定将要发生的事情。
siride

35
@siride:其他数据库(例如MSSQL或Oracle)没有此任意限制
BlueRaja-Danny Pflughoeft 2013年

3
@ BlueRaja-DannyPflughoeft:这不是任意的。根据替代方案的成本,这是合理的设计决策。其他数据库系统仍选择处理这些费用。但是这些系统不允许,例如,当您使用GROUP BY时,您可以在SELECT列表中包括未聚合的列,而MySQL可以。我认为MySQL在这里是错的,我可能会说其他DBMS的UPDATE语句也是如此。
siride

33
@siride从一个关系代数点,T并且(SELECT * FROM T)是完全等价的。他们是同一个关系。因此,这是一个任意的,无用的限制。更具体地说,这是一种变通方法,可以迫使MySQL执行它显然可以做的事情,但是由于某种原因,它无法以更简单的形式进行解析。
Tobia 2015年

4
就我而言,被接受的解决方案不起作用,因为我的桌子太大了。查询从未完成。显然,这占用了太多内部资源。相反,我使用内部查询创建了一个View并将其用于数据选择,这绝对好用。 DELETE FROM t WHERE tableID NOT IN (SELECT viewID FROM t_view);另外,我建议OPTIMIZE TABLE t;事后运行以减小表的大小。
CodeX

53

您可以分三个步骤进行操作:

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;

要么

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
是的,大多数子查询可以用CREATE TABLE语句重写为多个步骤-我希望作者意识到这一点。但是,这是唯一的解决方案吗?还是可以用子查询或联接重写查询?为什么(不)这样做?
Konerak 2010年

我认为您在第二个解决方案中有一个大写错误。不应该UPDATE Pers PUPDATE pers P吗?
ubiquibacon 2012年

2
尝试过这种解决方案,对于临时/秒表中的大量条目,查询可能非常慢;尝试使用索引/主键创建临时/第二张表[请参见dev.mysql.com/doc/refman/5.1/en/create-table-select.html ]
Alex

正如@Konerak所说,这实际上不是最佳答案。下面的BlueRaja答案对我来说似乎最好。投票似乎同意。
ShatyUT 2014年

@Konerak,CREATE TABLE AS SELECT表现不佳吗?
Pacerier

27

在Mysql中,不能通过子查询同一表来更新一个表。

您可以将查询分为两部分,也可以

 UPDATE TABLE_A AS A
 A.field1 = B.field1上的INNER JOIN TABLE_A AS B
 SET栏位2 =? 

5
SELECT ... SET?我从未听说过。
Serge S.

@grisson感谢您的澄清。现在我明白了为什么我的IN子句不起作用-我的目标是同一张表。
安东尼

2
...这似乎并不实际。它仍然给我同样的错误。
BlueRaja-Danny Pflughoeft13年

2
这个答案实际上做的更正确,更有效,这是AS B对的第二次引用TABLE_A。可以使用AS T代替潜在的低效率来简化最受赞扬的示例中的答案FROM (SELECT * FROM myTable) AS something,所幸的是查询优化器通常会消除这种情况,但可能并非总是如此。
natbro 2013年

23

通过子查询创建临时表(tempP)

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
)

我引入了一个单独的名称(别名),并为临时表的“ persID”列指定了新名称


为什么不将值选择为变量而不是进行内部内部内部选择?
Pacerier

SELECT ( SELECT MAX(gehalt * 1.05)..-第一个SELECT不选择任何列。
Istiaque Ahmed

18

这很简单。例如,代替编写:

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

你应该写

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

或类似。


13

BlueRaja发布的方法很慢,因为我曾经使用它从表中删除重复项,所以我对其进行了修改。万一它可以帮助任何拥有大表的人原始查询

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

这需要更多时间:

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

更快的解决方案

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

如果您不赞成,请添加评论。
Ajak6


3

如果您尝试从表A中读取fieldA并将其保存在同一表的fieldB上,则当fieldc = fielded时,您可能需要考虑这一点。

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

当条件字段满足您的条件时,以上代码将值从fieldA复制到fieldB。这也可以在ADO中使用(例如access)

资料来源:尝试过


3

MariaDB已从10.3.x(DELETE和和UPDATE)开始取消了此操作:

更新-具有相同源和目标的语句

从MariaDB 10.3.2起,UPDATE语句可能具有相同的源和目标。

在MariaDB 10.3.1之前,以下UPDATE语句将不起作用:

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

从MariaDB 10.3.2起,该语句成功执行:

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

删除-相同的源表和目标表

在MariaDB 10.3.1之前,无法从具有相同源和目标的表中删除。从MariaDB 10.3.1开始,这已经成为可能。例如:

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

DBFiddle MariaDB 10.2-错误

DBFiddle MariaDB 10.3-成功


0

其他变通办法包括在子查询中使用SELECT DISTINCT或LIMIT,尽管它们对实现的影响不那么明显。这对我有用

如MySql Doc中所述

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.