您无法在FROM子句中指定要更新的目标表

CSc*_*ulz 353 mysql sql mysql-error-1093

我有一个简单的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);
Run Code Online (Sandbox Code Playgroud)

我试图运行以下更新,但我只得到错误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
))
Run Code Online (Sandbox Code Playgroud)

我搜索了错误,并在http://dev.mysql.com/doc/refman/5.1/en/subquery-restrictions.html页面的mysql中找到,但它对我没有帮助.

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

Blu*_*eft 726

问题在于,无论出于什么原因,MySQL都不允许你编写这样的查询:

UPDATE myTable
SET myTable.A =
(
    SELECT B
    FROM myTable
    INNER JOIN ...
)
Run Code Online (Sandbox Code Playgroud)

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


该解决方案是更换的情况下myTable在子查询中使用(SELECT * FROM myTable),这样的

UPDATE myTable
SET myTable.A =
(
    SELECT B
    FROM (SELECT * FROM myTable) AS something
    INNER JOIN ...
)
Run Code Online (Sandbox Code Playgroud)

这显然会导致必要的字段被隐式复制到临时表中,所以它是允许的.

我在这里找到了解决方案.该文的说明:

你不想只是SELECT * FROM table在现实生活中的子查询中; 我只是想让这些例子保持简单.实际上,您应该只在最内层查询中选择所需的列,并添加一个good WHERE子句来限制结果.

  • @siride:其他数据库,如MSSQL或Oracle,没有这种任意限制 (31认同)
  • @siride从关系代数的角度来看,`T`和`(SELECT*FROM T)`是完全等价的.它们是相同的关系.因此,这是一种任意的,无限制的限制.更具体地说,它是一种解决方法,可以强迫MySQL做一些它显然可以做的事情,但由于某些原因它无法以更简单的形式解析. (28认同)
  • 我认为原因并不是真的.想想语义.MySQL必须在更新开始之前保留表的副本,否则内部查询可能会使用已在查询过程中更新的数据.这些副作用都不一定是理想的,所以最安全的做法是强迫你指定使用额外表格会发生什么. (10认同)
  • 在我的情况下,接受的解决方案不起作用,因为我的桌子太大了.查询从未完成.显然这需要太多的内部资源.相反,我使用内部查询创建了一个View,并将其用于数据选择,这非常合适.`DELETE FROM t WHERE tableID NOT IN(SELECT viewID FROM t_view);`我还建议运行`OPTIMIZE TABLE t;`然后减小表的大小. (4认同)
  • @ BlueRaja-DannyPflughoeft:这不是随意的.这是基于替代品成本的合理设计决策.其他数据库系统无论如何都选择处理这些成本.但是,例如,当您使用GROUP BY时,这些系统不允许您在SELECT列表中包含非聚合列,而MySQL则允许.我认为MySQL在这里是错误的,我可能会说UPDATE语句的其他DBMS也是如此. (3认同)
  • 请注意,从[MySQL 5.7.6 on](http://dev.mysql.com/doc/relnotes/mysql/5.7/en/news-5-7-6.html)开始,优化器可能会优化查询掉并仍然给您错误,除非您`SEToptimizer_switch ='derived_merge = off';`:-( (3认同)
  • @Tobia:对于记录,SQL Server在所有情况下都正确地假脱机表数据,所以它没有这个限制. (2认同)
  • 毫无疑问,这只是一个巧合,当您在2016-02-18 20:18:50Z编辑答案时,我正坐在此页面上阅读您的答案。自2013年以来未进行过编辑。 (2认同)

Mic*_*sov 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
  )
)
Run Code Online (Sandbox Code Playgroud)

...

UPDATE pers P
SET P.gehalt = P.gehalt * 1.05
WHERE PersId
IN (
  SELECT PersId
  FROM test2
)
DROP TABLE test2;
Run Code Online (Sandbox Code Playgroud)

