为啥不能为具有 date=max(date) 的最大日期的每个代码选择记录?

Posted

技术标签:

【中文标题】为啥不能为具有 date=max(date) 的最大日期的每个代码选择记录?【英文标题】:Why can't select records for each code having the maximum date with having date=max(date)?为什么不能为具有 date=max(date) 的最大日期的每个代码选择记录? 【发布时间】:2021-03-16 03:56:34 【问题描述】:

显示创建表结构;

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);

请详细说明原因。

【问题讨论】:

【参考方案1】:

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

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

在您的第二个查询中,您使用SELECT *,然后在您尝试在HAVING 子句中使用MAX(date) 之前仅按code 进行分组。此查询不起作用,因为您正在使用 SELECT * 选择表中的每一列,但您仅在 GROUP BY 子句中按 code 分组。从 MySQL v5.7 及更高版本开始,有一个名为 only_full_group_by 的东西不允许您使用 GROUP BY 运行查询,除非您在 GROUP BY 中指定 SELECT 语句中的每一列,IE:获取您的要运行第二个查询,您必须将表中的每一列都列在 GROUP BY 子句中,因为您使用的是 SELECT * 选择语句 (here is the documentation that talks about only_full_group_by)。

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

【讨论】:

【参考方案2】:

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

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

开始:

select * from quote group by code

从 SQL 标准的角度来看,此查询本身不是有效的。

如果所有其他列在功能上都依赖于code,则可能是这样,而基于表定义的情况并非如此(代码不是唯一的,也不是主键)。相关阅读:Group by clause in mySQL and postgreSQL, why the error in postgreSQL?

查询的行为类似于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+ 可以使用:

本节描述非聚合窗口函数,对于查询中的每一行,使用与该行相关的行执行计算

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

【讨论】:

【参考方案3】:

这个

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

具有max(date),这在GROUP BY code 的上下文中是有意义的。但是date 没有。问题是应该比较行的date?简单来说,很可能是“无效”的 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)。这将显着扩展该子查询,并且可能会改进其他一些查询。

【讨论】:

以上是关于为啥不能为具有 date=max(date) 的最大日期的每个代码选择记录?的主要内容,如果未能解决你的问题,请参考以下文章

选择具有 MAX(Date) 但没有得到明显回报的记录

为啥我的 Access Max() 子查询不起作用?

使用 GROUP BY 后查找具有 MAX 值的行

查询存在 Max(Date) 的记录

选择具有最大值的行,并结合WHERE。 MAX和CAST,在spark.sql中

获取 MAX(date) < X 的数据