Laravel Eloquent Join与Inner Join有何区别?

26

我在尝试做一个类似于Feed的MySQL查询,但是不确定是Eloquent的问题还是MySQL的问题。我相信两种方法都是可行的,我只需要一些帮助。

现在我有一个用户,他们进入自己的Feed页面,在这个页面上显示来自他们朋友的信息(朋友的投票、评论、状态更新)。比如说,我有Tom、Tim和Taylor三个朋友,我需要获取他们所有的投票、评论和状态更新。我有一个按Id编号列出所有朋友的列表,并且我为每个事件(投票、评论、状态更新)都有一个表格,其中存储了与用户相关联的Id。那么,我该如何一次性地获取所有这些信息,以便我可以以以下形式在Feed中显示:

Tim 评论了“酷”

Taylor 说:“Woot第一条状态更新!”

Taylor 投票给了“最好的比赛”

编辑@damiani 所以在进行模型更改后,我的代码如下,它确实返回了正确的行:

$friends_votes = $user->friends()->join('votes', 'votes.userId', '=', 'friend.friendId')->orderBy('created_at', 'DESC')->get(['votes.*']);
$friends_comments = $user->friends()->join('comments', 'comments.userId', '=', 'friend.friendId')->orderBy('created_at', 'DESC')->get(['comments.*']);
$friends_status = $user->friends()->join('status', 'status.userId', '=', 'friend.friendId')->orderBy('created_at', 'DESC')->get(['status.*']);

但我希望它们都能同时发生,这是因为MySQL按顺序排序数千个记录比PHP获取3个列表、合并它们然后进行排序要快100倍。有什么想法吗?


你是如何存储这个“所有朋友的ID列表”的?它是在一个“朋友”表中,还是在你的“用户”表中的一个逗号分隔的列中?如果是后者,那么请规范化你的数据库,以便你可以使用模型关系(hasManyThrough())。 - Mark Baker
1
您可以在数据库中创建一个视图,并为其创建一个 Eloquent“model”。然后只需获取您想要的所有事件。否则,您可以使用多态关系:laravel.com/docs/eloquent#polymorphic-relations - Jarek Tkaczyk
请不要将正确答案加入原始问题,否则会变得非常模糊。 - challet
你的最后一条评论总结得很好,直接使用 SQL 比笨重的 Eloquent 和 ORM 查询快得多,而讽刺的是,它反而比标准 SQL 更复杂。 - Manachi
2个回答

47

我相信还有其他方法可以实现这个目标,但一种解决方案是通过查询构建器使用 join

如果您的表格设置类似于以下内容:

users
    id
    ...

friends
    id
    user_id
    friend_id
    ...

votes, comments and status_updates (3 tables)
    id
    user_id
    ....

在你的User模型中:

class User extends Eloquent {
    public function friends()
    {
        return $this->hasMany('Friend');
    }
}
在你的朋友模型中:
class Friend extends Eloquent {
    public function user()
    {
        return $this->belongsTo('User');
    }
}

然后,要收集所有与用户ID为1的朋友相关的投票,您可以运行此查询:

$user = User::find(1);
$friends_votes = $user->friends()
    ->with('user') // bring along details of the friend
    ->join('votes', 'votes.user_id', '=', 'friends.friend_id')
    ->get(['votes.*']); // exclude extra details from friends table

对于 commentsstatus_updates 表格,运行相同的 join。如果你想要投票、评论和状态更新按照时间顺序排列在一个列表中,你可以将三个结果合并成一个,然后对合并的集合进行排序。


编辑

为了在一个查询中获取投票、评论和状态更新,你可以构建每个查询,然后将结果合并。不幸的是,如果我们使用 Eloquent 的 hasMany 关联关系,则似乎无法使用 union(有关该问题的讨论,请参见此问题的评论),因此我们必须修改查询以改用 where 条件。

$friends_votes = 
    DB::table('friends')->where('friends.user_id','1')
    ->join('votes', 'votes.user_id', '=', 'friends.friend_id');

$friends_comments = 
    DB::table('friends')->where('friends.user_id','1')
    ->join('comments', 'comments.user_id', '=', 'friends.friend_id');

$friends_status_updates = 
    DB::table('status_updates')->where('status_updates.user_id','1')
    ->join('friends', 'status_updates.user_id', '=', 'friends.friend_id');

$friends_events = 
    $friends_votes
    ->union($friends_comments)
    ->union($friends_status_updates)
    ->get();

不过,目前为止,我们的查询已经变得有点复杂了,因此像DefiniteIntegral建议的那样使用多态关系和额外的表可能是一个更好的主意。


也许我做错了什么,但如果我运行这段代码,最终会得到id为1的用户的所有信息,而不是朋友的信息。 - CMOS
经过一些调整,我最终让它工作了。所以你说如果我想获取评论、状态更新等,我应该运行单独的调用,有没有一种方法可以同时获取所有这些信息? - CMOS
1
我编辑了答案,包括使用“union”一次获取所有3个的代码。 - damiani
1
令我困惑的是,Eloquent和ORM通常使查询数据库变得比标准SQL复杂得多,并且效率大大降低。而大多数人则盲目接受了这一点...也许我只是被固定思维所束缚(对于指数级更快/更高效的SQL)。 - Manachi

5

也许不是你想听到的,但“feeds”表将成为这种交易的一个很好的中间人,它可以为你提供一种非规范化的方式,通过多态关系将所有这些数据进行透视。

您可以按照以下方式构建它:

<?php

Schema::create('feeds', function($table) {
    $table->increments('id');
    $table->timestamps();
    $table->unsignedInteger('user_id');
    $table->foreign('user_id')->references('id')->on('users')->onDelete('cascade');
    $table->morphs('target'); 
});

按照以下方式构建Feed模型:

<?php

class Feed extends Eloquent
{
    protected $fillable = ['user_id', 'target_type', 'target_id'];

    public function user()
    {
        return $this->belongsTo('User');
    }

    public function target()
    {
        return $this->morphTo();
    }
}

然后,像这样保持最新状态:

<?php

Vote::created(function(Vote $vote) {
    $target_type = 'Vote';
    $target_id   = $vote->id;
    $user_id     = $vote->user_id;

    Feed::create(compact('target_type', 'target_id', 'user_id'));
});

你可以将上述内容更加通用和健壮,这只是为了演示目的。
此时,你可以轻松地一次性检索所有的源项目。
<?php

Feed::whereIn('user_id', $my_friend_ids)
    ->with('user', 'target')
    ->orderBy('created_at', 'desc')
    ->get();

请注意...从技术上讲,如果你对额外的表有道德上的反对意见,你也可以使用联合查询来实现这个功能。就我个人而言,我认为去规范化是在这种情况下一种强大的技术手段...但使用联合查询版本会更加复杂。 - DefiniteIntegral

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