如何对 Eloquent 关系中的数据透视表列进行 GROUP 和 SUM?

Posted

技术标签:

【中文标题】如何对 Eloquent 关系中的数据透视表列进行 GROUP 和 SUM?【英文标题】:How to GROUP and SUM a pivot table column in Eloquent relationship? 【发布时间】:2015-03-31 19:08:33 【问题描述】:

在 Laravel 4 中;我有模型ProjectPart,它们与数据透视表project_part 具有多对多关系。数据透视表有一列 count,其中包含项目中使用的部件 ID 的编号,例如:

id  project_id  part_id count
24  6           230     3

这里的project_id 6,使用了3个part_id 230。

同一项目的一个部分可能会被多次列出,例如:

id  project_id  part_id count
24  6           230     3
92  6           230     1

当我显示我的项目的零件清单时,我不想显示 part_id 两次,所以我将结果分组。

我的 Projects 模型有这个:

public function parts()

    return $this->belongsToMany('Part', 'project_part', 'project_id', 'part_id')
         ->withPivot('count')
         ->withTimestamps()
         ->groupBy('pivot_part_id')

当然我的count 值不正确,我的问题来了:如何获得项目所有分组部分的总和?

意思是我的project_id 6 的零件清单应该是这样的:

part_id count
230     4

我真的很想把它放在Projects-Parts 关系中,这样我就可以急切地加载它。

如果没有遇到 N+1 问题,我无法解决如何做到这一点,感谢任何见解。


更新: 作为临时解决方法,我创建了一个演示者方法来获取项目中的总零件数。但这给了我 N+1 问题。

public function sumPart($project_id)

    $parts = DB::table('project_part')
        ->where('project_id', $project_id)
        ->where('part_id', $this->id)
        ->sum('count');

    return $parts;

【问题讨论】:

【参考方案1】:

来自code source:

我们需要使用“pivot_”前缀为所有枢轴列起别名,以便我们可以轻松地将它们从模型中提取出来,并在检索它们并将它们水合到模型中时将它们放入枢轴关系中。

所以你可以用select方法做同样的事情

public function parts()

    return $this->belongsToMany('Part', 'project_part', 'project_id', 'part_id')
        ->selectRaw('parts.*, sum(project_part.count) as pivot_count')
        ->withTimestamps()
        ->groupBy('project_part.pivot_part_id')

【讨论】:

这似乎有效,只是我必须将parts.* 也添加到selectRaw,否则我没有得到任何其他字段。我会彻底测试它并让你知道。谢谢:) 我认为你不需要在幕后添加parts.*,Eloquent add it。 虽然我也是这样,但如果不添加它,它就不是查询的一部分。 很奇怪,您实际上是如何构建查询的?你能分享执行的查询吗?你用什么版本?没有part.*,你会得到什么(请转储)? 我正在使用 Laravel 4.2,我已经移动了一些东西。我在控制器中做selectRaw,而不是在关系上,这样我可以选择是否要合并行。 pastebin.com/KTZRFigU这涵盖了吗?【参考方案2】:

尝试在Collection中求和,

$project->parts->sum('pivot.count');

这是我找到的最好方法。它很干净(易于阅读),并且能够在 parts 多对多定义中重用所有范围、排序和关系属性缓存。

@hebron 如果您使用with('parts') 到eager load,则此解决方案没有N+1 问题。因为$project->parts没有函数调用)是一个缓存属性,所以返回一个包含所有数据的Collection 实例。而sum('pivot.count')Collection 的一个方法,它包含纯函数助手(与数据库无关,如js 世界中的underscore)。

完整示例:

关系部分的定义:

class Project extends Model

    public function parts()
    
        return $this->belongsToMany('Part', 'project_part', 'project_id', 'part_id')
            ->withPivot('count')
            ->withTimestamps();
    

当你使用它时(注意,Eager load 对于避免 N+1 问题很重要),

App\Project::with('parts')->get()->each(function ($project) 
    dump($project->parts->sum('pivot.count'));
);

或者你可以在Project.php中定义sum函数,

class Project extends Model

    ...

    /**
     * Get parts count.
     *
     * @return integer
     */
    public function partsCount()
    
        return $this->parts->sum('pivot.count');
    

如果您想在调用方避免with('parts')(默认情况下急切加载部分),您可以添加$with 属性

class Project extends Model

    /**
     * The relations to eager load on every query.
     *
     * @var array
     */
    protected $with = ['parts'];

    ...

【讨论】:

这不会给我 N+1 的问题吗? @hebron 此解决方案没有 N+1 个问题。示例已更新。【参考方案3】:

您可以使用的最佳方法是:

$project->parts->sum('pivot.count');

我遇到了同样的问题,但这解决了我的问题。

【讨论】:

以上是关于如何对 Eloquent 关系中的数据透视表列进行 GROUP 和 SUM?的主要内容,如果未能解决你的问题,请参考以下文章

如何在 Eloquent ORM 中按多对多关系的数据透视表的字段进行排序

Laravel Eloquent按关系表列排序

Laravel Eloquent 按关系表列排序

SQL 动态数据透视表列顺序

如何通过 Eloquent 中具有多对多关系的外部表中的列聚合进行分组?

Eloquent Api 资源:显示数据透视表中的一列