Красноречивый: Как соединить напрямую связанную таблицу и косвенно связанную таблицу?

Чтобы продемонстрировать свою проблему, я создал фиктивную базу данных с этими 4 таблицами (определенными как модели Eloquent): User, Product, ShoppingItem (сокращенно: Item ), Порядок; со всеми их соответствующими отношениями. (Пользователь включен сюда только для полноты)
Продукты:

  • id
  • имя
  • hasMany (элемент)

Заказы:

  • id
  • ID пользователя
  • Дата
  • hasMany (элемент)

Предметы:

  • id
  • идантификационный номер продукта
  • номер заказа
  • ownTo (Продукт)
  • ownTo (Заказ)

Таким образом, записи (Покупки) Items создаются всякий раз, когда покупатель покупает некоторые продукты. Заказ (корзина) также создается и содержит 1 или несколько Товаров, принадлежащих только одному покупателю (Пользователю) и его текущим покупкам. процесс.

Теперь эта проблема касается списка продуктов и способа анализа их «успешности». Как часто они продаются и когда они продавались в последний раз?

Я могу, например, использовать следующий запрос, чтобы получить все заказы для одного продукта и, следовательно, подсчитать, как часто они продаются:

$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 или набором Query Builder, чтобы я мог использовать разбиение на страницы в теме.

У меня есть мутатор, определенный в моей модели продукта, например:

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 }}

Однако я не могу использовать «last_time_sold» в выражении Eloquent OrderBy!

Есть ли другой способ получить значение «last_time_sold» продукта в отношениях с базой данных, как описано выше, без потери возможности выполнять разбиение на страницы?

В конечном итоге, как правильно запрашивать таблицу "Товары" и заказывать товары по дате их последнего заказа?

Имейте в виду, что у предметов нет даты, только у заказа (корзины покупок) есть дата.


person matthiku    schedule 30.09.2016    source источник
comment
Если у элементов нет даты, как это $query ->where('date', '<', Carbon::now()); должно работать?   -  person Sanzeeb Aryal    schedule 04.10.2016
comment
Хороший вопрос! Но по какой-то причине это работает ... и «дата» на самом деле является полем таблицы «Заказы», ​​а «product_id» - полем таблицы Items. Таким образом, в этом подзапросе он, по-видимому, позволяет мне смешивать имена полей из обеих таблиц - при условии, что они имеют разные имена, я думаю. Я ни в коем случае не эксперт по Eloquent ...   -  person matthiku    schedule 04.10.2016
comment
Я могу получить список всех товаров с подробными сведениями о продукте и фактической датой покупок (заказа), упорядоченных по этой дате следующим образом: $items = Item::with('product','order')->orderBy('product_id','date') но это дает список всех товаров, а не всех продукты (что намного меньше).   -  person matthiku    schedule 21.10.2016
comment
Есть ли в этих таблицах какие-либо другие столбцы (в частности, таблица элементов)?   -  person Rwd    schedule 23.10.2016
comment
Конечно, есть еще много всего. Это лишь минимум, чтобы воспроизвести / продемонстрировать проблему.   -  person matthiku    schedule 24.10.2016


Ответы (2)


Используйте ЛЕВЫЕ СОЕДИНЕНИЯ с таблицами items и orders, группируйте по 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), вам нужно будет включить все столбцы из таблицы products в предложение groupBy().

Примечание 3: для продуктов, которые никогда не заказывались, в столбце last_ordered будет ПУСТО (NULL). В MySQL NULL упорядочивается первым при использовании ASC и последним при использовании DESC. Но AFAIK это не задокументировано. Поэтому, если вы не хотите полагаться на такое поведение, используйте:

->orderbyRaw('max(orders.date) is null')
->orderBy('last_ordered', 'desc')
person Paul Spiegel    schedule 23.10.2016

В зависимости от того, как часто вы будете выполнять этот запрос, может быть лучше создать столбец last_ordered для отметки времени продуктов и касаться его каждый раз, когда есть заказ.

person pubudeux    schedule 23.10.2016
comment
Вы абсолютно правы, но на самом деле это не очень частый запрос. - person matthiku; 24.10.2016