PostgreSQL中高效实现先排序再去重(取各id最小时间行)
高性能获取每个ID最小时间对应的记录
先明确咱们的原始数据表结构和数据:
id | time | value 1 | 0:00 | 'a' 1 | 2:00 | 'b' 2 | 4:00 | 'c' 2 | 3:00 | 'd' 2 | 2:00 | 'e' 3 | 5:00 | 'f' 3 | 3:00 | 'g'
需求很清晰:给每个id挑出唯一一条记录,而且这条记录得是该id下time最小的那条,同时要适配百万级数据量,必须高性能,不能搞那种先子查询排序再去重的冗余操作。
方案1:聚合关联查询(通用SQL,适配绝大多数数据库)
这是最通用的高效路子,先通过聚合拿到每个id的最小time,再和原表关联拿到完整记录,全程没全局排序,性能拉满,尤其适合给表建了(id, time)联合索引的场景:
SELECT t.* FROM your_table t INNER JOIN ( SELECT id, MIN(time) AS min_time FROM your_table GROUP BY id ) AS agg ON t.id = agg.id AND t.time = agg.min_time;
必加的性能buff:
- 给表建联合索引
(id, time):数据库能直接通过索引快速算出每个id的最小time,不用扫全表;关联的时候也能直接定位到目标行,速度快得飞起。 - 别碰全局排序:这种方式只做一次聚合+一次关联,计算量比先排序再去重小太多,百万级数据下差距特别明显。
方案2:DISTINCT ON(PostgreSQL专属)
如果你用的是PostgreSQL,那这个语法简直是为这个需求量身定做的,简洁又高效:
SELECT DISTINCT ON (id) * FROM your_table ORDER BY id, time ASC;
性能优化关键点:
- 同样要建联合索引
(id, time):数据库会直接用索引按id分组,然后取每组的第一条(也就是time最小的那条),完全不用额外排序,性能拉满。
方案3:窗口函数(适合复杂筛选场景)
如果你的需求后续还要加更复杂的筛选逻辑,窗口函数也是个不错的选择,但要注意不要做多余计算:
SELECT id, time, value FROM ( SELECT *, ROW_NUMBER() OVER (PARTITION BY id ORDER BY time ASC) AS rn FROM your_table ) AS t WHERE rn = 1;
性能注意事项:
- 必须有
(id, time)联合索引:数据库可以利用索引来做分区和排序,避免全表排序。相比那种先全局排序再去重的方案,窗口函数的分区排序是局部的,性能要好很多,但比前两种方案稍逊一筹。
为啥要避开“先排序再去重”?
百万级数据量下,全局排序(比如先ORDER BY再用GROUP BY或DISTINCT)会产生巨量的IO和计算开销,甚至可能触发磁盘排序,直接把性能拉垮。而上面的几个方案都是基于分组聚合或者索引直接定位目标行,计算量和IO开销都小得多,完全适配百万级数据的场景。
内容的提问来源于stack exchange,提问作者user1299412
相关产品推荐
相关产品推荐

