用ROW_NUMBER()窗口函数可同时提取极值及对应时间戳:先按分类分区、目标字段降序及时间戳升序排序,标出每组首行,再筛选rn=1记录;PostgreSQL/MySQL 8.0+/SQL Server均支持该标准写法。

用 ROW_NUMBER() 配合窗口函数替代 GROUP BY 实现极值+时间戳双提取
直接在 GROUP BY 中对非聚合字段(比如时间戳)求值会报错,因为 SQL 不知道该取哪一行的值。必须把“找极值”和“取对应时间戳”拆成同一行内的逻辑——窗口函数是唯一可靠解法。
核心思路:先按分类分区,再按目标字段(如温度、价格)排序,用 ROW_NUMBER() 标出每组内第一行;最后外层筛选序号为 1 的记录。
-
ORDER BY value DESC得到最大值对应行,ASC则得最小值 - 若存在并列极值,
ROW_NUMBER()只取其一;需全量返回请改用RANK()或DENSE_RANK() - 排序键中建议加入时间戳二次排序(如
ORDER BY value DESC, ts ASC),避免因引擎实现差异导致结果不稳定
PostgreSQL / MySQL 8.0+ / SQL Server 中的标准写法
这些数据库都支持标准窗口函数,语法一致,可直接复用:
SELECT category, value, ts
FROM (
SELECT category, value, ts,
ROW_NUMBER() OVER (
PARTITION BY category
ORDER BY value DESC, ts ASC
) AS rn
FROM sensor_data
) ranked
WHERE rn = 1;
注意:PARTITION BY category 是分组依据,ORDER BY 决定谁是“第一”。如果表很大,category 和 value 字段上建联合索引能显著提速。
SQLite 和旧版 MySQL(
这些环境不支持窗口函数,只能靠相关子查询或自连接,性能差但可行:
SELECT s1.category, s1.value, s1.ts
FROM sensor_data s1
WHERE s1.value = (
SELECT MAX(s2.value)
FROM sensor_data s2
WHERE s2.category = s1.category
)
AND s1.ts = (
SELECT MIN(s3.ts)
FROM sensor_data s3
WHERE s3.category = s1.category
AND s3.value = s1.value
);
这个写法隐含两个假设:极值唯一,或你接受任意一个极值时间戳。如果只要“首个出现的极值时间”,第二层子查询用 MIN(s3.ts);如果要“最新出现的”,则用 MAX(s3.ts)。没有索引时,这类查询可能全表扫描多次。
为什么不能用 MAX(value), ts 加 GROUP BY category?
这是最常踩的坑:SELECT category, MAX(value), ts FROM t GROUP BY category 在多数数据库中会报错,例如 PostgreSQL 报 column "t.ts" must appear in the GROUP BY clause or be used in an aggregate function;MySQL 5.7+ 严格模式下同样拒绝。
即使 MySQL 5.6 或开启 sql_mode=ONLY_FULL_GROUP_BY 关闭后“看似成功”,ts 的值也是随机的——它来自分组内某一行,但 SQL 标准不保证是哪一行,不同执行计划甚至可能返回不同结果。
真正需要的是确定性关联,不是侥幸运行。











