为什么不能 select 记录具有最大日期且具有 date=max(date) 的每个代码?

Why can't select records for each code having the maximum date with having date=max(date)?

显示创建 table 结构;

CREATE TABLE `quote` (
  `id` int(8) unsigned NOT NULL AUTO_INCREMENT,
  `code` text COLLATE utf8mb4_unicode_ci,
  `date` date DEFAULT NULL,
  `open` double DEFAULT NULL,
  `high` double DEFAULT NULL,
  `low` double DEFAULT NULL,
  `close` double DEFAULT NULL,
  `volume` bigint(15) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=17449887 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci 

使用内连接查找具有最大日期的每个代码的记录,例如:

SELECT q1.*
FROM quote q1
INNER JOIN
(
    SELECT code, MAX(date) AS max_date
    FROM quote
    GROUP BY code
) q2
    ON q2.code = q1.code AND
       q2.max_date = q1.date;

我想知道为什么子查询不能像上面那样得到想要的结果:

select * from quote group by code having date=max(date);

请详细说明原因

@scrapy 我相信你的问题的答案与子查询与其他查询的结构之间的区别有关。子查询的工作方式是 MySQL 在外部查询 运行 之前从查询创建派生的 table,然后在 [=38] 时使用派生的 table =] 执行外部查询 (here's the documentation on derived tables for you to refer to).

您的子查询有效,因为您仅 selecting 1 列 (code),然后您通过使用 MAX(date) 作为第二列获得聚合值最后,您在子查询的最后一行按 code 分组。

在您的第二个查询中,您正在使用 SELECT *,然后在您尝试在 HAVING 子句中使用 MAX(date) 之前仅按 code 进行分组。此查询不起作用,因为您使用 SELECT * selecting table 中的每一列,但您仅在 GROUP BY 子句中按 code 分组.从 MySQL v5.7 及更高版本开始,有一个名为 only_full_group_by 的东西不允许您使用 GROUP BY 运行 查询,除非您指定 [=22] 中的每一列=] 语句在你的 GROUP BY,即:为了让你的第二个查询工作,你必须在你的 GROUP BY 子句中列出你的 table 中的每一列,因为你正在使用 SELECT * 用于您的 select 语句 (here is the documentation that talks about only_full_group_by).

最后,为了获得您正在寻找的结果集,您必须按正确的列进行分组,就像您在子查询中所做的那样。如果您在尝试获取每个 code 的最大日期的查询中使用 code 以外的任何其他内容,结果集将不相同,因为您必须按额外的列进行分组,这会抛出关闭你的结果集。

I wonder why the subquery can't get desired result as above:

select * from quote group by code having date=max(date);

开始于:

select * from quote group by code

从 SQL 标准角度来看,此查询本身无效。

可能是,如果所有其他列在功能上都依赖于 code,但根据 table 定义则不是这种情况(代码不是唯一的,也不是主键)。相关阅读:

查询的行为类似于 ANY_VALUE:

select code, ANY_VALUE(id), ANY_VALUE(`date`), ANY_VALUE(`open`)...
from quote
group by code

关于第二部分:

having date=max(date);
--
having any_value(date) = max(date) -- sidenote: it will work for single row per `code`

此处 HAVING 中的条件在聚合后适用,这意味着比较是每个代码的 MAX(date) 与“未指定”日期之间的比较。


举例说明(此代码仅在 only_full_group_by 关闭时有效):

CREATE TABLE `quote` (
  `id` int(8) unsigned NOT NULL AUTO_INCREMENT,
  `code` text COLLATE utf8mb4_unicode_ci,
  `date` date DEFAULT NULL,
  `open` double DEFAULT NULL,
  PRIMARY KEY (`id`)
) ;

INSERT INTO quote(`code`, `date`, `open`)
VALUES ('a', '2020-01-01',10),
       ('a', '2021-01-01',20),
       ('a', '2022-01-01',30);

和查询:

SELECT * FROM quote;
+-----+-------+-------------+------+
| id  | code  |    date     | open |
+-----+-------+-------------+------+
|  1  | a     | 2020-01-01  |   10 |
|  2  | a     | 2021-01-01  |   20 |
|  3  | a     | 2022-01-01  |   30 |
+-----+-------+-------------+------+

select * from quote group by code;

-- this part is unspecified, id/date/open are arbitrary
+-----+-------+-------------+------+
| id  | code  |    date     | open |
+-----+-------+-------------+------+
|  1  | a     | 2020-01-01  |    1 |
+-----+-------+-------------+------+

select *, MAX(date) from quote group by code;

-- MAX(date) is stable, date is arbitrary, comparison does not make sense at this point
+-----+-------+-------------+-------+------------+
| id  | code  |    date     | open  | MAX(date)  |
+-----+-------+-------------+-------+------------+
|  1  | a     | 2020-01-01  |   10  | 2022-01-01 |  
+-----+-------+-------------+-------+------------+

select * from quote group by code having date=max(date);

-- empty
+-----+-------+-------+------+
| id  | code  | date  | open |
+-----+-------+-------+------+

db<>fiddle demo


这么说,为了得到所有列 ranking functions MySQL 8.0+ 可以使用:

This section describes nonaggregate window functions that, for each row from a query, perform a calculation using rows related to that row

SELECT *
FROM (SELECT *, ROW_NUMBER() OVER(PARTITION BY `code` ORDER BY `date` DESC) AS rn
      FROM `quote`) s   --RANK() if `date` is not unique per code
WHERE rn = 1;

db<>fiddle demo 2

这个

select * from quote group by code having date=max(date);

max(date),这在 GROUP BY code 的上下文中是有意义的。但是 date 没有。问题是 应该比较哪 行的 date?简单的说,大概就是'invalid'SQL.

另见“only_full_group_by”的讨论。 (较新版本的 MySQL 会将您的查询标记为无效。该标记是一种将其关闭以获取旧的、错误的评估的方法。)

这会导致子查询,例如您的子查询。还有其他一些。这是我执行 groupwise-max 的最佳方法目录:http://mysql.rjweb.org/doc.php/groupwise_max

也有很多讨论;查看我添加的标签 [groupwise-maximum]

其他问题:code是股票代码吗?如果是这样,则不需要 TEXT。通过更改为 VARCHAR(15),您可以获得很多性能:

删除 id 并更改为 PRIMARY KEY(code, date)。这将扩展该子查询 显着 并且可能会改进其他一些查询。