Laravel 8+ 和 PHP 7.4+ 才真正支持原生窗口函数,需 MySQL 8.0+/PostgreSQL 11+,须用 selectRaw() 显式声明,禁用 orderBy()/groupBy() 干扰,SQLite 不支持。

PHP 7.4+ 和 Laravel 8+ 才真正支持原生窗口函数
直接说结论:Laravel 8 之前(含 7.x)的 Eloquent 根本不解析 OVER()、PARTITION BY、ROW_NUMBER() 这类语法;底层 Query Builder 会把它们当普通字符串拼进 SQL,但 MySQL/PostgreSQL 不认,执行时报错 SQLSTATE[42000]: Syntax error or access violation。必须确认两点:PHP 版本 ≥ 7.4(因需 PDO 支持命名参数绑定),Laravel ≥ 8.0(Query Builder 开始允许在 selectRaw() 中安全混用原生窗口表达式)。
用 selectRaw() 写窗口函数,别碰 orderBy() 或 groupBy()
Eloquent 的链式方法(如 orderBy()、groupBy())会干扰窗口函数所需的 SQL 结构——窗口函数必须出现在 SELECT 列表里,且不能被 ORM 自动重排。正确做法是把整个窗口表达式塞进 selectRaw(),并手动控制字段顺序:
DB::table('sales')
->selectRaw('product_id')
->selectRaw('amount')
->selectRaw('SUM(amount) OVER (PARTITION BY product_id ORDER BY created_at) AS running_total')
->selectRaw('ROW_NUMBER() OVER (PARTITION BY product_id ORDER BY amount DESC) AS rank_in_product')
->where('created_at', '>=', '2024-01-01')
->get();
- 每个窗口函数必须独立写一个
selectRaw(),不能合并到同一行(否则字段别名冲突或解析失败) -
PARTITION BY和ORDER BY里的字段名要和表实际列一致,别用模型属性名(如created_at,不是createdAt) - 避免在同一个查询里混用
orderBy('product_id')—— 它会生成额外的ORDER BY子句,可能覆盖窗口函数内部的ORDER BY
MySQL 8.0+ 和 PostgreSQL 11+ 是硬门槛,SQLite 不行
窗口函数不是 PHP 或 Laravel 实现的,是数据库引擎能力。Laravel 只负责发 SQL,执行靠 DBMS。所以:
- MySQL 必须 ≥ 8.0(5.7 及以前完全不识别
OVER关键字,报错ERROR 1064) - PostgreSQL 必须 ≥ 11(10 及以前只支持有限窗口函数,如无
RANGE框架支持) - SQLite 原生不支持任何窗口函数(即使 Laravel 用
selectRaw()发过去,也会直接报no such function: ROW_NUMBER) - 连接配置里
'strict' => true会加剧报错,开发期建议设为false,方便看到原始 SQL 错误
想封装复用?别写 Eloquent Scope,用 Query Builder 方法更稳
很多人想写个 scopeWithRanking(),但 Eloquent scope 本质是调用 where() 类方法,没法安全注入 SELECT 列。强行封装容易漏掉 selectRaw() 上下文,导致字段丢失或别名错乱。推荐方案是定义一个静态工具方法:
立即学习“PHP免费学习笔记(深入)”;
class SalesQuery
{
public static function withRunningTotal($query, $partitionBy = 'product_id')
{
return $query->selectRaw("SUM(amount) OVER (PARTITION BY {$partitionBy} ORDER BY created_at) AS running_total");
}
}
// 使用
Sales::query()
->select('id', 'product_id', 'amount')
->when(request('with_rank'), fn ($q) => SalesQuery::withRunningTotal($q))
->get();
关键点:所有窗口字段必须显式 select() 或 selectRaw() 声明,Eloquent 不会自动补全;如果模型有 $casts 或 $appends,窗口计算结果不会触发类型转换,得手动 cast(比如 asInt() 或 map() 处理)。



















