first_value必须配合over子句使用,且order by为强制要求;不支持变量动态指定列名或排序方向,需用case预定义分支或动态sql实现变体逻辑。

FIRST_VALUE 不能直接用变量指定窗口范围
SQL 标准里 FIRST_VALUE 是窗口函数,它的“窗口”由 OVER 子句定义,而 OVER 中的 PARTITION BY 和 ORDER BY 可以引用列或表达式,但**不能动态插入列名、排序方向或帧子句(如 ROWS BETWEEN ...)**——这些必须在解析期确定,不支持运行时拼接。
常见错误是试图写成这样(会报语法错误):
SELECT FIRST_VALUE(val) OVER (ORDER BY <code>@sort_col</code> <code>@sort_dir</code>) FROM t;
数据库会直接拒绝,因为 @sort_col 是变量,不是合法的标识符位置。
用 CASE + 多个 FIRST_VALUE 实现逻辑上的“动态列”
如果目标只是根据条件切换排序依据(比如按 created_at 或 updated_at 取首值),可以预先写出所有可能分支,用 CASE 控制输出:
假设参数是 @sort_by VARCHAR(20),取值为 'created' 或 'updated':
SELECT
CASE @sort_by
WHEN 'created' THEN FIRST_VALUE(val) OVER (ORDER BY created_at)
WHEN 'updated' THEN FIRST_VALUE(val) OVER (ORDER BY updated_at)
END AS dynamic_first
FROM t;
注意点:
- 每个
FIRST_VALUE都要独立写全OVER子句,不能共用 - 所有分支返回类型需兼容,否则报错(例如一个返回
INT,另一个返回VARCHAR) - 性能上会多算几个窗口,但优化器通常能复用排序结果,影响有限
真正需要动态帧范围?只能靠动态 SQL
如果连窗口帧都要变——比如 ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW 还是 ROWS BETWEEN 2 PRECEDING AND 2 FOLLOWING ——标准 SQL 没有绕过方式。必须拼字符串再执行:
在 PostgreSQL 中:
DO $$
DECLARE
frame_clause TEXT := 'ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW';
sql TEXT;
BEGIN
sql := format('SELECT FIRST_VALUE(val) OVER (ORDER BY id %s) FROM t', frame_clause);
EXECUTE sql;
END $$;
在 SQL Server 中用 sp_executesql;MySQL 用 PREPARE + EXECUTE。这类操作:
- 无法被查询缓存利用,每次都是硬解析
- 存在 SQL 注入风险,
frame_clause必须白名单校验或严格转义 - 不能出现在视图、内联表值函数中
替代思路:用子查询 + LIMIT/TOP 模拟 FIRST_VALUE 行为
如果只是想“按某条件取第一条”,且不强依赖窗口函数语义(比如不需要和当前行同窗口对齐),可用相关子查询加排序限制:
SELECT *,
(SELECT val FROM t t2
WHERE t2.group_id = t.group_id
ORDER BY
CASE @sort_by WHEN 'a' THEN a END,
CASE @sort_by WHEN 'b' THEN b END
LIMIT 1) AS dynamic_first
FROM t;
这个方法绕开了窗口定义限制,但要注意:
- 没有
PARTITION BY的等价性,得靠WHERE显式关联 -
LIMIT/TOP在不同数据库写法不同,SQL Server 要写TOP 1,Oracle 用ROWNUM = 1 - 缺少
ORDER BY稳定性保障时,结果可能非确定
真正难处理的是跨数据库兼容 + 动态帧 + 高性能三者同时要求的场景——这种时候,往往该考虑把逻辑提到应用层做分组排序,而不是硬塞进一条 SQL 里。










