Laravel Eloquent "with ()" нетерпеливая загрузка и "whereHas ()" условия - PullRequest
0 голосов
/ 14 февраля 2019

У меня есть 2 таблицы: ' Автор ' и ' Книга ' со следующими простыми отношениями:

class Author extends Model
{
    public function books() {
        return $this->hasMany('App\Book');
    }
}

class Book extends Model 
{
    public function author() {
        return $this->belongsTo('App\Author');
    }
}

Модель книги имеет атрибут price (float) и category (string).

Я пытаюсь найти эффективный способ сделать эти 3 вещи в одном запросе:

  1. получить списоквсе authors, которые имеют books из category комедии , created_at между датой 1 и датой 2.
  2. у авторов в списке должен быть дополнительный атрибут highest_price, который является самой высокой ценой всех книг автора, которые соответствуют критериям, указанным в шаге 1.
  3. список книг должен быть отсортирован desc по price, а список авторов конечных результатов также должен быть отсортирован desc по этому новому атрибуту highest_price.

Результат должен быть таким:

[
    {
        id: 1,
        name: 'Author ABC'
        highest_price: 15
        books: [
            {
                id: 1,
                name: 'book1',
                category: 'comedy',
                price: 15
            },
            {
                id: 2,
                name: 'book2',
                category: 'comedy',
                price: 10
            }
        ]
    },
    {
        id: 10,
        name: 'Author XYZ'
        highest_price: 12
        books: [
            {
                id: 3,
                name: 'book3',
                category: 'comedy',
                price: 12
            }
        ]
    }
]

То, что я получил до сих пор:

Author::with(['books' => function($query) {
    $query->where('category', 'comedy')
        ->where('created_at', '>=', $someFromDate)
        ->where('created_at', '<=', $someToDate);
}])
->has('books', '>', 0)
->get()
->dd();

Но это только дает мне всех авторов, у которых есть книги, это не учитывает условия в нетерпеливой загрузке ...

Возможно ли это даже в 1 запросе?Буду признателен за любую помощь!

РЕШЕНИЕ

Благодаря ответам я обнаружил, что мне нужно было сочетание with() и whereHas() (нашел ответ здесь: https://stackoverflow.com/a/29594039/5297218). Поскольку условия "где" должны быть в обоих методах, я использовал область запроса в модели Author:

public function scopeWithAndWhereHas($query, $relation, $constraint){
  return $query->whereHas($relation, $constraint)
               ->with([$relation => $constraint]);
}

Для дополнительного атрибута highest_price I 'Мы использовали метод transform() в коллекции Collection в сочетании с методом max() для получения максимальной цены. Это конечный результат:

Author::withAndWhereHas('books', function($query) use ($category, $date){
    $query->where('category', $category)
        ->where('created_at', '>=', $date->get('start'))
        ->where('created_at', '<=', $date->get('end'))
        ->orderBy('price', 'desc');
})
->get()
->transform(function ($author){
    $author['highest_price'] = $author->books->max('price');
    return $author;
})
->sortByDesc('highest_price')
->values();

ps: это решениеВ 42 раза больше того, что я делал раньше!

Ответы [ 3 ]

0 голосов
/ 14 февраля 2019

Существует два разных результата при использовании условий отношения:

1- Ограничение результатов RELATIONSHIP на основе условия (то, что вы делаете, но не то, что вы хотите):

$authors = Author::with(['books' => function($query) {
    $query->whereNotNull('published');
}])->get();

Здесь у вас будут ВСЕ авторы, независимо от того, опубликовали они книги или нет.Те, у кого опубликованы книги, будут иметь информацию о книге.Все остальные не будут.

2- Ограничение результатов PARENT в зависимости от условий отношений (что вам следует использовать):

$authors = Author::whereHas('books', function($query) {
     $query->whereNotNull('published');
})->get();

Здесь у вас будут ТОЛЬКО авторы, опубликовавшие книги.

Примечание. Если вы хотите загружать модель Book, вам все равно придется использовать -> with ('books')

0 голосов
/ 15 февраля 2019

Из набора результатов в вашем вопросе ниже должно быть указано, что вам нужно:

Author::whereNotNull('highest_price')
    ->has('books')
    ->with(['books' => function($query) use($startDate, $endDate){
        return $query->where('category', 'comedy')
            ->whereBetween('created_at', [$startDate, $endDate])
            ->orderBy('price', 'desc')
            ->select('id', 'name', 'category', 'price', 'author_id');
    }])
    ->orderBy('highest_price', 'desc')
    ->select('id', 'name', 'highest_price')
    ->get()

Объяснение:

Простой способ обойти эти вещи - сначала структурировать родительский запрос.строитель, который является автором в этом случае.Итак, в соответствии с вашим требованием мы проверили наличие наименьшей цены, отфильтровали авторов, у которых по крайней мере 1 книга, упорядочены по наивысшей цене и выбранным столбцам, которые нам нужны, из таблицы авторов.

Теперь, изучая готовую загрузку, мы сделалито же самое, отфильтровал диапазон дат созданного_атериала, порядок по цене и выбранные столбцы.Я добавил дополнительные столбцы author_id, которые являются преднамеренным внешним ключом, чтобы они отображали результаты книги.

0 голосов
/ 14 февраля 2019

Этот подзапрос на with() будет ограничивать то, что загружено в отношение автора к книгам, но не будет ограничивать возвращаемые автором.Вы пробовали whereHas() вместо with()?Это вернет только автора, у которого есть книги, которые соответствуют этим критериям.Это возможно с одним запросом, но это будет немного зависеть от того, что вам нужно.Добавление поля highest_price может быть лучше отрегулировано для другого отношения.

Author::with('books', 'highest_price')->whereHas(['books' => function($query) use($someFromDate, $someToDate) {
$query->where('category', 'comedy')
    ->where('created_at', '>=', $someFromDate)
    ->where('created_at', '<=', $someToDate);
}])
->get()->sortByDesc('highest_price.highest_price');

Дайте мне знать, если у вас есть какие-либо вопросы.

...