【问题标题】:Table is specified twice, both as a target for 'UPDATE' and as a separate source for data in mysql表被指定了两次,既作为“更新”的目标,又作为 mysql 中数据的单独源
【发布时间】:2017-07-07 12:17:54
【问题描述】:

我在 mysql 中有以下查询,我想检查 branch idyearfinance 类型是否来自 branch_mastermanagerbranch idyear 相等,然后更新状态manager 表针对 manager 中的branch id

UPDATE manager as m1 
  SET m1.status = 'Y'
  WHERE m1.branch_id IN (
    SELECT m2.branch_id FROM manager as m2 
     WHERE (m2.branch_id,m2.year) IN (
        (
          SELECT DISTINCT branch_id,year 
            FROM `branch_master` 
           WHERE type = 'finance'
        )
     )
  )

但出现错误

表 'm1' 被指定了两次,既作为 'UPDATE' 的目标,又作为 单独的数据源

【问题讨论】:

  • 这看起来是正确的,但是 MySQL 可能会混淆 branch_idyeartype 在您最后一次选择为 m1 变量时,因此认为您正在从同一个表中选择您'正在更新,这将导致此错误。尝试将这些变量标识为 branch_master.XXX 变量,看看是否可以解决问题。
  • SO 中有很多答案尝试使用谷歌搜索 mysql 作为“更新”的目标和单独的数据源
  • @yanman1234 试过但同样的错误
  • @P.Salmon 已经在 google 上进行了检查,并按照答案中的建议将不同的名称应用于 m1 和 m2 表。但仍然无法正常工作
  • 尝试拆分您的查询以查看导致错误的部分。硬编码最后一次选择提供的 where 值。如果错误仍然存​​在,则硬编码设置下一个值,依此类推,直到错误消失。

标签: mysql


【解决方案1】:

这是典型的 MySQL 事情,通常可以通过从派生的表中选择来规避,即,而不是

FROM manager AS m2

使用

FROM (select * from manager) AS m2

完整的陈述:

UPDATE manager
SET status = 'Y'
WHERE branch_id IN
(
  select branch_id
  FROM (select * from manager) AS m2
  WHERE (branch_id, year) IN
  (
    SELECT branch_id, year
    FROM branch_master
    WHERE type = 'finance'
  )
);

【讨论】:

  • 但是你为什么要在你的更新声明中寻找其他经理呢?现在听起来你只是想用财务部门/年更新经理:UPDATE manager SET status = 'Y' WHERE (branch_id, year) IN (SELECT branch_id, year FROM branch_master WHERE type = 'finance');。这与您展示的完全不同。
  • 我之前尝试过,但即使是从大约 30k 条记录更新单条记录也需要花费太多时间。答案中的代码对我有用,我在第一次选择中添加了年份
  • 这很奇怪。如果简单的 SQL 是您真正想要的,但速度太慢,请为您的表添加索引:create index idx_bm on branch_master(type, branch_id, year)create index idx_m on manager(branch_id, year)
  • 好的。谢谢。我会尝试。回答中的查询比评论中的查询要快得多
  • 这很难过。这表明 MySQL 的优化器在这里做得不好。
【解决方案2】:

正确答案在this SO post

这里接受的答案的问题是 - 正如已经多次提到的 - 创建整个表格的完整副本。这远非最佳和最复杂的空间。这个想法是实现仅用于更新的数据子集,因此在您的情况下,它将是这样的:

UPDATE manager as m1
SET m1.status = 'Y'
WHERE m1.branch_id IN (
    SELECT * FROM(
        SELECT m2.branch_id FROM manager as m2 
        WHERE (m2.branch_id,m2.year) IN (
            SELECT DISTINCT branch_id,year 
            FROM `branch_master` 
            WHERE type = 'finance')
    ) t
)

基本上,您只需将之前的数据源查询封装在

SELECT * FROM (...) t

