Laravel中复合主键模型需显式定义外键数组、补全关联参数并建联合索引,嵌套查询须用数组路径与闭包嵌套,聚合需手动JOIN或子查询。

在 Laravel 中对含复合主键的模型执行嵌套查询(如 with、whereHas、嵌套聚合)时,Eloquent 默认无法识别多字段主键语义,直接调用会报 RelationNotFoundException 或静默返回空结果,必须显式补全关联定义与外键映射。
定义复合主键模型并声明外键关系
第一步:在模型中使用 HasCompositePrimaryKey Trait,并显式设置 $primaryKey 为数组。
第二步:在所有涉及该模型的 belongsTo 关联方法中,【必须传入第二个参数 $foreignKey】,不能依赖约定。例如 Product 模型属于 Order,但外键是 order_code + order_year,则:
public function order() { return $this->belongsTo(Order::class, ['order_code', 'order_year']); }
第三步:检查迁移文件中对应外键字段是否已建立联合索引——缺少索引会导致 JOIN 全表扫描,10 万行以上数据时查询超时。
嵌套预加载 with() 的正确写法
只能用数组形式一次性传入完整嵌套路径,点号语法仅限一级关联后继续展开,且每级都需模型正确定义。
方法一:三级预加载(User → Orders → Products)
User::with(['orders' => fn ($q) => $q->with('products')])->find(1);
方法二:跨复合主键模型的嵌套(Order → LineItems → Sku)
Order::with(['lineItems.sku'])->get();
注意:lineItems 关联方法内部必须返回 $this->hasMany(LineItem::class, ['order_code', 'order_year']);,否则 .sku 会解析失败。
嵌套 whereHas 的闭包嵌套结构
whereHas 不支持点号路径作为第一个参数,必须逐层用闭包嵌套。
第一步:先查出带复合主键约束的中间记录
第二步:在内层闭包中操作目标关联模型
Order::whereHas('lineItems', function ($q) {
$q->where('status', 'shipped')->whereHas('sku', function ($s) {
$s->where('is_active', true);
});
})->get();
如果 lineItems 表本身用 (order_code, line_no) 作主键,则其模型中必须定义 protected $primaryKey = ['order_code', 'line_no']; 且 $incrementing = false;否则 whereHas('sku') 生成的 JOIN 条件会丢失 line_no 字段,导致匹配失败。
嵌套聚合查询(如统计用户总订单商品数)
不能用 withCount('orders.products'),这会触发 RelationNotFoundException。
方法一:用 DB::table 手动三表 JOIN
DB::table('users')
->join('orders', 'users.id', '=', 'orders.user_id')
->join('line_items', function ($join) {
$join->on('orders.order_code', '=', 'line_items.order_code')
->on('orders.order_year', '=', 'line_items.order_year');
})
->select('users.id', DB::raw('COUNT(*) as item_count'))
->groupBy('users.id')
->get();
方法二:子查询方式(更安全,避免 GROUP BY 字段爆炸)
User::select('id', 'name')
->withCount(['orders as total_orders' => function ($q) {
$q->select(DB::raw('SUM((SELECT COUNT(*) FROM line_items WHERE line_items.order_code = orders.order_code AND line_items.order_year = orders.order_year))'));
}])
->get();











