如何将带有子查询的 MySQL select 语句转换为同一张表的更新语句?

Posted

技术标签:

【中文标题】如何将带有子查询的 MySQL select 语句转换为同一张表的更新语句?【英文标题】:How to convert a MySQL select statement with a subquery to the same table into an update statement? 【发布时间】:2018-11-09 08:46:44 【问题描述】:

我有以下工作选择语句。

SELECT t1.id, t1.option_key, ( 
    SELECT
        t3.content AS option_value
    FROM tblfoo t2
    LEFT OUTER JOIN tblbar t3 ON( t3.refid = t2.id )
    WHERE t2.id = t1.id
    LIMIT 1
) AS option_value
FROM tblfoo t1

表结构如下所示:

| tblfoo                         |
+----+------------+--------------+
| id | option_key | option_value |
+----+------------+--------------+
|  1 | foo        | NULL         |
|  2 | bar        | NULL         |
|  3 | baz        | NULL         |


| tblbar               |
+----+-----------------+
| id | refid | content |
+----+-----------------+
|  1 |     1 | value1  |
|  1 |     2 | value2  |
|  1 |     3 | value3  |

更新语句的结果应该是这样的:

| tblfoo                         |
+----+------------+--------------+
| id | option_key | option_value |
+----+------------+--------------+
|  1 | foo        | value1       |
|  2 | bar        | value2       |
|  3 | baz        | value3       |

我想用来自contenttblbar 的相关数据更新option_valuetblfoo。不幸的是,tblbar 可能有多个具有相同值的refid 条目。这就是为什么子查询需要LIMIT 1(或GROUP BY t,idDISTINCT)。

我发现,当我使用LIMIT 1 进行子查询而不是使用SELECT DISTINCT 进行子查询或结合GROUP BY t1.id 进行连接时,查询速度明显更快。所以在执行时间优化之后,我最终得到了上面的select语句。

源表还有一个问题,应该更新。 option_value 是一个实际字段,它也存在于源表中(但具有 NULL 值)。

我在尝试将上面优化的选择语句转换为更新语句时遇到的问题主要是我无法从子查询内部访问t1.id

如何在不损失性能优化的情况下将select语句转换为update语句?

【问题讨论】:

哪些表/列正在更新哪些数据? 子查询中,为什么要加入table2和table3;相信你可以直接使用table3 显示您正在尝试更新的代码 @TimBiegeleisen 我已经用表格结构编辑了我的问题,并且更清楚地说明了应该更新哪些字段。 如果有重复值,你要使用哪一个? 【参考方案1】:

只需使用带有子查询的更新连接,它会在 tblbar 中找到不同的值:

UPDATE tblfoo f
INNER JOIN
(
    SELECT DISTINCT refid, content
    FROM tblbar
) b
    ON f.id = b.refid
SET f.option_value = b.content;

【讨论】:

谢谢,您的 SQL 可以正常工作,但有一处改动:它是 SET f.content = b.option_value;。你能更新你的答案吗?那我就可以接受了:) @burnersk 实际上,根据您实际向我们展示的表格,我的回答似乎是正确的。如果没有,请更新您的问题。 对不起,你是对的。我必须匿名的公司限制让我很生气。【参考方案2】:

您的相关子查询可以通过避免两个表之间的Left Join 来进一步优化。相反,您可以直接从第二个表中获取content 值。

对于更新,您可以将 select 查询用作派生表,并将其连接到源表:

UPDATE tblfoo AS tfoo 
JOIN (
      SELECT t1.id, 
             (SELECT t3.content AS option_value
              FROM tblbar t3
              WHERE t3.refid = t1.id
              LIMIT 1
             ) AS option_value
      FROM tblfoo t1
     ) AS dt ON dt.id = tfoo.id 
SET tfoo.option_value = dt.option_value;

【讨论】:

您的更新声明有效。但是@TimBiegeleisen 的版本运行速度更快(1 分钟对 10 分钟) @burnersk 是的,蒂姆进一步优化。我没有整体优化您的原始查询;只是子查询部分。【参考方案3】:

如果您要更新所有行,那么最有效的方法可能是:

UPDATE tblfoo f
    SET f.option_value = (SELECT b.content FROM tblbar WHERE f.id = b.refid LIMIT 1);

特别是,这可以利用tblbar(refid, content) 上的索引。

【讨论】:

以上是关于如何将带有子查询的 MySQL select 语句转换为同一张表的更新语句?的主要内容,如果未能解决你的问题,请参考以下文章

带有 Knex.js 的 select 语句的子查询

mysql学习之路_联合查询与子查询

SELECT中(非常)常用的子查询操作

mysql:使用子查询更新,

MySQL必知应会-第14章-使用子查询

MySQL—— 子查询