0

使用表格和数据:

CREATE TABLE test.tem(a INT,b INT,INDEX (a),INDEX (b));
INSERT INTO test.tem VALUES(1,2),(1,1),(1,NULL),(2,3);

现在数据应该是:

+------+------+
| a    | b    |
+------+------+
|    1 |    2 |
|    1 |    1 |
|    1 | NULL |
|    2 |    3 |
+------+------+

我想按 a 列将 b 列更新为 min(b) 组。
我知道一个正确的 SQL 是:

UPDATE tem AS t1
  JOIN (SELECT a,MIN(b) AS m FROM tem GROUP BY a) AS t2
    USING (a)
SET t1.b = t2.m; 

产生正确的结果是:

+------+------+
| a    | b    |
+------+------+
|    1 |    1 |
|    1 |    1 |
|    1 |    1 |
|    2 |    3 |
+------+------+

但是,使用此 SQL 查询在一个包含 450 万条记录的表中更新大约需要 5 分钟。

所以,我有一个自己的 SQL:

UPDATE test.tem t1
  JOIN test.tem t2
    ON t1.a = t2.a
SET t1.b = t2.b
WHERE t1.b > t2.b
     OR t1.b IS NULL;

但它得到了错误的结果:

+------+------+
| a    | b    |
+------+------+
|    1 |    1 |
|    1 |    1 |
|    1 |    2 |
|    2 |    3 |
+------+------+

我认为原因是关于 MYSQL 在更新时如何工作。谁能告诉我不正确的结果是如何产生的?如果有人可以修复我的SQL,那也会有所帮助。

4

2 回答 2

3

对于“查询未正确更新行”:

您希望将列更新为具有相同的所有行b最小值ba

您建议使用以下JOIN方法来做到这一点:

UPDATE test.tem t1
  JOIN test.tem t2
    ON t1.a = t2.a
SET t1.b = t2.b
WHERE t1.b > t2.b
     OR t1.b IS NULL;

与您的想法相反,这JOIN不会执行 1-1 JOIN。它实际上是多对多的,JOIN因为正如我昨天所说,您在联接子句中不使用主键(也不是非空唯一键)。

事实上,将查询重写为 asSELECT可能会帮助您理解问题:

SELECT t1.a as t1a, t1.b as t1b, t2.a as t2a,t2.b as t2b FROM tem t1 JOIN tem t2
    ON t1.a = t2.a
WHERE t1.b > t2.b
     OR t1.b IS NULL;

+------+---------+------+--------+
| T1A  |  T1B    | T2A  |  T2B   |
+------+---------+------+--------+
|   1  | (null)  |   1  | 2      |
|   1  | 2       |   1  | 1      |
|   1  | (null)  |   1  | 1      |
|   1  | (null)  |   1  | (null) |
+------+---------+------+--------+

http://sqlfiddle.com/#!2/856a7/8

正如您现在将看到的,行(1, null)匹配(1, 1)和。根据查询执行的(非确定性)顺序,这可能会分配三个可能值中的任何一个('我不确定,但甚至可能更新它几次)。在某种程度上,您很幸运在测试时发现了“错误”的结果!(1, 2)(1, null)b

我希望这能解释一下为什么您的查询没有产生预期的结果。由于多表UPDATE语句不允许ORDER BYnor子句,就我自己而言,找到“好”结果,除了首先通过子查询GROUP BY找到最小值之外,我没有看到很多其他选择......

于 2013-08-08T09:59:32.510 回答
2

要提高查询的执行速度,您可以做的一件主要WHERE事情是使用适当的索引来优化/USING子句。

UPDATE tem AS t1
  JOIN (SELECT a,MIN(b) AS m FROM tem GROUP BY a) AS t2
    USING (a)
SET t1.b = t2.m;

这个请求有一个USING(a)子句。最快的 MySQL 将能够匹配a最快的查询将运行的列。因此,您必须在该列上添加一个索引:

ALTER TABLE tem ADD INDEX (a);

事实上,由于子查询同时使用GROUB BY(a)and MIN(b),因此索引(a,b)很可能会执行得更好:

ALTER TABLE tem ADD INDEX (a,b);

为了确定这一点,您可能必须检查查询计划(见EXPLAIN 下文)。


第二个查询是:

UPDATE test.tem t1
  JOIN test.tem t2
    ON t1.a = t2.a
SET t1.b = t2.b
WHERE t1.b > t2.b
     OR t1.b IS NULL;

我在这里回答“我的查询有什么问题”。但从纯粹的性能角度来看,由于此查询使用子句b中的列WHERE,您将优化其执行,您至少需要在b.

ALTER TABLE tem ADD INDEX (b);

作为提示,当您的查询速度较慢 SELECT时,您可能会EXPLAIN SELECT ...检查查询计划以检查 MySQL 是否/如何使用索引。

在这里将您的两个查询重写为SELECT语句,这将给出:

EXPLAIN SELECT t1.b = t2.b
  FROM tem AS t1
  JOIN (SELECT a,MIN(b) AS m FROM tem GROUP BY a) AS t2
  USING (a);

EXPLAIN SELECT t1.b = t2.b
  FROM test.tem t1
  JOIN test.tem t2
  ON t1.a = t2.a
  WHERE t1.b > t2.b
     OR t1.b IS NULL;
于 2013-08-08T08:41:40.393 回答