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

Laravel中如何通过关联将多pivot表结果合并为单个数据集

问题描述

我的Laravel应用有以下数据库表结构:

user_comments
    id - integer
    name - string
    created_at - timestamp
 
user_actions
    id - integer
    name - string
    created_at - timestamp
 
users
    id - integer
    name - string
 
user_history
    user_id - integer
    history_id - integer
    history_type - string

App\Models\User模型当前的关联关系:

/**
 * Get all of the comments that are associated with this user.
 */
public function comments() : BelongsToMany {
    return $this->belongsToMany(UserComment::class, 'user_history', 'user_id', 'history_id')->where('user_history.history_type', 'comment');
}

/**
 * Get all of the actions that are associated with this user.
 */
public function actions() : BelongsToMany {
    return $this->belongsToMany(UserAction::class, 'user_history', 'user_id', 'history_id')->where('user_history.history_type', 'action');
}

需求:

  1. 能单独获取各类型的历史数据(如评论、操作)
  2. 能通过一个关联直接获取所有历史记录的合并集合,按created_at排序,后续还会新增user_photos、user_emails等关联类型,需要通用方案。

当前做法是分别获取各关联数据,合并集合后排序,但希望通过数据库层面的关联逻辑实现,避免手动合并。


解决方案

一、重构为多态多对多关联

利用Laravel的多态多对多关联特性,既能保留单独获取各类型数据的能力,又能实现统一的历史记录查询。

  1. 修改各历史模型
    给每个历史模型(UserComment、UserAction等)添加与User的多态关联:
// App\Models\UserComment.php
namespace App\Models;

use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\MorphedByMany;

class UserComment extends Model
{
    public function users(): MorphedByMany
    {
        // 'history'对应user_history表中的history_id/history_type字段前缀
        return $this->morphedByMany(User::class, 'history', 'user_history');
    }
}
// App\Models\UserAction.php
namespace App\Models;

use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\MorphedByMany;

class UserAction extends Model
{
    public function users(): MorphedByMany
    {
        return $this->morphedByMany(User::class, 'history', 'user_history');
    }
}
  1. 更新User模型的关联
    保留单独获取各类型数据的关联(简化写法),同时新增统一获取所有历史记录的关联:
// App\Models\User.php
namespace App\Models;

use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\MorphToMany;

class User extends Model
{
    // 单独获取评论记录
    public function comments(): MorphToMany
    {
        return $this->morphToMany(UserComment::class, 'history', 'user_history')
            ->select([
                'user_comments.id',
                'user_comments.name',
                'user_comments.created_at',
                'user_history.history_type' // 用于区分记录类型
            ]);
    }

    // 单独获取操作记录
    public function actions(): MorphToMany
    {
        return $this->morphToMany(UserAction::class, 'history', 'user_history')
            ->select([
                'user_actions.id',
                'user_actions.name',
                'user_actions.created_at',
                'user_history.history_type'
            ]);
    }

    // 统一获取所有历史记录(按created_at降序)
    public function history(): MorphToMany
    {
        // 初始化查询,以comments的查询为基础
        $query = $this->comments()->getQuery();

        // 合并其他类型的历史记录查询(后续新增模型只需添加到数组)
        $historyModels = [UserAction::class, /* UserPhoto::class, UserEmail::class */];
        foreach ($historyModels as $model) {
            $tableName = $model::getTableName();
            $relatedQuery = $this->morphToMany($model, 'history', 'user_history')
                ->select([
                    "{$tableName}.id",
                    "{$tableName}.name",
                    "{$tableName}.created_at",
                    'user_history.history_type'
                ])->getQuery();

            $query->union($relatedQuery);
        }

        // 创建MorphToMany实例并替换查询
        $morphRelation = $this->morphToMany(UserComment::class, 'history', 'user_history');
        $morphRelation->setQuery($query);

        // 数据库层面按创建时间降序排序
        return $morphRelation->orderBy('created_at', 'desc');
    }
}

二、关键说明

  • 字段一致性:使用select()指定每个关联查询返回的字段,确保UNION操作的字段数量、类型一致,这是数据库层面合并的前提。
  • 扩展性:后续新增user_photos、user_emails等类型时,只需:
    1. 给对应的模型添加users()多态关联;
    2. 在User模型的history()方法的$historyModels数组中新增模型类即可。
  • 使用方式:
    • 单独获取:$user->comments、$user->actions;
    • 获取统一排序的历史:$user->history,遍历的时候可通过history_type区分记录类型,实现不同的展示逻辑。

三、替代方案(字段差异极大时)

如果各历史表字段差异极大,无法通过UNION合并,可以用预加载+集合排序的方式优化性能:

// User模型中新增方法
public function getAllHistory()
{
    // 预加载所有需要的关联,避免N+1问题
    $this->load(['comments', 'actions', /* 其他关联 */]);

    // 合并所有关联集合并排序
    return $this->comments
        ->merge($this->actions)
        // 后续新增的关联继续merge
        ->sortByDesc('created_at');
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.02 07:05:20