要么

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
Run Code Online (Sandbox Code Playgroud)

  • 好吧,大多数子查询可以用`CREATE TABLE`语句重写为多个步骤 - 我希望作者知道这一点.但是,这是唯一的解决方案吗?或者可以使用子查询或连接重写查询?为什么(不)那样做? (15认同)
  • 尝试过这种解决方案,对于临时/秒表中的大量条目,查询可能非常慢;尝试使用索引/主键创建临时/第二张表[请参见http://dev.mysql.com/doc/refman/5.1/en/create-table-select.html] (2认同)

Yua*_*tao 25

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

您可以将查询分为两部分,或者执行

 UPDATE TABLE_A AS A
 INNER JOIN TABLE_A AS B ON A.field1 = B.field1
 SET field2 = ? 

  • `SELECT ... SET`?我从来没有听说过这个. (5认同)
  • ......这似乎没有实际效果.它仍然给我同样的错误. (2认同)
  • 这个答案实际上是更正确和有效的事情,它在对TABLE_A的第二个引用上使用了AS B。最赞成的示例中的答案可以使用AS T来简化,而不是使用效率可能较低的FROM(SELECT * FROM myTable)AS something来简化,幸运的是,查询优化器通常会消除这种情况,但可能并非总是如此。 (2认同)

Bud*_*dda 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
)
Run Code Online (Sandbox Code Playgroud)

我已经引入了一个单独的名称(别名),并为临时表的'persID'列提供了一个新名称


Dar*_*ide 18

这很简单.例如,而不是写:

INSERT INTO x (id, parent_id, code) VALUES (
    NULL,
    (SELECT id FROM x WHERE code='AAA'),
    'BBB'
);
Run Code Online (Sandbox Code Playgroud)

你应该写

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

或类似的.


Har*_*Das 15

MySQL 不允许同时从表中进行选择并在同一个表中进行更新。但总有一个解决方法:)

这不行>>>>

UPDATE table1 SET col1 = (SELECT MAX(col1) from table1) WHERE col1 IS NULL;
Run Code Online (Sandbox Code Playgroud)

但这有效>>>>

UPDATE table1 SET col1 = (SELECT MAX(col1) FROM (SELECT * FROM table1) AS table1_new) WHERE col1 IS NULL;
Run Code Online (Sandbox Code Playgroud)

  • 谢谢。我认为大多数人对使用 MariaDB 的本地 env xampp 感到困惑,然后生产服务器仍然使用 MySQL (3认同)

Aja*_*ak6 11

BlueRaja发布的方法很慢我修改了它,因为我用来删除表中的重复项.万一它可以帮助任何人使用大表原始查询

delete from table where id not in (select min(id) from table group by field 2)
Run Code Online (Sandbox Code Playgroud)

这需要更多时间:

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

更快的解决方案

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


Luk*_*zda 7

MariaDB 从 10.3.x 开始提升了这一点(包括DELETEUPDATE):

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

从 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
Run Code Online (Sandbox Code Playgroud)

从 MariaDB 10.3.2 开始,语句成功执行:

UPDATE t1 SET c1=c1+1 WHERE c2=(SELECT MAX(c2) FROM t1);
Run Code Online (Sandbox Code Playgroud)

DELETE - 相同的源表和目标表

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

DELETE FROM t1 WHERE c1 IN (SELECT b.c1 FROM t1 b WHERE b.c2=0);
Run Code Online (Sandbox Code Playgroud)

DBFiddle MariaDB 10.2 - 错误

DBFiddle MariaDB 10.3 - 成功


Fil*_*zza 5

作为参考,您还可以使用Mysql变量保存临时结果,例如:

SET @v1 := (SELECT ... );
UPDATE ... SET ... WHERE x=@v1;
Run Code Online (Sandbox Code Playgroud)

https://dev.mysql.com/doc/refman/5.7/zh-CN/user-variables.html

  • 一般来说,了解这一点是件好事,但它不适用于更新/删除多行“错误 1242 (21000):子查询返回超过 1 行” (2认同)