3 条 sql 是实现知乎评论,7 条 sql 实现点赞 + 评论,且可扩展
数据结构
文章数据结构
Post
- id
- title
- body
- vote_count
- comment_count
评论数据结构
Comment
- id
- title
- body
- commentable_id
- commentable_type
- parent_id
- user_id
- first_depth_id 一级评论id
- vote_count
- comment_count
点赞数据结构
Vote
- id
- user_id
- voteable_id
- voteable_type
Model
Post
public function comments()
{
return $this->morphMany(Comment::class, 'commentable');
}
public function votes()
{
return $this->morphMany(Vote::class, 'voteable');
}
Comment
public function commentable()
{
return $this->morphTo();
}
public function parent()
{
return $this->belongsTo(Comment::class,'parent_id');
}
public function children()
{
return $this->hasMany(Comment::class, 'parent_id');
}
public function user()
{
return $this->belongsTo(User::class);
}
public function votes()
{
return $this->morphMany('App\Vote', 'voteable');
}
public function seconds()
{
return $this->hasMany(Comment::class,'first_depth_id');
}
Vote
public function voteable()
{
return $this->morphTo();
}
public function user()
{
return $this->belongsTo(User::class);
}
查询评论
1直接用模型查询评论及用户的数据,数据类型也符合前端的要求
直接用sql去查文章的评论及用户(不建议),因为用了7条sql
$posts = Post::with(['comments'=>function ($query){
$query->where('parent_id',null)->with(['user','seconds'=>function($query){
$query->with('user','parent.user');
}]);
}])->get();
debug 显示
返回到前端的数据
2查询出所有评论及用户,再去解析数据,返回前端。
该查询用到三条sql(建议)
$posts = Post::with('comments','comments.user')->get();
$posts = collect($posts->toArray())->map(function ($post){
$nestedKeys = [];
$post['comments'] = array_column($post['comments'], null, 'id');
foreach ($post['comments'] as $key=>$comment){
if(!$parent_id=$comment['parent_id']){
continue;
}
if (array_key_exists($first_depth_id=$comment['first_depth_id'], $post['comments'])) {
if(!isset($post['comments'][$first_depth_id]['seconds'])){
$post['comments'][$first_depth_id]['seconds'] = [];
}
$comment['parent']=$post['comments'][$parent_id];
$post['comments'][$first_depth_id]['seconds'][] = $comment;
$nestedKeys[]=$key;
}else{
$nestedKeys[]=$key;
}
}
foreach ($nestedKeys as $val){
unset($post['comments'][$val]);
}
return $post;
});
debug
返回到前端的数据,可以看到和上方的返回数据是一样的,而这个只用了三条sql
查询点赞+评论
由于点赞没有无限极的影响,所以实现起来比较简单
直接用模型实现
在评论的基础上加上点赞和点赞的用户即可(不建议,因为sql增加到12条)
$posts = Post::with(['comments'=>function ($query){
$query->where('parent_id',null)->with(['votes'=>function($query){
$query->with('user');
},'user','seconds'=>function($query){
$query->with(['user','parent.user','votes'=>function($query){
$query->with('user');
}]);
}]);
},'votes'=>function($query){
$query->with('user');
}])->get();
debug
返回的数据
先查出总数据再去解析
在评论的基础上加个点赞即可,用了7条sql(建议)
$posts = Post::with('comments','comments.user','votes.user','comments.votes.user')->get();
$posts = collect($posts->toArray())->map(function ($post){
$nestedKeys = [];
$post['comments'] = array_column($post['comments'], null, 'id');
foreach ($post['comments'] as $key=>$comment){
if(!$parent_id=$comment['parent_id']){
continue;
}
if (array_key_exists($first_depth_id=$comment['first_depth_id'], $post['comments'])) {
if(!isset($post['comments'][$first_depth_id]['seconds'])){
$post['comments'][$first_depth_id]['seconds'] = [];
}
$comment['parent']=$post['comments'][$parent_id];
$post['comments'][$first_depth_id]['seconds'][] = $comment;
$nestedKeys[]=$key;
}else{
$nestedKeys[]=$key;
}
}
foreach ($nestedKeys as $val){
unset($post['comments'][$val]);
}
return $post;
});
debug 增加的4条sql是不可避免的
返回的数据
总结
一般来说查询sql的时间,要多于程序解析的时间,所以把需要用到的数据,查询出来,再去由程序去解析成相应的数据类型,而不要用sql去实现。
【2019/8/8 更新】
上面的总结过于粗暴~ 数据量大的话,要用到其他优化方式,比如加缓存。具体业务,在具体分析,这里抛砖引玉
本作品采用《CC 协议》,转载必须注明作者和本文链接
本帖由系统于 6年前 自动加精
高认可度评论:
可以使用
$post['comments'] = array_column($post['comments'], null, 'id')
代替,PHP原生库函数是使用C实现的,效率想必会更高可以使用
$post['comments'] = array_column($post['comments'], null, 'id')
代替,PHP原生库函数是使用C实现的,效率想必会更高@xinhuo 已优化,感谢
性能调优范畴不只是语句,还要综合考虑数据库和主机性能。有的时候数据库承载运算也许会更好。
要么空间换时间,要么时间换时间。
帖子写得挺好的,学习了,谢谢!
这个点赞信息为什么要查出来?不是应该给每条评论加一个是否点赞的标识吗? $nestedKeys[]=$key;这句每次都执行,没必要写else了,当然不影响也。
@jiacongcong 自身理解,写这篇的目的是要把所有数据,都查询出来,然后再根据具体的需要修改成自己想要的部分。判断是否点赞,自己认为有两个方法。
一是用空间换取时间,在评论字段中,增加一个冗余字段,记录该评论的所有点赞id
然后
二用时间换取空间。
@jcc123 第二种好些吧,第一种加字段感觉不是很好。嘻嘻
我是查详情用了这种方法,就一个文章,这种评论分页岂不是,每次得获取所有评论,自己再计算。
foreach 嵌套 if的代码 实在是。。。。让我想起了 tp3.2
@yourself 大神有好的方法可以分享一下。
@jiacongcong 不是什么大神,好方法就是最小化分解,判断和处理拆分,在调用位置引用处理。单一职责,一个方法只处理一件事,易扩展,可插拔形式。
laravel萌新,请问这个debug工具叫啥,能看到实际执行的sql数目
@windpuller clockwork 你google一下吧
看到这么多foreach 就头痛 不是好代码。。。
如果是我写的话,这里应该不会将 $posts 转换成数组后又转成集合。:)
@superSnail 不转的话,可能会使用到
Model
里的方法大佬牛逼