Laravel eloquent сортировать по отношениям
У меня есть 3 модели
- Пользователь
- Канал
- Ответить
модельные отношения
-
user
имеют belongsToMany('App\Channel');
-
channel
имеют hasMany('App\Reply', 'channel_id', 'id')->oldest();
скажем, у меня есть 2 канала
- канал-1
- канал-2
channel-2
имеет последние ответы, чем channel-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');
}
ИЗМЕНИТЬ:
![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
по самым новым ответам (при условии, что у вас есть только один ответ на канал.) Это не ваш случай, но кто-то может искать что-то подобное (как и я).