【问题标题】:Why can't Laravel/Eloquent use JOIN for Eager Loading?为什么 Laravel/Eloquent 不能使用 JOIN 进行 Eager Loading?
【发布时间】:2014-07-18 04:52:42
【问题描述】:
<?php

class Cat extends Eloquent {

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

class User extends Eloquent {

    public function cats() {
        return $this->hasMany('Cat');
    }
}

现在:

$cats = Cat::with('user')->get();

执行 2 个查询:

select * from `cats`
select * from `users` where `users`.`id` in ('1', '2', 'x')

为什么不能这样做:

select * from cats inner join users on cats.user_id = users.id

对于那些说表中有两个 id 列的人,可以通过别名轻松避免:

select 
    c.id as cats__id,
    c.name as cats__name,
    c.user_id as cats__user_id,
    b.id as users__id,
    b.name as users__name
from cats c
inner join users b on b.id = c.user_id

更新

有人指出 Eloquent 不知道模型中表的列,但我想他们可以提供一种在模型中定义它们的方法,这样它就可以使用别名并进行适当的连接而不是额外的查询。

【问题讨论】:

  • @deczo Ahmmm... 因为它是 1 个查询而不是 2 个? Cat 上的 user_id 不可为空,每个 Cat 都属于 User... 那么为什么不 inner join
  • @deczo 同样,如果 ORM 设计人员 想要 解决它,这不是主要问题。对象的定义可以很容易区分“总是有”和“有时有”,或者检索“肯定有”和“可能有”,并适当地选择inner、left或right join。我不知道 Eloquent,所以也许他们只是决定让事情变得简单。
  • @deczo 我不认为我在关注你。 join 子句的存在是有原因的,因此您不必执行额外的查询来匹配来自不同表的行。如果你在谈论不同类型的连接,比如左、右、内......我想你可以根据模型中的关系告诉 Eloquent 使用哪一个。 belongsTo 表示它始终属于,因此外键不能为空。他们可以添加另一个类似canBelongTo 的内容,这将允许外键为空,因此它将使用左连接。不知道,只是说...
  • 事情是 ORM 只是让生活更轻松的另一层。它的主要目的是对开发人员友好,我认为这个尽可能简单。它有缺陷和限制,还有性能问题,代码中也存在不一致,但另一方面,它确实很有说服力,非常适合简单的任务。但是,没有足够灵活的 ORM 来满足所有要求,这就是为什么您(我)不将 ORM 用于更复杂的工作。
  • @emzero,我最近开始检查 laravel,我面临着完全相同的问题,我无法相信开发人员怎么能说额外的查询是可以的,join 可以完成工作和/或@ 987654333@还可以

标签: php join laravel-4 eloquent eager-loading


【解决方案1】:

catsusers 可能都有一个名为 id 的列,从而使您建议的查询不明确。 Laravel 的预加载使用了额外的查询,但避免了这种潜在的事故。

【讨论】:

  • 你是对的。这不是将表名附加到返回列的方法吗?比如cat.idcat.nameuser.id等等?
  • 在查询中处理别名很简单(例如cat.id as cat__id),我希望 ORM 能够从容应对。
  • @IMSoP 没错,我也希望如此。
  • @IMSoP 与某些 ORM 不同,Eloquent 不知道表中有哪些列 - 大多数时候它在做 SELECT *。它不知道要别名的列。
  • @ceejayoz 请参阅我上面关于为每个连接返回一个额外的“分隔符”列的评论(我在调试中经常使用的技巧)。在事先不知道非键列的情况下,当然可以构建一个使用连接的 ORM。这要么是 Eloquent 的设计决定,要么只是他们不想修复的限制。
【解决方案2】:

我的猜测是,这允许预先加载多个一对多的关系。比如说,我们还有一张狗桌:

class User extends Eloquent {

    public function cats() {
        return $this->hasMany('Cat');
    }

    public function dogs() {
        return $this->hasMany('Dog');
    }
}

现在我们想用 User 预先加载它们:

$users = User::with('cats','dogs')->get();

没有可以将这些组合成单个查询的连接。但是,对每个“with”元素进行单独查询确实有效:

select * from `users`
select * from `cats` where `user`.`id` in ('1', '2', 'x')
select * from `dogs` where `user`.`id` in ('1', '2', 'x') 

因此,虽然这种方法在某些简单的情况下可能会产生额外的查询,但它提供了在连接方法将失败的情况下预加载更复杂数据的能力。

这是我对为什么会这样的猜测。

【讨论】:

  • 这适用于 NM 和 1N 关系,但不适用于 11 或 N1。即使你只实现了这 2 - 仍然有很大的改进,特别是对于大型集合。 (另外,1N 也可以通过一个额外的查询来解决,不管集合中有多少记录)
  • belongsTo 关系不适用
  • 但是 eloquent 可以使用 join 来表示 belongsTo 和另一个查询 hasMany
  • There is no join that would work to combine these into a single query. 好了,它的左连接
【解决方案3】:

我认为,当您想使用 LIMIT 和/或 OFFSET 时,连接查询方法有一个致命的缺点。

$users = User::with('cats')->get() - 这将输出以下 2 个查询。

select * from `users`
select * from `cats` where `user`.`id` in ('1', '2', 'x')

它不是一个单一的查询作为

select * from users inner join cats on cats.user_id = users.id

但是可以说,我们需要对这个记录集进行分页。

User::with('cats')->paginate(10) - 这将输出以下 2 个有限制的查询。

select * from `users` limit 10
select * from `cats` where `user`.`id` in ('1', '2', 'x')

加入,会是这样的

select * from users inner join cats on cats.user_id = users.id limit 10

它将获取 10 条记录,但并不意味着 10 个用户,因为每个用户都可以拥有多只猫。

我认为的另一个原因是,关系数据库和 NOSQL 数据库之间的关系可以通过分离查询方法轻松实现

与之前的答案一样,id 是不明确的,您必须在每个语句前面加上不需要的表名。

另一方面,JOIN 比 EXISTS 昂贵,而 EXISTS 更快,因为它不命令 RDBMS 获取任何数据,只需检查相关行是否存在。 EXISTS 用于返回一个布尔值,JOIN 返回一个完整的其他表。

出于可扩展性目的,如果遵循分片架构,则必须删除 JOIN。这是 pinterest 在缩放期间实践的。 http://highscalability.com/blog/2013/4/15/scaling-pinterest-from-0-to-10s-of-billions-of-page-views-a.html

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-10-20
    • 1970-01-01
    • 2018-08-03
    • 2018-01-25
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多