Laravel много ко многим моделям, связанным с загрузкой, с графом


Я пытаюсь связать 4 таблицы, а также добавить пользовательское поле, вычисленное путем подсчета идентификаторов некоторых связанных таблиц с помощью laravel. У меня есть это в SQL, который делает то, что я хочу, но я думаю, что это может быть сделано более эффективным:

DB::select('SELECT 
                        posts.*,
                          users.id AS users_id, users.email,users.username,
                          GROUP_CONCAT(tags.tag ORDER BY posts_tags.id) AS tags,
                          COUNT(DISTINCT comments.id) AS NumComments, 
                          COUNT(DISTINCT vote.id) AS NumVotes
                        FROM 
                          posts    
                          LEFT JOIN comments ON comments.posts_id = posts.id
                          LEFT JOIN users ON users.id = posts.author_id
                          LEFT JOIN vote  ON vote.posts_id = posts.id
                          LEFT JOIN posts_tags  ON posts_tags.posts_id = posts.id
                          LEFT JOIN tags  ON tags.id = posts_tags.tags_id

                        GROUP BY 
                          posts.id, 
                          posts.post_title');

Я попытался реализовать его с помощью eloquent, сделав это:

$trending=Posts::with(array('comments' => function($query)
                {
                    $query->select(DB::raw('COUNT(DISTINCT comments.id) AS NumComments'));

                },'user','vote','tags'))->get();

Однако значение NumComments не отображается в результатах запроса. Есть идеи, как еще это сделать?

1 4

1 ответ:

Вы не можете сделать это с помощью with, потому что он выполняет отдельный запрос.

То, что вам нужно, просто join. Просто переведите запрос, который у вас есть, на что-то вроде:

Posts::join('comments as c', 'posts.id', '=', 'c.id')
    ->selectRaw('posts.*, count(distinct c.id) as numComments')
    ->groupBy('posts.id', 'posts.post_title')
    ->with('user', 'vote', 'tags')
    ->get();

Тогда каждая запись в коллекции будет иметь атрибут count:

$post->numComments;

Однако вы можете сделать это проще с отношениями, как показано ниже:

Хотя первое решение лучше с точки зрения производительности (может быть не заметно, если у вас нет больших данных)

// helper relation
public function commentsCount()
{
    return $this->hasOne('Comment')->selectRaw('posts_id, count(*) as aggregate')->groupBy('posts_id');
}

// accessor for convenience
public function getCommentsCountAttribute()
{
    // if relation not loaded already, let's load it now
    if ( ! array_key_exists('commentsCount', $this->relations)) $this->load('commentsCount');

    return $this->getRelation('commentsCount')->aggregate;
}

Это позволит вам сделать это:

$posts = Posts::with('commentsCount', 'tags', ....)->get();
// then each post:
$post->commentsCount;

И для многих ко многим отношениям:

public function tagsCount()
{
    return $this->belongsToMany('Tag')->selectRaw('count(tags.id) as aggregate')->groupBy('pivot_posts_id');
}

public function getTagsCountAttribute()
{
    if ( ! array_key_exists('tagsCount', $this->relations)) $this->load('tagsCount');

    $related = $this->getRelation('tagsCount')->first();

    return ($related) ? $related->aggregate : 0;
}

Другие примеры, подобные этому, можно найти здесь http://softonsofa.com/tweaking-eloquent-relations-how-to-get-hasmany-relation-count-efficiently/