【讨论】:

    【解决方案3】:

    尝试使用EXISTS 运算符:

    UPDATE manager as m1 
    SET m1.status = 'Y'
    WHERE EXISTS (SELECT 1
                  FROM (SELECT m2.branch_id             
                        FROM branch_master AS bm
                        JOIN manager AS m2
                        WHERE bm.type = 'finance' AND
                            bm.branch_id = m2.branch_id AND
                            bm.year = m2.year) AS t
                  WHERE t.branch_id = m1.branch_id); 
    

    注意:该查询使用@Thorsten 提出的额外嵌套级别作为规避Table is specified dual错误的一种手段。

    Demo here

    【讨论】:

    • 这是另一种说法。您不是在检查所有财务部门主管,而只是检查经理状态为 1 的那些。
    • @ThorstenKettner 我的声明中没有WHERE status = 1。关联将检查manager所有 记录,并对branch_master 中存在branch_id, year 对的那些记录执行UPDATE
    • 对不起,我似乎完全糊涂了。这与状态无关,但仍然......假设有两个经理记录:1)分支=1,年份=2016。 2) 分支=1,年份=2017。还有一个 branch_master 记录,branch=1,year=2017,type=finance。 OP 的声明会更新两个经理记录,你的只是第二个。
    • @ThorstenKettner 是的,你是对的,我现在明白你的意思了。我将尝试修复保留 EXISTS 运算符的语句。
    • 我想更新年份也匹配的记录。在 Thorsten Kettner 的例子中。只有一行需要更新分支 =1 和年 = 2017
    【解决方案4】:

    试试 :::

    UPDATE manager as m1 
    SET m1.status = 'Y'
    WHERE m1.branch_id IN (
      (SELECT DISTINCT branch_id
      FROM branch_master
      WHERE type = 'finance'))
      AND m1.year IN ((SELECT DISTINCT year
      FROM branch_master 
      WHERE type = 'finance'))
    

    【讨论】:

      【解决方案5】:

      我接受的答案的问题是创建整个表的副本,对我来说不是一个选项,我尝试执行它,但几个小时后我不得不取消它。

      如果您有大量数据,一个非常快速的方法是创建一个临时表:

      1. 创建 TMP 表

        创建临时表 tmp_manager (branch_id bigint auto_increment 主键, 年份日期时间 null);

      2. 填充 TMP 表

        插入 tmp_manager (branch_id, year) 选择 branch_id,年份 来自经理;

      3. 加入更新

        更新管理器为 m,tmp_manager 为 tmp_m 内部 JOIN 管理器作为 tmp_m.branch_id = man.branch_id 上的人 设置状态 = 'Y' WHERE m.branch_id = tmp_m.branch_id and m.year = tmp_m.year and m.type = 'finance';

      【讨论】:

        【解决方案6】:

        这是迄今为止最快的方法:

        UPDATE manager m
        INNER JOIN branch_master b on m.branch_id=b.branch_id AND m.year=b.year
        SET m.status='Y'
        WHERE b.type='finance'
        

        请注意,如果是 1:n 关系,则 SET 命令将运行多次。在这种情况下,这没有问题。但是如果你有类似“SET price=price+5”的东西,你就不能使用这种结构。

        【讨论】:

          【解决方案7】:

          也许不是一个解决方案,但是关于为什么它首先不起作用的一些想法:

          从表中读取数据并将数据写入同一个表在某种程度上是一项定义不明确的任务。应该以什么顺序读取和写入数据?从同一个表中读回新写入的数据时是否应该考虑它? MySQL 拒绝执行这不仅仅是因为限制,而是因为它不是一个定义明确的任务。

          涉及SELECT ... FROM (SELECT * FROM table) AS tmp 的解决方案只是将表的全部内容转储到临时表中,然后可以将其用于任何进一步的外部查询,例如更新查询。这将强制操作顺序为:首先将所有内容选择到临时表中,然后然后使用该数据(而不是原始表中的数据)进行更新。

          但是,如果涉及的表很大,那么这个临时复制将会非常慢。没有索引会加速SELECT * FROM table

          我今天可能会过得很慢...但是原来的查询不是和这个相同的,没有任何问题吗?

          UPDATE manager as m1 
          SET m1.status = 'Y'
          WHERE (m1.branch_id, m1.year) IN (
                SELECT DISTINCT branch_id,year 
                  FROM `branch_master` 
                 WHERE type = 'finance'
              )
          

          【讨论】:

            猜你喜欢
            • 2016-09-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2017-11-24
            • 1970-01-01
            相关资源
            最近更新 更多