总结 - 统计模型关联关系个数的不同方法
9

前言

入住社区也有两个月了,因为平时自己只接触英文资讯,所以计划开始把阅读到的好文章搬一些来这里:不会去逐字翻译,做下详细的总结,再加点自己的观点,希望大家能够共同进步!每篇的最后会贴上原英文链接。

统计 Eloquent 模型关联关系的个数

文章中介绍了三种方法:query builderthe relationshipeloquent collection,主要都是用 SQL 语句做统计。因此它并不包括:先加载关联模型到内存中,然后使用 php 或 eloquent 的 count 方法进行统计,像 $user->posts->count()count($user->posts)

Query Bilder

这个应该很熟悉,使用 withCount($relationship),然后通过 {$relationship}_count 得到个数:

$users = User::withCount('posts')->get();

$users->first()->posts_count;

当然你也可以查询多个关联模型:

$users = User::withCount(['posts', 'addresses'])->get();

$users->first()->posts_count;
$users->first()->addresses_count;

以上官方文档中都有介绍,而且你还可以加入 constraints 和 alias:https://laravel-china.org/docs/laravel/5.7/eloquent-relationships/2295#d1b30d

其实我们可以更进一步,比如统计 User 的关联关系 -> Post 的关联关系 -> Comment 的个数:

$user = User::with(['posts' => function ($query) {
    $query->withCount('comments');
}])->find(1);

$user->posts->first()->comments_count;

当然如果你用了 hasManyThrough,就会更简单!

最后,如果你有很多语句都要查询个数的话,可以写在 Model 的 $withCount 属性里,这样每次都能够直接使用:

class User extends Model
{
    protected $withCount = ['posts'];
}

Relationships

这是个很简单的方法:

$user->posts()->count();

新手会经常提问它和文章开头那个 count 的区别:这个每调用一次就会执行一次 sql 语句

基于上面的语句,其实我们可以在 Model 里写个 posts_count 属性:

class User extends Model
{
    public function posts()
    {
        return $this->hasMany(Post::class);
    }

    protected function getpostsCountAttribute($value)
    {
        return $value ?? $this->posts_count = $this->posts()->count();
    }
}

这样就可以使用 $user->posts_count,第一次调用过后,后面就可以避免每次都去触碰数据库。不过这个方法并不能避免 N+1 的问题,比如你第一次查询所有的用户,然后每个用户输出 posts_count,它还是会执行 N 次。所以这个方案有点鸡肋,不建议使用。

Eloquent collection

这个是本文的重点,有兴趣的可以看下这个 commit,它是 Laravel 5.7 新加的功能 loadCount,特别可以用于 Polymorphic Relationships 的反向统计。

假设我们有个 Tag 模型:

class Tag extends Model
{
    public function taggable()
    {
        return $this->morphTo();
    }
}

然后 User 和 Post 都可以被设置多个 tags:

class User extends Model
{
    public function tags()
    {
        return $this->morphMany(Tag::class, 'taggable');
    }

    public function addresses()
    {
        return $this->hasMany(Address::class);
    }
}

class Post extends Model
{
    public function tags()
    {
        return $this->morphMany(Tag::class, 'taggable');
    }

    public function comments()
    {
        return $this->hasMany(Comment::class);
    }
}

现在我们想要输出所有的 tags,并且显示相关联的 Users 的 addresses_count 和 Posts 的 comments_count,第一反应可能会写这样的语句:

$tags = Tag::with(['taggable' => function ($query) {
    $query->withCount(['addresses', 'comments']);
}])->get();

但是它并不对,因为 taggable 既可能是 User,也可以是 Post,所以你会遇到比如:Post 并没有 addresses 的关联关系的错误。

正确的方法是先得到所有的 tags,然后 group_by taggable 的类:

$tags = Tag::with('taggable')->get();

$groups = $tags->map(function ($tag) {
    return $tag->taggable;
})->groupBy(function ($taggable) {
    return get_class($taggable);
});

上面会得到类似如下的结果:

[
    'App\Models\User' => [
        $user_1,
        $user_2,
    ],
    'App\Models\Post' => [
        $post_1,
        $post_2,
    ],
]

接下来你就可以用新添加的 loadCount 方法做统计:

$groups[User::class]->loadCount('addresses');
$groups[Post::class]->loadCount('comments');

同样的, loadcount 也支持加载多个关联关系:

$groups[User::class]->loadCount(['addresses', 'orders']);

最后附上原英文链接:https://timacdonald.me/loading-eloquent-relationship-counts/

《L01 基础入门》
我们将带你从零开发一个项目并部署到线上,本课程教授 Web 开发中专业、实用的技能,如 Git 工作流、Laravel Mix 前端工作流等。
《L04 微信小程序从零到发布》
从小程序个人账户申请开始,带你一步步进行开发一个微信小程序,直到提交微信控制台上线发布。
讨论数量: 0
(= ̄ω ̄=)··· 暂无内容!

  • 请注意单词拼写,以及中英文排版,参考此页
  • 支持 Markdown 格式, **粗体**、~~删除线~~、`单行代码`, 更多语法请见这里 Markdown 语法
  • 支持表情,使用方法请见 Emoji 自动补全来咯,可用的 Emoji 请见 :metal: :point_right: Emoji 列表 :star: :sparkles:
  • 上传图片, 支持拖拽和剪切板黏贴上传, 格式限制 - jpg, png, gif
  • 发布框支持本地存储功能,会在内容变更时保存,「提交」按钮点击时清空
  请勿发布不友善或者负能量的内容。与人为善,比聪明更重要!