游乐游手机版
首页/数据库/文章详情

SQL如何获取分组中第一条或最后一条记录_利用FIRST_VALUE函数

时间:2026-04-24 17:14
SQL如何获取分组中第一条或最后一条记录:利用FIRST_VALUE函数 为什么FIRST_VALUE返回的不是“每组第一条记录”? 很多朋友第一次用FIRST_VALUE窗口函数时,都会遇到一个困惑:结果怎么和想的不一样?它确实按你指定的顺序,取到了窗口里的第一个值,但问题是——它把那个值“复制”

SQL如何获取分组中第一条或最后一条记录:利用FIRST_VALUE函数

SQL如何获取分组中第一条或最后一条记录_利用FIRST_VALUE函数

为什么FIRST_VALUE返回的不是“每组第一条记录”?

很多朋友第一次用FIRST_VALUE窗口函数时,都会遇到一个困惑:结果怎么和想的不一样?它确实按你指定的顺序,取到了窗口里的第一个值,但问题是——它把那个值“复制”给了组里的每一行,行数一点没少。这就像给团队每个人都发了一份队长的名片,但队长本人并没有被单独请出来。而我们实际业务中想要的,往往是“每组只留一条代表记录”,比如最新的那笔订单、最早的那次登录、分数最高的那位学生。这时候你就会发现,FIRST_VALUE更像是个辅助工具,单靠它,解决不了“提取整行数据”这个核心问题。

怎么用FIRST_VALUE配合ROW_NUMBER拿到完整记录?

那么,正确的“组合拳”该怎么打呢?核心思路其实很清晰:分两步走。第一步,用ROW_NUMBER()给每个分组内的行,按照你的排序规则(比如时间倒序)编上号(1,2,3…)。第二步,在外层查询里,轻松地WHERE rn = 1,只把每组的“1号”选手筛选出来。在这个过程中,FIRST_VALUE可以作为一个验证字段出现,但它不是筛选的主力。

这里有三个关键点必须注意:

  • 排序是灵魂ORDER BY子句必须明确写在OVER()里面,否则ROW_NUMBER的编号顺序就是不确定的,结果自然不可靠。
  • 分组要对齐PARTITION BY里的字段(比如user_id),必须和你想分组的逻辑完全一致。
  • “最后一条”怎么取:很简单,把ORDER BY created_at DESC改成ASC就行了。当然,你也可以用LAST_VALUE,但要小心它的默认窗口范围,最好显式指定为RANGE BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING,以免出错。

来看一个经典例子(获取每个用户的最新订单):

SELECT order_id, user_id, amount, created_at
FROM (
  SELECT order_id, user_id, amount, created_at,
         ROW_NUMBER() OVER (PARTITION BY user_id ORDER BY created_at DESC) AS rn
  FROM orders
) t
WHERE rn = 1;

FIRST_VALUE在SELECT里直接用有哪些陷阱?

如果把FIRST_VALUE当成“取一行”的函数来用,很容易踩坑。因为它本质上是个“填充”函数,不负责删减行数。下面这几种情况,就是典型的误用:

  • 场景一:想找“每组金额最高的订单号”。如果直接写FIRST_VALUE(order_id) OVER (PARTITION BY user_id ORDER BY amount DESC),结果会是每一行都显示同一个最高的订单号,但你却看不到这个订单对应的金额、时间等其他信息。这显然不是我们想要的完整记录。
  • 场景二:忘了写ORDER BY。这时数据库可能会按物理存储顺序来排,每次执行的结果都可能不同,毫无稳定性可言。
  • 场景三:在聚合查询里混用。窗口函数和GROUP BY聚合不能直接放在同一层SELECT中,否则要么报错,要么语义变得混乱不清。

所以,更稳妥的做法是:要么先用子查询或CTE把窗口计算的结果准备好,再去做关联查询;要么就坚持使用ROW_NUMBER配合过滤的方案,思路直观,控制力也强。

MySQL 8.0+ 和 PostgreSQL 的行为差异要点

