为啥不能为具有 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) 的最大日期的每个代码选择记录?的主要内容,如果未能解决你的问题,请参考以下文章