在Laravel Eloquent中,如何在子查询中引用主查询

时间:2018-12-24 20:39:44

标签: laravel eloquent laravel-query-builder

我有一个有许多订单的模型用户。订单有很多产品,其中有数据透视表订单产品。我不想在可能的情况下预加载和遍历订单。

我需要返回用户

  1. signed_date ===对用户为true
  2. 订单上的订单日期晚于用户上的签名日期
  3. 订单产品显示尚未付款

我在2号上失败了。 在以下代码中,whereHas中的第一个查询是错误的。我不知道如何从哪里有引用用户的签名日期。如果要遍历集合中的用户,我可以使用($ query)来使用$ user,但是如何在不预载所有用户的情况下做到这一点?

return User::whereNotNull('signed_date')
           ->whereHas('orders', function ($query) {
               $query->where('order_date', '<=', 'user.signed_date');
               $query->whereHas('products', function ($q) {
                   $q->where('paid', false);
               });
           })
           ->get(['id','fname','lname', 'title', 'signed_date']);

如果可能的话,我想表达雄辩。如果这不可能,那么我将很高兴提供使用查询生成器/ sql解决此问题的提示。

1 个答案:

答案 0 :(得分:0)

雄辩的查询构建器具有一个称为whereColumn('a', '<=', 'b')的特殊功能,用于将列而不是列与值进行比较。由于查询生成器生成实际查询的方式,因此有必要使用此函数代替常规的where()。您需要让查询生成器知道要传递列名而不是值,以正确转义和格式化查询字符串。

无论如何,似乎您也可以将以表名为前缀的列名传递给函数,从而使您可以跨表比较列:

$query->whereColumn('orders.order_date', '<=', 'users.signed_date')

之所以可行,是因为您在查询中使用了whereHas()。您的查询基本上被翻译为:

SELECT id, fname, lname, title, signed_date
FROM users
WHERE signed_date NOT NULL
  AND EXISTS (
    SELECT 1
    FROM orders
    WHERE orders.order_date <= users.signed_date
      AND EXISTS (
        SELECT 1
        FROM products
        WHERE paid = 0
      )
  )

实际上,根本不需要将表名和whereColumn()中的列名一起使用。但是,如果您要在另一张表上添加一列相同的列,查询可能会中断-因此恕我直言,在自定义查询中使用表名是个好习惯。


顺便说一句,它不能与with('relationship')一起使用的原因是此函数导致一个附加查询,并且您显然无法比较查询之间的列。想象一下:

Order::with('user')->take(5)->get();

它将被翻译为以下内容:

SELECT *
FROM orders
LIMIT 5

SELECT *
FROM users
WHERE id IN (?, ?, ?, ?, ?)

其中五个?将成为订单的user_id。如果第一个查询返回的多个行具有相同的user_id,则从users表中提取的行数当然会减少。

注意:所有查询仅是示例。

可能是因为查询生成器会根据数据库类型构建不同的查询和/或以不同的方式对它们进行转义(即,反引号中的列名称)。