Получить все родительские элементы на основе условия where последнего элемента

#laravel #eloquent

#laravel #красноречивый

Вопрос:

У меня есть две таблицы с отношением один ко многим.

Первая таблица status :

 | id | product_id | status | created_at          |
|----|------------|--------|---------------------|
| 1  | 1          | 0      | 2018-01-01 00:00:00 |
| 2  | 1          | 1      | 2018-02-01 00:00:00 |
| 3  | 2          | 1      | 2018-01-01 00:00:00 |
| 4  | 2          | 5      | 2018-02-01 00:00:00 |
 

Отношение

 public function product()
{
    return $this->belongsTo('AppProduct');
}
 

Второе product :

 | id | name      |
|----|-----------|
| 1  | product_1 |
| 2  | product_2 |
 

Отношение:

 public function status()
{
    return $this->hasMany('AppProductStatus');
}
 

Я пытаюсь получить все product s, где они последние status 1 , но я получаю также product s, даже если оно последнее status != 1 , но в истории оно было status 1 .

Комментарии:

1. Какой запрос / красноречивый запрос вы используете? Что вы пробовали до сих пор? Является status ли таблица «статусом истории», потому что, если ваш ответ отрицательный, нет смысла иметь 2 или более статусов для одного и того же product . Пожалуйста, дайте больше информации.

2. да, таблица statue — это таблица состояния истории

Ответ №1:

В неидеальном мире вы можете фильтровать после факта:

Добавьте отношение:

 public function latestStatus()
{
     return $this->hasOne('AppProductStatus')->latest();
}
 

Это предполагает, что существует отношение один к одному, и поэтому будет получен только один статус

Затем вы можете сделать:

 Product::has('latestStatus')->with('latestStatus')->get()->filter(function (Product $product) {
   return $product->latestStatus->status === 1;
});
 

И я говорю, что это не идеально, потому что сначала будут получены все продукты, а затем отфильтрованы по последнему статусу.

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

 $latestStatus = ProductStatus::latest()->take(1);
              
$productsWithLatestStatusOne = Product::joinSub($latestStatus, 'latest_status', function ($join) {
            $join->on('products.id', '=', 'latest_status.product_id');
        })
        ->select('products.*')
        ->where('latest_status.status', 1)
        ->get();

 

но, конечно, при этом используется соединение с подзапросом, которое может быть медленнее.

Комментарии:

1. Это работает нормально, но, как вы говорите, это не идеальное решение для получения продуктов и их фильтрации!! есть идеи, как использовать обратное отношение и начать работу с lastStatus, чтобы получить все продукты?

2. Насколько мне известно, соединение подзапроса с $latestStatus запросом немного неизбежно. Проблема не красноречива, просто, насколько я знаю, именно так вы обычно решаете эту проблему в SQL. Однако я никоим образом не эксперт по SQL, поэтому, возможно, кто-то, кто лучше знает SQL-часть, может меня поправить

Ответ №2:

Попробуйте метод whereHas()

 Product::wherehas('status', function(IlluminateDatabaseEloquentBuilder $query) {
    $query->where('status', 1);
})->get();
 

Комментарии:

1. вы получите продукты, даже если его последний статус равен != 1, но в истории у него был статус 1