Laravel eloquent сортировать по отношениям

У меня есть 3 модели

  • Пользователь
  • Канал
  • Ответить

модельные отношения

  • user имеют belongsToMany('App\Channel');
  • channel имеют hasMany('App\Reply', 'channel_id', 'id')->oldest();

скажем, у меня есть 2 канала - канал-1 - канал-2

channel-2 имеет последние ответы, чем channel-1

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

  • канал-2
  • канала 1

Я уже пробовал некоторые коды. но ничего не происходит

// User Model
public function channels()
    {
        return $this->belongsToMany('App\Channel', 'channel_user')
                    ->withPivot('is_approved')
                    ->with(['replies'])
                    ->orderBy('replies.created_at'); // error

    }
// also
public function channels()
    {
        return $this->belongsToMany('App\Channel', 'channel_user')
                    ->withPivot('is_approved')
                    ->with(['replies' => function($qry) {
                        $qry->latest();
                    }]);
    }
// but i did not get the expected result

ИЗМЕНИТЬ Кроме того, я пробовал это. да, я получил ожидаемый результат, но он не будет загружать весь канал, если нет ответа.

public function channels()
{
    return $this->belongsToMany('App\Channel')
                ->withPivot('is_approved')
                ->join('replies', 'replies.channel_id', '=', 'channels.id')
                ->groupBy('replies.channel_id')
                ->orderBy('replies.created_at', 'ASC');
}

ИЗМЕНИТЬ:

query result

Ответ 1

По моим сведениям, метод с нетерпением загрузки with запускает второй запрос. Вот почему вы не можете достичь того, чего хотите, с помощью надежной загрузки with.

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

// In User Model
public function channels()
{
    return $this->belongsToMany('App\Channel', 'channel_user')
        ->withPivot('is_approved');
}

public function sortedChannels($orderBy)
{
    return $this->channels()
            ->join('replies', 'replies.channel_id', '=', 'channel.id')
            ->orderBy('replies.created_at', $orderBy)
            ->get();
}

Затем вы можете вызвать $user->sortedChannels('desc'), чтобы получить список каналов, отредактировав атрибут created_at.

Для условия типа каналов (которое может иметь или не иметь ответов) просто используйте метод leftJoin.

public function sortedChannels($orderBy)
    {
        return $this->channels()
                ->leftJoin('replies', 'channel.id', '=', 'replies.channel_id')
                ->orderBy('replies.created_at', $orderBy)
                ->get();
    }

Edit:

Если вы хотите добавить метод groupBy к запросу, вы должны обратить особое внимание на ваше предложение orderBy. Поскольку в Sql природе, предложение Group By выполняется сначала перед предложением Order By. Подробнее об этой проблеме см. этот вопрос в стеке.

Итак, если вы добавите метод groupBy, вы должны использовать метод orderByRaw и должны быть реализованы следующим образом.

return $this->channels()
                ->leftJoin('replies', 'channels.id', '=', 'replies.channel_id')
                ->groupBy(['channels.id'])
                ->orderByRaw('max(replies.created_at) desc')
                ->get();

Ответ 2

Во-первых, вам не нужно указывать имя сводной таблицы, если вы следуете соглашение об именах Laravel, чтобы ваш код выглядел немного уборщик:

public function channels()
{
    return $this->belongsToMany('App\Channel') ...

Во-вторых, вам нужно будет явно вызвать join для достижения результата в одном запросе:

public function channels()
{
    return $this->belongsToMany(Channel::class) // a bit more clean
        ->withPivot('is_approved')
        ->leftJoin('replies', 'replies.channel_id', '=', 'channels.id') // channels.id
        ->groupBy('replies.channel_id')
        ->orderBy('replies.created_at', 'desc');
}

Ответ 3

Внутри вашего класса канала вам нужно создать это отношение hasOne (вы hasMany ответы hasMany, но оно имеет hasOne последний ответ):

public function latestReply()
{
    return $this->hasOne(\App\Reply)->latest();
}

Теперь вы можете получить все каналы, упорядоченные по последнему ответу, например так:

Channel::with('latestReply')->get()->sortByDesc('latestReply.created_at');

Чтобы получить все каналы от пользователя, упорядоченные по последнему ответу, вам понадобится такой метод:

public function getChannelsOrderdByLatestReply()
{
    return $this->channels()->with('latestReply')->get()->sortByDesc('latestReply.created_at');
}

где channels() определяются как:

public function channels()
{
    return $this->belongsToMany('App\Channel');
}

Ответ 4

Если у вас есть отношение hasOne(), вы можете отсортировать все записи, выполнив:

$results = Channel::with('reply')
                   ->join('replies', 'channels.replay_id', '=', 'replies.id')
                   ->orderBy('replies.created_at', 'desc')
                   ->paginate(10);

Это сортирует все записи channels по самым новым ответам (при условии, что у вас есть только один ответ на канал.) Это не ваш случай, но кто-то может искать что-то подобное (как и я).