【问题标题】:Eloquent: How to join a directly related table and a indirectly related table?Eloquent:如何连接一个直接相关的表和一个间接相关的表?
【发布时间】:2017-02-08 20:17:24
【问题描述】:

为了演示我的问题,我用这 4 个表(定义为 Eloquent 模型)创建了一个虚构的数据库:UserProduct、ShoppingItem(简称:项目), 订单;与他们所有的相关关系。 (用户仅出于完整性考虑)
产品:

  • 身份证
  • 姓名
  • hasMany(Item)

订单:

  • 身份证
  • user_id
  • 日期
  • hasMany(Item)

项目:

  • 身份证
  • product_id
  • order_id
  • 属于(产品)
  • belongsTo(Order)

因此,每当客户购买某些产品时,都会创建(购物)Items 记录。 Order(购物车)也被创建,其中包含 1 个或多个 Items 恰好属于一个客户(User)和他当前的购物过程。

现在这个问题是关于产品列表和分析它们“成功”的方法。它们多久售出一次?最后一次售出是什么时候?

例如,我可以使用以下查询来获取单个产品的所有订单,从而计算它们的销售频率:

$orders = Order::whereHas('items', function ($query) use ($id) {
    $query->where('product_id', $id) 
          ->where('date', '<', Carbon::now());
})->orderBy('date', 'desc')->get();

此外,我可以通过以下方式获得所有订购产品的列表,这些产品按销售时间长短排列:

$products = Products::withCount('items')
                    ->orderBy('item_count', 'desc');

但我想获取所有产品的列表,按上次订购(购买)的日期排序。该结果必须是 Eloquent 查询或查询生成器集,以便我可以在上面使用分页。

我在我的 Product 模型中定义了一个 Mutator,如下所示:

public function getLastTimeSoldAttribute( $value ) {

    $id = $this->id;

    // get list of plans using this song
    $order = Order::whereHas('items', function ($query) use ($id) {
        $query->where('product_id', $id) 
              ->where('date', '<', Carbon::now());
    })->orderBy('date', 'desc')->first();

    return $order->date;
}

它返回上次购买此产品的日期,我可以在这样的视图中使用它:

{{ $product->last_time_sold }}

但是,我不能在 Eloquent OrderBy 语句中使用“last_time_sold”!

是否有另一种方法可以在如上所述的数据库关系中获取产品的“last_time_sold”值,而不会失去执行分页的能力?

最终,查询 Products 表并在最后一次订购产品之前订购产品的正确方法是什么?

请注意,商品没有日期,只有订单(购物车)有日期。

【问题讨论】:

  • 如果项目没有日期,$query -&gt;where('date', '&lt;', Carbon::now()); 应该如何工作?
  • 好问题!但由于某种原因,它可以工作......并且“日期”实际上是表订单的一个字段,而“产品ID”是表项目的一个字段。所以在那个子查询中,它显然允许我混合两个表中的字段名称——我猜只要它们有不同的名称。我绝不是 Eloquent 方面的专家……
  • 我可以获得所有购物项目的列表,其中包含产品详细信息和实际购物卡丁车(订单)日期,按此日期订购,如下所示:$items = Item::with('product','order')-&gt;orderBy('product_id','date') 但是生成所有项目的列表,而不是所有产品的列表(要小得多)。
  • 这些表中是否还有其他列(特别是 items 表)?
  • 当然,还有很多。这些只是能够重现/演示问题的最低限度。

标签: mysql laravel eloquent laravel-5.2 laravel-5.3


【解决方案1】:

itemsorders 表使用LEFT JOIN,按products 分组并按MAX(orders.date) 排序。

$products = Product::leftJoin('items', 'items.product_id', '=', 'products.id')
    ->leftJoin('orders', 'orders.id', '=', 'items.order_id')
    ->groupBy('products.id')
    ->selectRaw('products.*, max(orders.date) as last_ordered')
    ->orderBy('last_ordered', 'desc')
    ->get();

这将执行以下查询:

select products.*, max(orders.date) as last_ordered
from `products`
left join `items` on `items`.`product_id` = `products`.`id`
left join `orders` on `orders`.`id` = `items`.`order_id`
group by `products`.`id`
order by `last_ordered` desc

注意 1:如果您只想获得已订购的产品 - 请使用 join() 而不是 leftJoin

注意 2:如果您以严格模式运行 MySQL(laravel 5.3 的默认设置),您需要在 groupBy() 子句中包含来自 products 表的所有列。

注意 3: 对于从未订购过的产品,last_ordered 列将为 NULL。在 MySQL 中,NULL 在使用 ASC 时排在第一位,在使用 DESC 时排在最后。但是AFAIK这没有记录。因此,如果您不想依赖这种行为,请使用:

->orderbyRaw('max(orders.date) is null')
->orderBy('last_ordered', 'desc')

【讨论】:

    【解决方案2】:

    根据您执行此查询的频率,最好为产品的时间戳创建一个“last_ordered”列,并在每次有订单时触摸它。

    【讨论】:

    • 你说的很对,但它实际上并不是一个非常频繁的查询。
    猜你喜欢
    • 2014-11-13
    • 1970-01-01
    • 2012-06-26
    • 1970-01-01
    • 2011-11-27
    • 1970-01-01
    • 2018-10-05
    • 1970-01-01
    • 2021-04-28
    相关资源
    最近更新 更多