
本文讲解如何在 laravel 中通过条件化 join 实现多态关系的精准关联——仅当 `scoreable_type` 匹配对应模型时,才分别关联 `supplementary_assessments` 或 `points_interaction_system` 表,并最终按用户 id 汇总所有积分。
在 Laravel 中处理多态关联(Polymorphic Relationships)时,若需在查询中按 scoreable_type 动态选择关联目标表(如仅当类型为 SupplementaryAssessment 时才 JOIN supplementary_assessments),不能依赖常规的 Eloquent 多态加载(如 morphTo()),而应使用原生查询构建器(DB::table())配合条件化 JOIN。
关键在于:必须使用 JOIN(内连接)而非 LEFT JOIN。原因在于 LEFT JOIN 会保留左侧 user_points 的所有行,即使 WHERE 条件不满足(如 scoreable_type 不匹配),右侧字段仍为 NULL,导致后续聚合逻辑混乱,且无法实现“仅对特定类型生效”的语义。而 JOIN 能天然确保:只有 scoreable_type 精确匹配且存在对应记录时,该行才被纳入结果集——这正是我们分表关联的前提。
以下是正确实现方式(含聚合与优化):
use Illuminate\Support\Facades\DB;
$results = DB::table('user_points')
->join('user_classes', function ($join) use ($user) {
$join->on('user_points.user_class_id', '=', 'user_classes.id')
->where('user_classes.user_id', $user->id);
})
// 仅关联 scoreable_type 包含 'SupplementaryAssessment' 的记录
->join('supplementary_assessments', function ($join) {
$join->on('user_points.scoreable_id', '=', 'supplementary_assessments.id')
->where('user_points.scoreable_type', 'LIKE', '%SupplementaryAssessment%');
})
// 仅关联 scoreable_type 包含 'PointInteractionSystem' 的记录
->join('points_interaction_system', function ($join) {
$join->on('user_points.scoreable_id', '=', 'points_interaction_system.id')
->where('user_points.scoreable_type', 'LIKE', '%PointInteractionSystem%');
})
->select(
'user_points.user_class_id',
DB::raw('SUM(COALESCE(supplementary_assessments.points, 0) + COALESCE(points_interaction_system.points, 0)) as total_points')
)
->groupBy('user_points.user_class_id')
->get();⚠️ 重要注意事项:
- LIKE '%ModelName%' 是临时方案,强烈建议改用全限定类名精确匹配(如 'App\\Models\\SupplementaryAssessment'),避免因命名相似导致误匹配;
- 若需同时支持两种类型(即一行 user_points 可能对应任一表,但不同时存在),上述双 JOIN 会因笛卡尔积导致重复计数。此时应改用 UNION ALL 分别查询再聚合,或使用 CASE WHEN + LEFT JOIN 配合 COALESCE;
- 更健壮的长期方案是:在 user_points 表中增加 scoreable_table 字段(如 'supplementary_assessments' 或 'points_interaction_system'),直接存储表名,避免字符串匹配开销与歧义。
最终,该查询将返回每个 user_class_id 对应的积分总和,逻辑清晰、性能可控,符合多态场景下的精准关联需求。










