如何在Laravel中不使用原生SQL实现分区查询取各部门薪资Top2
Laravel 实现部门内薪资Top2员工查询的方案
原SQL的核心逻辑为:按部门对员工数据做分区,在分区内按薪资从高到低生成行号,最终筛选行号符合TopN范围的记录,完全可以通过Laravel Eloquent/查询构建器实现,无需编写整段原生SQL。
方案一:Laravel 8+ 原生窗口函数构建(最贴近原SQL逻辑)
Laravel 8.x及以上版本,查询构建器内置了窗口函数的封装能力,可以1:1复现原SQL的执行逻辑:
use App\Models\Employee; $topPaidStaff = Employee::query() ->select(['dept', 'name', 'salary']) ->selectSub(function ($query) { // 构建row_number窗口函数逻辑 $query->rowNumber() ->partitionBy('dept') ->orderByDesc('salary'); }, 'rn') ->where('rn', '<=', 2) ->get();
各部分对应关系:
rowNumber()对应SQL原生的row_number()窗口函数partitionBy('dept')实现按部门划分数据分区的规则orderByDesc('salary')定义分区内按薪资降序的排序规则where('rn', '<=', 2)筛选每个部门排序前2的员工
注意:原示例SQL中
where T.rn < 2的条件实际只会提取每个部门的第1名员工,和“筛选薪资最高2名”的需求不匹配,需调整为<=2才能覆盖top2范围。
方案二:低版本Laravel兼容实现
如果使用Laravel 7及更早版本,没有内置窗口函数封装,可以通过关联子查询实现同等效果,依然不需要编写整段原生SQL:
use Illuminate\Support\Facades\DB; $topPaidStaff = DB::table('employees as t1') ->select(['t1.dept', 't1.name', 't1.salary']) ->whereRaw( '(SELECT COUNT(DISTINCT t2.salary) FROM employees as t2 WHERE t2.dept = t1.dept AND t2.salary >= t1.salary) <= 2' ) ->orderBy('t1.dept') ->orderByDesc('t1.salary') ->get();
该逻辑的执行结果和窗口函数方案等价:对每一位员工,统计同部门内薪资不低于他的薪资档位数量,只要数量不超过2,就属于部门内薪资Top2范围。
关联场景下的简化实现
如果已经定义了部门和员工的Eloquent关联,可以直接通过ofMany方法快速实现TopN查询,无需手写子查询逻辑:
// Department 模型内定义关联方法 public function topTwoPaidEmployees() { return $this->hasMany(Employee::class) ->ofMany('salary', 'max', function ($query) { $query->limit(2); }); }
调用时直接预加载关联即可获取每个部门的Top2员工:
$departmentsWithTopStaff = Department::with('topTwoPaidEmployees')->get();
内容的提问来源于stack exchange,提问作者Rashid Abbasi
相关产品推荐
相关产品推荐

