
本文介绍如何对包含可选用户 id 和副驾 id 的排班表进行分组查询,通过 case 表达式统一主键逻辑,并使用 group_concat 聚合关联的排班 id,确保单侧非空或双侧均存在时结果语义一致。
本文介绍如何对包含可选用户 id 和副驾 id 的排班表进行分组查询,通过 case 表达式统一主键逻辑,并使用 group_concat 聚合关联的排班 id,确保单侧非空或双侧均存在时结果语义一致。
在处理如 Shifts(排班)、Users(用户)和 Vehicles(车辆)三表关联场景时,一个常见挑战是:shifts 表中的 user_id 和 subdriver_id 均为可空字段——某些排班仅指定司机,某些仅指定副驾,还有些同时指定两者。此时若直接按 vehicle_id 分组,会导致同一车辆下本应逻辑归属同一“操作主体”的记录被错误拆分。
核心思路是:构造一个逻辑上的“责任主体 ID”作为分组依据之一,该 ID 优先取 user_id(当其非空),其次取 subdriver_id(当 user_id 为空而 subdriver_id 非空),若两者皆为空则返回 NULL(可根据业务需要额外过滤)。这正是 CASE 表达式的典型应用场景。
以下是 Laravel Eloquent Query Builder 实现示例(兼容 MySQL):
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 responsible_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('responsible_id') // 可选:排除无责任主体的异常记录
->groupBy('responsible_id', 'shifts.vehicle_id')
->get();
⚠️ 注意事项:
-
分组字段必须完整覆盖 SELECT 中所有非聚合列:此处需同时
GROUP BY responsible_id, vehicle_id,否则 MySQL 严格模式将报错; -
GROUP_CONCAT默认以逗号拼接,如需自定义分隔符(如 JSON 数组),可改用JSON_ARRAYAGG(shifts.id)(MySQL 5.7.22+); - 若业务要求区分
user_id和subdriver_id来源,可在SELECT中增加标志字段,例如DB::raw("CASE WHEN shifts.user_id IS NOT NULL THEN 'user' ELSE 'subdriver' END AS role"); - 左连接
users表两次(别名u1/u2)仅为预留扩展空间(如后续需获取用户姓名),当前逻辑仅依赖shifts表原始字段,故实际可省略连接以提升性能。
最终结果将按“责任主体 + 车辆”组合去重聚合,每个分组返回清晰的 responsible_id、vehicle_id 和字符串化的 shift_ids,便于前端解析或进一步处理。










