
本文介绍如何在 shifts 表中对 vehicle_id 进行分组,同时智能合并 user_id 和 subdriver_id(优先取 user_id,仅当其为空时取 subdriver_id),并聚合对应 shift_id,适用于用户与副驾共存但非必填的排班场景。
本文介绍如何在 `shifts` 表中对 `vehicle_id` 进行分组,同时智能合并 `user_id` 和 `subdriver_id`(优先取 `user_id`,仅当其为空时取 `subdriver_id`),并聚合对应 `shift_id`,适用于用户与副驾共存但非必填的排班场景。
在实际排班系统中,shifts 表常存在「主司机(user_id)」和「副驾(subdriver_id)」二选一或两者共存的情况。若直接按 vehicle_id 分组,会导致同一车辆下因 user_id/subdriver_id 不同而被错误拆分;若强行联合分组,则无法统一标识“该车次归属主体”。此时需通过逻辑判别生成一个归一化的关联 ID,并配合聚合函数完成结构化输出。
核心思路是:用 CASE 表达式构造统一的 user_id 字段——当 user_id 非空时优先采用;仅当 user_id 为 NULL 且 subdriver_id 存在时,退而取 subdriver_id;若两者皆为空,则该记录逻辑上无效(可根据业务补充 ELSE NULL 处理)。再结合 GROUP_CONCAT()(MySQL)或 STRING_AGG()(PostgreSQL)聚合 shift.id,最终按 vehicle_id 分组即可满足需求。
以下是 Laravel Eloquent 查询示例(适配 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 user_id"),
'shifts.vehicle_id',
DB::raw('GROUP_CONCAT(shifts.id) AS shift_id')
)
->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("
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
"))
->get();⚠️ 注意事项:
-
必须将
CASE表达式加入GROUP BY:仅按vehicle_id分组会导致user_id字段值不明确(SQL 标准不允许 SELECT 中非聚合、非分组字段),因此需同步按判别后的user_id分组,确保每组语义唯一。 -
GROUP_CONCAT默认以逗号连接,如需自定义分隔符(如|),可写为GROUP_CONCAT(shifts.id SEPARATOR '|')。 - 若使用 PostgreSQL,需替换为
STRING_AGG(shifts.id::TEXT, ',')并调整CASE语法。 - 左连接
users表两次(分别关联user_id和subdriver_id)仅为扩展用户信息预留,本例中未选取用户字段,可按需移除以提升性能。 - 建议在
shifts(user_id)、shifts(subdriver_id)、shifts(vehicle_id)上建立复合索引(如(vehicle_id, user_id, subdriver_id))以加速分组查询。
最终结果将返回形如 ['user_id' => 123, 'vehicle_id' => 45, 'shift_id' => '101,105,112'] 的集合,清晰表达「某车辆由某主体(主驾或副驾)承担的多个班次」,兼顾数据完整性与业务可读性。

















