如何用窗口函数 FIRST_VALUE / LAST_VALUE 取分组首尾值

发布时间 - 2026-01-28 00:00:00    点击率:
根本原因是FIRST_VALUE和LAST_VALUE默认窗口帧为ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW,导致LAST_VALUE仅取当前行及之前行的末值而非全分组末值;需显式指定UNBOUNDED FOLLOWING并确保ORDER BY唯一确定、妥善处理NULL。

为什么 FIRST_VALUELAST_VALUE 有时取不到预期的首尾值?

根本原因是这两个函数默认的窗口帧(frame)是 ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW,也就是说,LAST_VALUE 并不是看整个分组的最后一条,而是看“当前行及之前所有行”里的最后一个——这通常不是你想要的。而 FIRST_VALUE 虽然在此帧下总能取到分组第一行(因起始是 UNBOUNDED PRECEDING),但它的行为也依赖于 ORDER BY 是否明确、是否唯一。

常见错误现象:LAST_VALUE(col) 返回的总是当前行的值,或某几行重复出现同一个“末尾值”,分组内其他行没更新。

  • 必须显式指定窗口帧为 ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING
  • ORDER BY 子句不可省略——即使业务上不关心顺序,也得选一个确定性排序列(如主键、时间戳)
  • ORDER BY 列存在重复值,且未加额外去重手段(如 ORDER BY ts, id),FIRST_VALUE/LAST_VALUE 的结果可能非确定

PostgreSQL / MySQL 8.0+ / SQL Server 中正确写法示例

以按用户分组、取每个用户最早和最晚订单金额为例:

SELECT
  user_id,
  order_time,
  amount,
  FIRST_VALUE(amount) OVER (
    PARTITION BY user_id 
    ORDER BY order_time, order_id 
    ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING
  ) AS first_amount,
  LAST_VALUE(amount) OVER (
    PARTITION BY user_id 
    ORDER BY order_time, order_id 
    ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING
  ) AS last_amount
FROM orders;

注意点:

  • ORDER BY order_time, order_id 确保排序唯一,避免并列导致 FIRST_VALUE 随机选中某一行
  • MySQL 8.0+ 和 PostgreSQL 支持该完整语法;SQL Server 同样支持,但旧版本(如 2012)的 LAST_VALU

    E
    帧必须显式声明,否则行为异常
  • SQLite 目前(3.45)仍不支持自定义帧,LAST_VALUE 无法可靠使用

替代方案:用 MIN/MAX 还是 FIRST_VALUE/LAST_VALUE

如果只是取分组内某个字段的极值(比如最大金额、最小时间),直接用 MIN()/MAX() 更简洁安全;但如果你需要的是「对应那条记录的其他字段」,就必须用窗口函数。

例如:要取每个用户「最早订单的订单号 + 金额 + 商品名」,就不能只靠 MIN(order_time),因为没法把商品名一起捞出来——这时得靠 FIRST_VALUE(order_id)FIRST_VALUE(amount)FIRST_VALUE(product_name) 同步拉取。

  • MIN/MAX 是聚合函数,丢失行上下文;FIRST_VALUE/LAST_VALUE 是窗口函数,保留原始行结构
  • 性能上,带完整帧的 LAST_VALUE 在大数据集可能比 MAX 略慢,因需维护完整窗口状态
  • 某些引擎(如 BigQuery)对 LAST_VALUE 的帧优化较好,但 Hive/Spark SQL 旧版本可能有 bug,建议测试验证

容易被忽略的 NULL 处理与排序方向

FIRST_VALUELAST_VALUE 默认把 NULL 当作最小值(即 ORDER BY col ASCNULL 排最前)。如果你的排序列含 NULL,且希望它们被排除在首尾之外,得提前过滤或用 CASE 调整顺序。

  • 安全做法:在 ORDER BY 中写 ORDER BY col IS NULL, colNULL 排最后
  • 或者用 COALESCE(col, '9999-12-31')(日期场景)临时替换,但要注意类型兼容性
  • 反向取“最后非 NULL 值”时,别只改 DESC,还要配合 NULLS LAST(PostgreSQL/Oracle 支持),否则 LAST_VALUE 可能真就取到 NULL

