Laravel MySQL 按数量排序

6
我正在使用Laravel和MySQL,有一个名为post的表,代表用户可以在其上进行评论的帖子。现在我想按每个帖子的评论数量升序/降序排序,如何在Laravel中实现?我不想在post表中添加一个字段来跟踪每个帖子的评论数,因为每次添加/删除评论或评论的评论时手动更新该字段会让我疯掉...
以下是我创建posts表和comments表的方法:
Schema::create('posts', function($table) {
    $table->increments('id');
    $table->string('title', 100)->unique();
    $table->string('content', 2000);
    $table->timestamps();
});
Schema::create('comments', function($table) {
    $table->increments('id');
    $table->string('content', 2000);
    $table->unsignedInteger('post_id');
    $table->foreign('post_id')->references('id')->on('posts')->onDelete('cascade')->onUpdate('cascade');
    $table->unsignedInteger('parent_id')->nullable();
    $table->foreign('parent_id')->references('id')->on('comments')->onDelete('cascade')->onUpdate('cascade');
    $table->timestamps();
});

这是我在我的Post模型中设置帖子和评论之间关系的方法:

public function comments() {
    return $this->hasMany('Comment', 'post_id');
}

在评论模型中:
public function post() {
    return $this->belongsTo('Post', 'post_id');
}
2个回答

4
你可以按照你展示的方法来实现,但是现在你会从数据库中获取所有的条目。如果你有100个帖子,每个帖子都有100条评论,那么当你排序时,你将从数据库中获取10000行数据(我假设在排序时你不想显示这些评论)。
你可以在你的帖子模型中添加以下内容:
public function commentsCountRelation()
{
    return $this->hasOne('Comment')->selectRaw('post_id, count(*) as count')
        ->groupBy('post_id');
}

public function getCommentsCountAttribute()
{

    return $this->commentsCountRelation ?
        $this->commentsCountRelation->count : 0;
}

现在你可以使用:

$posts = Post::with('commentsCount')->get()->sortBy(function($post) {
    return $post->comments_count;
});

按升序排序或

$posts = Post::with('commentsCount')->get()->sortBy(function($post) {
    return $post->comments_count;
}, SORT_REGULAR, true);

降序排列。

顺便说一下,使用 sortBy 再使用 reverse 不是一个好主意,你应该像我展示的那样使用 sortBy 参数。


谢谢,我可以在你的假设中补充一点,如果使用->paginate($perPage)对帖子进行排序后如何分页? - dulan
@dulan 这可能是个问题,但我不是 Eloquent 专家。你可以提出一个新的问题,也许其他人有另外的解决方案或者知道如何在这种情况下处理分页。最简单的解决方案是像你一开始说的那样添加计数器列,并在数据库中添加触发器来更新这个数字,但也许还有其他解决方案。 - Marcin Nabiałek
我正在使用Laravel模型事件而非数据库触发器。我这么说跟手动跟踪评论计数复杂有关,因为我正在使用onDelete('cascade'),所以如果删除一条评论,所有子评论都会被删除,那么我就必须计算该评论有多少个子评论,还要进行迭代、递归等操作。模型事件仅会为被删除的原始评论触发一次,对于级联则不会触发。 - dulan
@dulan 但我认为使用数据库触发器应该可以解决这个问题,但我还没有测试过,所以不确定。 - Marcin Nabiałek

1

我想我已经想出了一个解决方法:

$posts = Post::with('comments')->get()->sortBy(function($post) {
    return $post->comments->count();
});

这个按评论数升序排序,如果你想按降序排序,可以这样做:
$posts = Post::with('comments')->get()->sortBy(function($post) {
    return $post->comments->count();
})->reverse();

这太棒了。我在计数器上漏掉了括号,所以我编辑了我的注释。 - Nicholas Decker

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接