You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Laravel中如何用Eloquent ORM处理单行多关联ID的关联查询?

问题描述

我的series表数据格式如下:

{"id": 9770,"name": "Name of serie","type": null,"release_date": "2021-09-30 00:00:00","year": 2021,"genre": "4,600,2815"}

我希望通过genre列中逗号分隔的多个ID值,从genres表获取对应数据,并将genres字段包含在返回的主数据中。

当前的Serie和Genre模型代码:

class Serie extends Model
{
    protected $table = 'series';

    public function genres()
    {
        $genreIds = explode(',', $this->genre);
        return Genre::whereIn('id', $genreIds)->get();
    }
}

class Genre extends Model
{
    protected $table = 'genres';
}

控制器代码:

$page = $request->input('page', 1);
$perPage = $request->input('perPage', 10);

$allSeries = Serie::with('genres')->paginate($perPage, ['*'], 'page', $page);

return response()->json($allSeries, 200, [], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);

尝试修改genres关联方法后,查询成功但返回的genres数据为空:

public function genres()
{
    $genreIds = explode(',', $this->genre);
    return $this->hasMany(Genre::class, 'id', 'genre')->whereIn('id', $genreIds);
}

也尝试过以下查询方式,同样无效:

$query = Serie::query();

$query->with([
    'genres' => function ($query) {
        $genreIds = explode(',', $this->genre);
        $query->whereIn('id', $genreIds);
    },
]);

$allSeries = $query->paginate($perPage, ['*'], 'page', $page);

限制条件:无法在genres表中添加关联主数据的字段,请问在Laravel中该如何解决?


解决方案

方法一:使用访问器(推荐)

由于genre字段是逗号分隔的ID字符串,不符合Laravel默认的关联规则(需要关联表存在外键),最直接的方式是给Serie模型添加访问器,自动加载并返回对应分类数据:

class Serie extends Model
{
    protected $table = 'series';
    // 自动将genres字段追加到JSON返回结果中
    protected $appends = ['genres'];

    public function getGenresAttribute()
    {
        if (empty($this->genre)) {
            return collect();
        }
        $genreIds = explode(',', $this->genre);
        return Genre::whereIn('id', $genreIds)->get();
    }
}

控制器无需再使用with('genres'),直接查询即可:

$page = $request->input('page', 1);
$perPage = $request->input('perPage', 10);

$allSeries = Serie::paginate($perPage, ['*'], 'page', $page);

return response()->json($allSeries, 200, [], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);

方法二:手动预加载优化(避免N+1查询)

如果担心访问器导致N+1查询(每条Serie单独查询一次genres),可以手动批量获取分类数据再匹配:

$page = $request->input('page', 1);
$perPage = $request->input('perPage', 10);

$allSeries = Serie::paginate($perPage, ['*'], 'page', $page);

// 收集所有Serie的genre ID
$allGenreIds = [];
foreach ($allSeries as $serie) {
    if (!empty($serie->genre)) {
        $allGenreIds = array_merge($allGenreIds, explode(',', $serie->genre));
    }
}
$allGenreIds = array_unique($allGenreIds);

// 批量获取分类数据并按ID分组
$genres = Genre::whereIn('id', $allGenreIds)->get()->keyBy('id');

// 给每个Serie匹配对应分类
foreach ($allSeries as $serie) {
    if (empty($serie->genre)) {
        $serie->genres = collect();
        continue;
    }
    $genreIds = explode(',', $serie->genre);
    $serie->genres = $genres->only($genreIds);
}

return response()->json($allSeries, 200, [], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);

之前方法无效的原因

  • 最初的genres方法直接返回get()结果,不属于Laravel的关联定义,with('genres')无法识别并预加载,会导致逻辑错误。
  • 修改后的hasMany写法错误:hasMany的参数顺序是关联模型类、关联表外键、当前模型本地键,但genres表不存在指向series的外键,关联逻辑不成立,所以返回空数据。
  • 闭包预加载中,$this->genre无法获取单个Serie的字段值,因为闭包是在全局查询上下文执行,而非针对单个模型实例,导致$genreIds无效。

内容的提问来源于stack exchange,提问作者spo spo

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.04 18:10:33