Laravel — запрос для объединения таблиц во многих отношениях

#php #mysql #sql #laravel

Вопрос:

У меня есть следующие таблицы

Публикации
 - идентификатор
- имя

Категории
 - идентификатор
- имя

category_post
 - post_id
 - идентификатор категории

Post.php

 public function Category()
{
    return $this->belongsToMany(Category::class);
}
 

Category.php

 public function posts()
{
    return $this->belongsToMany(Post::class);
}
 

Сообщение может содержать множество категорий.

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

Например, если сообщение относится к трем категориям, я хочу получить все сообщения, относящиеся к этим трем категориям. Я мог бы достичь этого за 4 запроса к БД, как показано ниже.

 $post = Post::find(1);

$categoryIds = $post->category()->pluck('id');

$postIds = DB::table('category_post')
    ->whereIn('category_id', $categoryIds)
    ->pluck('post_id')
    ->unique();

$relatedPosts = DB::table('posts')
    ->whereIn('id', $postIds)
    ->get();
 

Мы будем очень признательны за любую помощь в сокращении запросов к БД или рефакторинге кода способом laravel.
Спасибо

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

1. Просто в качестве записки. Если вы делаете это, потому что считаете, что меньшее количество запросов означает лучшую производительность, то это неверно. Гораздо важнее, что делают запросы, а не сколько их, за заметным исключением стоимости передачи данных, которая будет применяться только в том случае, если база данных находится за пределами локальной сети сервера виртуальной локальной сети.

2. @apokryfos да, я это понимаю. В данном конкретном случае, как вы думаете, не будет ли какой-либо разницы в производительности между моим кодом и вашим, скажем, для 25 категорий и 5000 сообщений?

3. В данном конкретном случае я думаю, что главным преимуществом является экономия памяти на веб-сервере, поскольку вы не загружаете частичные данные для подготовки следующего запроса, в дополнение к тому, что вам не нужно выполнять первый запрос для получения модели, если вы уже знаете идентификатор. Я думаю, что время, необходимое для обоих методов, будет масштабироваться одинаково, поскольку основным трудоемким процессом является «присоединение» к таблице сообщений через категории, что является неизбежной операцией

4. @apokryfos Спасибо за ваши подробные разъяснения

Ответ №1:

Ваш приведенный пример может быть написан так:

 $postWithRelatedPosts = Post::whereHas('category.post', function ($q) {
    $q->where('id', 1);
})->get();
 

Это один запрос, но в нем есть 2 подзапроса. Это порождает что-то вроде:

 select * 
from `posts` 
where exists (
    select * 
    from `categories` inner join `category_post` on `categories`.`id` = `category_post`.`category_id` 
    where `posts`.`id` = `category_post`.`post_id` and exists (
         select * 
         from `posts` inner join `category_post` on `posts`.`id` = `category_post`.`post_id` 
         where `categories`.`id` = `category_post`.`category_id` and `id` = ?
    )

)
 

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

1. Спасибо @apokryfos за ответ. Это сработало именно так, как я хотел.