虽然主流数据库都支持这些窗口函数,但魔鬼藏在细节里,跨平台使用时尤其要注意:

  • MySQL 8.0+ 更“较真”:它对ORDER BY子句里的表达式要求更严格。比如,你不能直接使用SELECT列表里定义的别名(像ORDER BY dt),必须写原始字段名或列位置。
  • PostgreSQL 处理空值更灵活:它支持ORDER BY ... NULLS FIRST/LAST语法,让你能明确控制空值排在前面还是后面,这在MySQL里目前还不行。
  • SQL Server 有“捷径”:在某些简单场景下,使用TOP 1 WITH TIES配合FETCH NEXT子句,写起来可能比窗口函数更简短。但这种方法不具备跨多个分组分别取第一条的能力,功能上还是有区别的。

总的来说,如果考虑代码的兼容性和可读性,ROW_NUMBER方案是更安全的选择。而FIRST_VALUE,更适合扮演一个“锦上添花”的角色,用来生成一些派生字段,不建议让它承担核心的数据筛选逻辑。

来源:https://www.php.cn/faq/2338466.html
上一篇SQL如何实现按自定义权重进行分组汇总_利用乘法聚合逻辑 下一篇苹果微软双修党福音:Navicat如何M芯片Mac开启原生适配_硬核技巧
本站内容用于信息整理与展示,如有侵权或内容问题请及时联系处理。

相关推荐

补充同频道和同主题内容,方便继续浏览更多相关内容。

同类最新

继续查看同栏目最近更新的文章。

更多
Redis 7.0增量AOF重写RDB前导码配置详解
数据库 · 2026-07-02

Redis 7.0增量AOF重写RDB前导码配置详解

先说一个几乎所有人都踩过的典型误区:很多人把 aof-use-rdb-preamble yes 当作开启“增量重写”的开关。实际上,这个配置只干了一件事——让重写后的 AOF 文件头部带上 RDB 快照。它解决的是加载速度问题,跟“增量重写”本身的概念压根不是一回事。真正的增量重写,依赖的是 Red

在Python Tornado异步框架中安全执行SQL命令的方法与最佳实践
数据库 · 2026-07-02

在Python Tornado异步框架中安全执行SQL命令的方法与最佳实践

直接在Tornado里用SQLAlchemy同步执行SQL,结果就是阻塞IOLoop,所谓“异步框架里写同步数据库代码”,等于白搭。安全执行的关键不是“怎么写SQL”,而是“怎么不卡住事件循环”。 为什么不能在RequestHandler里直接调用session execute() 因为sessio

利用SQL触发器实现在INSERT数据时自动同步到审计表
数据库 · 2026-07-02

利用SQL触发器实现在INSERT数据时自动同步到审计表

先说结论:可以用触发器把 INSERT 数据同步到审计表,但必须用 AFTER INSERT,并且审计表的字段顺序、类型、字符集得和源表严格一致。否则,轻则写入错位、数据截断,重则直接报错、丢数据。下面把这些坑一个一个掰开说。 能,但必须用 AFTER INSERT,且审计表字段顺序、类型、字符集要

如何用SQL编写按不同工作日统计员工出勤率
数据库 · 2026-07-02

如何用SQL编写按不同工作日统计员工出勤率

在实际业务中,统计不同工作日的出勤率是HR系统里的高频需求。如果直接按日期函数分组,很容易掉进语言环境、索引失效或分母口径的坑里。下面就来拆解具体的实现要点。 必须用 CASE WHEN 将日期映射为固定 weekday 标签(如 Mon )再分组,避免语言环境导致的分组断裂;需过滤 DOW IN

Spring Boot 3动态拼接SQL为何引发严重安全漏洞
数据库 · 2026-07-02

Spring Boot 3动态拼接SQL为何引发严重安全漏洞

SQL注入漏洞的核心成因,本质上是因为用户输入直接参与了SQL语句的字符串拼接,而未采用参数化绑定机制。在MyBatis中使用${}、QueryWrapper中调用apply()与last()、JPA的@Query注解进行拼接等操作,都会绕过PreparedStatement的安全防护。动态字段必须