真正麻烦的从来不是语法怎么写,而是排序依据是否稳定、NULL 怎么归置、以及不同数据库对 ROWS BETWEEN 的实际实现差异——这些地方一错,首尾值就静悄悄地偏了。


# mysql  # oracle  # 大数据  # win  # 聚合函数  # 为什么  # sql  # NULL  # sqlite  # hive  # spark  # postgresql  # 数据库  # bug  # 根本原因  # 的是  # 旧版本  # 如果你  # 子句  # 在此  # 这两个  # 较好  # 能有  # 就不能 


相关栏目: 【 网站优化151355 】 【 网络推广146373 】 【 网络技术251813 】 【 AI营销90571


相关推荐: Gemini手机端怎么发图片_Gemini手机端发图方法【步骤】  PHP 500报错的快速解决方法  如何选择PHP开源工具快速搭建网站?  Laravel的契約(Contracts)是什么_深入理解Laravel Contracts与依赖倒置  黑客如何通过漏洞一步步攻陷网站服务器?  网站制作企业,网站的banner和导航栏是指什么?  敲碗10年!Mac系列传将迎来「触控与联网」双革新  浅谈redis在项目中的应用  详解ASP.NET 生成二维码实例(采用ThoughtWorks.QRCode和QrCode.Net两种方式)  车管所网站制作流程,交警当场开简易程序处罚决定书,在交警网站查询不到怎么办?  Laravel数据库迁移怎么用_Laravel Migration管理数据库结构的正确姿势  Laravel如何实现API版本控制_Laravel版本化API设计方案  创业网站制作流程,创业网站可靠吗?  Thinkphp 中 distinct 的用法解析  如何确保西部建站助手FTP传输的安全性?  为什么要用作用域操作符_php中访问类常量与静态属性的优势【解答】  iOS验证手机号的正则表达式  Laravel如何监控和管理失败的队列任务_Laravel失败任务处理与监控  零基础网站服务器架设实战:轻量应用与域名解析配置指南  Laravel如何使用Scope本地作用域_Laravel模型常用查询逻辑封装技巧【手册】  如何用JavaScript实现文本编辑器_光标和选区怎么处理  音响网站制作视频教程,隆霸音响官方网站?  如何用低价快速搭建高质量网站?  如何在阿里云购买域名并搭建网站?  如何为不同团队 ID 动态生成多个非值班状态按钮  如何用AWS免费套餐快速搭建高效网站?  INTERNET浏览器怎样恢复关闭标签页_INTERNET浏览器标签恢复快捷键与方法【指南】  如何快速上传建站程序避免常见错误?  Laravel Docker环境搭建教程_Laravel Sail使用指南  Laravel如何使用Service Container和依赖注入?(代码示例)  如何快速搭建支持数据库操作的智能建站平台?  如何确保FTP站点访问权限与数据传输安全?  html5源代码发行怎么设置权限_访问权限控制方法与实践【指南】  BootStrap整体框架之基础布局组件  Laravel Fortify是什么,和Jetstream有什么关系  Win11怎么设置虚拟桌面 Win11新建多桌面切换操作【技巧】  *服务器网站为何频现安全漏洞?  EditPlus中的正则表达式实战(6)  高性能网站服务器部署指南:稳定运行与安全配置优化方案  如何在景安服务器上快速搭建个人网站?  在线制作视频网站免费,都有哪些好的动漫网站?  Laravel如何编写单元测试和功能测试?(PHPUnit示例)  Laravel表单请求验证类怎么用_Laravel Form Request分离验证逻辑教程  七夕网站制作视频,七夕大促活动怎么报名?  Win11怎么恢复误删照片_Win11数据恢复工具使用【推荐】  香港服务器网站测试全流程:性能评估、SEO加载与移动适配优化  北京网站制作费用多少,建立一个公司网站的费用.有哪些部分,分别要多少钱?  ChatGPT 4.0官网入口地址 ChatGPT在线体验官网  绝密ChatGPT指令:手把手教你生成HR无法拒绝的求职信  Laravel如何使用Passport实现OAuth2?(完整配置步骤)