Как запросить Eloquent отношения, отображая только последние записи - PullRequest
1 голос
/ 19 марта 2020

Я пытаюсь создать в Laravel отношение, которое предоставило бы мне только записи latest. У меня есть три объекта: Game, Player и Turn. Мне нужно иметь возможность создать отношения на Game, чтобы вернуть только currentTurns.

Это моя база данных:

Players
+--+--------+
|id|name    |
+--+--------+
|1 |John Doe|
|2 |Jane Doe|
|3 |John Roe|
+--+--------+

Turns
+--+-------+---------+----+-------------------+
|id|game_id|player_id|roll|created_at         |
+--+-------+---------+----+-------------------+
|1 |1      |1        |3   |2020-03-19 08:27:42|
|2 |1      |2        |5   |2020-03-19 08:27:46|
|3 |1      |3        |1   |2020-03-19 08:27:51|
|4 |1      |1        |6   |2020-03-19 08:28:05|
+--+-------+---------+----+-------------------+

Games
+--+--------+
|id|name    |
+--+--------+
|1 |Foobar  |
+--+--------+

Первоначально я написал бы необработанный SQL запрос примерно так:

SELECT * FROM turns WHERE game_id = 1 GROUP BY player_id ORDER BY created_at DESC

Я обнаружил, что это поведение изменилось с MySQL 5.7. Поэтому я нашел подход , который работает без необходимости отключения строгого режима в Laravel.

Мой текущий необработанный запрос выглядит так:

SELECT turns.* 
FROM turns
JOIN (
    SELECT player_id, max(created_at) as created_at FROM turns WHERE game_id = 1 GROUP BY player_id
) as latest_turns 
ON latest_turns.player_id = turns.player_id 
AND latest_turns.created_at = turns.created_at 
AND game_id = 1

Я Не уверен, что это наиболее эффективный способ, но я ищу способы достижения этого с помощью метода отношений Eloquent в Game классе.

<?php

// ...

public function currentTurns(): HasMany
{
    return $this->turns(); // ???
}

Любая помощь приветствуется, спасибо!

1 Ответ

1 голос
/ 20 марта 2020

Метод joinSub() позволяет вам присоединиться к подзапросу, как вы делаете в своем SQL. Предполагая, что у вас настроен метод Game::turns(), вы можете использовать его в методе доступа , который присоединяет к нему подзапрос:

class Game extends Model
{
    public function turns()
    {
        return $this->hasMany(Turn::class)->with('player');
    }

    public function getLatestTurnsAttribute()
    {
        $subquery = Turn::select('player_id')
            ->selectRaw('max(created_at) as created_at')
            ->where('game_id', $this->id)
            ->groupBy('player_id');

        return $this->turns()
            ->joinSub($subquery, 'latest_turns', function ($j) {
                $j->on('latest_turns.player_id', '=', 'turns.player_id')
                    ->on('latest_turns.created_at', '=', 'turns.created_at');
            })
            ->get();
    }
}

Теперь Game::find(1)->latest_turns создаст следующее SQL, очень похоже на ваш оригинал (обратите внимание, что я также отфильтровал подзапрос по идентификатору игры, чтобы уменьшить количество строк.)

select * from `turns`
inner join (
    select `player_id`, max(created_at) as created_at from `turns` where `game_id` = 1 group by `player_id`
) as `latest_turns`
on `latest_turns`.`player_id` = `turns`.`player_id`
and `latest_turns`.`created_at` = `turns`.`created_at`
where `turns`.`game_id` = 1 and `turns`.`game_id` is not null

Живой пример здесь .

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...