
本文介绍如何在 shifts 表中按 vehicle_id 分组,并根据 user_id 和 subdriver_id 的非空组合情况,统一提取主关联 ID(优先 user_id,空则取 subdriver_id),同时聚合对应 shift_id,适用于多角色共存但非必填的调度类业务场景。
本文介绍如何在 `shifts` 表中按 `vehicle_id` 分组,并根据 `user_id` 和 `subdriver_id` 的非空组合情况,统一提取主关联 id(优先 `user_id`,空则取 `subdriver_id`),同时聚合对应 `shift_id`,适用于多角色共存但非必填的调度类业务场景。
在实际调度系统中,shifts 表常存在“主司机(user_id)”和“副司机(subdriver_id)”二选一或两者并存的情况。若直接按 vehicle_id 分组,会导致同一车辆下因 user_id/subdriver_id 不同而被错误拆分;若强行 GROUP BY vehicle_id, user_id, subdriver_id,又会使本应合并的单角色班次碎片化。因此,需通过逻辑判断动态生成一个“归一化用户标识”,再以此为基础聚合。
核心思路是:用 CASE 表达式构造一个虚拟列,优先取 user_id(非空时),其次取 subdriver_id(当 user_id 为空时),若两者皆为空则返回 NULL(可根据业务需要补充 ELSE 0 或 COALESCE 处理)。配合 GROUP_CONCAT()(MySQL)或 STRING_AGG()(PostgreSQL)实现 shift_id 数组化。
以下是 Laravel Eloquent 查询构建器的完整实现(适配 MySQL):
$result = DB::table('shifts')
->select(
DB::raw('CASE
WHEN shifts.user_id IS NOT NULL THEN shifts.user_id
WHEN shifts.subdriver_id IS NOT NULL THEN shifts.subdriver_id
ELSE NULL
END AS unified_user_id'),
'shifts.vehicle_id',
DB::raw('GROUP_CONCAT(shifts.id) AS shift_ids')
)
->leftJoin('users as u1', 'u1.id', '=', 'shifts.user_id')
->leftJoin('users as u2', 'u2.subdriver_id', '=', 'shifts.subdriver_id')
->whereNotNull('shifts.vehicle_id') // 排除无效车辆记录
->groupBy('shifts.vehicle_id', DB::raw('unified_user_id')) // ⚠️ 注意:GROUP BY 必须包含所有非聚合SELECT列
->get();✅ 关键说明:
GROUP BY必须显式包含shifts.vehicle_id和unified_user_id(即CASE表达式结果),否则 MySQL 8.0+ 会报错(ONLY_FULL_GROUP_BY模式下);- 两个
LEFT JOIN仅用于后续扩展(如需关联用户姓名),本例中未在SELECT中使用,可酌情移除以提升性能;- 若需兼容 PostgreSQL,将
GROUP_CONCAT(shifts.id)替换为STRING_AGG(shifts.id::TEXT, ','),并将CASE中的NULL处理保持一致;- 返回的
shift_ids是逗号分隔字符串,应用层可explode(',', $row->shift_ids)转为数组;如需 JSON 数组格式,MySQL 5.7+ 可改用JSON_ARRAYAGG(shifts.id)。
最终结果每行代表一个「车辆 + 主关联人」组合,例如:{ "unified_user_id": 101, "vehicle_id": 501, "shift_ids": "123,456" }
清晰满足:单 user_id、单 subdriver_id 或两者共存时均归入同一分组,且 shift_id 完整聚合。

















