Получайте последнее сообщение (строку) для пользователя в Laravel
TL; DR: требуется последнее сообщение от каждого отправителя.
В моем приложении Laravel у меня есть две таблицы:
Пользователи
Сообщения:
- ID
- sender_id
- recipient_id
- Тело
- created_at
И, конечно, модели.
Модель пользователя:
public function messages() {
return $this->hasMany('App\Message', 'recipient_id');
}
Модель сообщений:
public function sender() {
return $this->belongsTo('App\User', 'sender_id');
}
public function recipient() {
return $this->belongsTo('App\User', 'recipient_id');
}
Когда пользователь открывает свой почтовый ящик, он должен увидеть список последних сообщений от любого другого пользователя.
Итак, если есть сообщения:
id sender_id recipient_id body created_at
1, 2, 1, hi, 2016-06-20 12:00:00
2, 2, 1, hi, 2016-06-21 12:00:00
3, 3, 1, hi, 2016-06-20 12:00:00
4, 3, 1, hi, 2016-06-21 12:00:00
Затем пользователь с идентификатором 1 (recipient_id) должен видеть только сообщения с идентификаторами 2 и 4.
Это текущее решение в модели пользователей:
return Message::whereIn('id', function($query) {
$query->selectRaw('max(`id`)')
->from('messages')
->where('recipient_id', '=', $this->id)
->groupBy('sender_id');
})->select('sender_id', 'body', 'created_at')
->orderBy('created_at', 'desc')
->get();
Это работает, но я блуждал, если можно достичь этого пути Laravel. Вероятно, с нетерпением. Мои навыки Laravel просто не достаточно, и после нескольких дней попыток у меня нет решения.
Спасибо.
Ответы
Ответ 1
Вдохновляя этот пост, самый эффективный способ сделать это будет следующим:
DB::table('messages AS m1')
->leftjoin('messages AS m2', function($join) {
$join->on('m1.sender_id', '=', 'm2.sender_id');
$join->on('m1.id', '<', 'm2.id')
})->whereNull('m2.id')
->select('m1.sender_id', 'm1.body', 'm1.created_at')
->orderBy('m1.created_at', 'm1.desc')->get();
Хотя это не самый дружелюбный к Laravel, это лучшее решение, основанное на производительности, как указано в сообщении, указанном в этом ответе выше.
Ответ 2
Почему бы просто не получить доступ к messages
, например:
// get the authenticated user
$user = \Auth::user();
// find the messages for that user
return User::with('message')->find($user->id)->messages;
Ответ 3
Я попробовал подобный подход и обнаружил, что вам нужно только заказатьBy created_at
, сразу вы найдете все сообщения User
, затем вы можете использовать Laravel Collection
, чтобы сгруппировать их по sender_id
. Поэтому, насколько мне известно, следующий подход должен работать, то есть вы получите последнее сообщение, полученное пользователем от отправителя s:
Предположим, что у вас есть Аутентифицированный User
как $user
, который в этом контексте совпадает с receiver
с receiver_id
в таблице messages
, а затем:
$messages = $user->messages()
->orderBy('created_at', 'desc')
->get(['sender_id', 'body', 'created_at'])
->groupBy('sender_id'); //this is collections method
Затем сверните вокруг коллекции и выберите первый:
$result = new \Illuminate\Database\Eloquent\Collection ();
foreach ($messages as $message){
$result->push($message->first()); //the first model in the collection is the latest message
}
Вы должны получить результат, например:
Illuminate\Database\Eloquent\Collection {#875
all: [
App\Message {#872
sender_id: "2",
body: "hi",
created_at: "2016-06-21 12:00:00",
},
App\Message {#873
sender_id: "4",
body: "hi",
created_at: "2016-06-21 12:00:00",
},
]
PS: Заметьте, что я не могу сказать, насколько это эффективно, но одно можно сказать наверняка, это ограниченное количество запросов на db.
Надеюсь, это поможет:)
ОБНОВЛЕНИЕ:
Я сломаю его, когда попробую. ~
Он извлекает все записи messages
, принадлежащие пользователю, в коллекцию (уже упорядоченную по create_at), затем используя laravel groupBy() у вас есть результат, подобный примеру, указанному в этом документе.
![Группа LaravelBy()]()
На этот раз я не конвертировался в Array. Вместо этого это коллекция коллекций. (коллекция (Сообщения))
Затем вы выбираете первую модель для каждого индекса. Параметры, которые я уже передал в метод get()
, обеспечивают только те поля (т.е. ->get(['sender_id', 'body', 'created_at'])
). Это полностью отличается от mysql groupBy(), поскольку он не возвращает одну строку для каждой группы, а просто группирует все записи по данному идентификатору.
ДРУГОЕ ОБНОВЛЕНИЕ
Позже я обнаружил, что вызов метода unique()
в результирующих упорядоченных сообщениях действительно выполнит задание, но в этом случае уникальный идентификатор будет sender_id
. (ref: Laravel unique) То есть:
$messages = $user->messages()
->orderBy('created_at', 'desc')
->get(['sender_id', 'body', 'created_at'])
->unique('sender_id'); //unique replaces `groupBy`
Следствием этого является то, что нам не нужны foreach
и groupBy
. Здесь мы имеем результат.
Другим способом избежать повторения (из вышеперечисленного), если это необходимо в нескольких местах, является использование область запроса Laravel, т.е. в модели Message
мы можем иметь что-то такое:
public function scopeLatestSendersMessages($query)
{
return $query->orderBy('created_at', 'desc')
->get(['sender_id', 'body', 'created_at'])
->unique('sender_id');
}
Затем в контроллере используйте:
$messages = $user->messages()->latestSendersMessages();
PS: Не уверен, какой из них оптимально лучше другого.
Ответ 4
это может быть решение (не проверено)
User::with([
'messages' => function ($q) {
$q->select('sender_id', 'body')->groupBy('sender_id')->orderBy('created_at', 'desc');
}
])->find(1);
Ответ 5
Мне нравится более простой подход, который упоминается здесь.
В вашей модели User
дополнительно к существующей связи messages()
добавьте это отношение
public function latestMessage()
{
return $this->hasOne(Message::class, 'recipient_id')->latest();
}
Затем, когда вы запрашиваете просто такой запрос.
$messages = User::with('latestMessage')->get();
$messages
содержит последнее сообщение для пользователя.
Edit
Чтобы заказать результат с помощью datetime/id, вы можете сделать это следующим образом.
$messages = User::with(['latestMessage' => function($message) {
$message->orderBy('id', 'desc');
}])->get();
$messages
содержит последнее сообщение для пользователя, заказанного id
. Обратитесь к этому вопросу
Ответ 6
Это самый "необычный способ", который я нашел из этого:
В модели пользователя:
public function messages() {
return $this->hasMany(Message::class, 'recipient_id');
}
public function latestMessagePerSender()
{
return $this->messages()
->whereNotExists(function ($query) {
$query->from('messages AS m2')
->whereRaw('m2.sender_id = messages.sender_id')
->whereRaw('m2.created_at > messages.created_at');
});
}
Тогда просто $user->latestMessagePerSender
Ответ 7
Возможно, вы можете попробовать следующее:
$user = \Auth::user();
// Get the latest date
$last_date_created = Message::latest()->first()->created_at; // also check for possible null
// Get the date only - use to specify date range in the where section in the eloquent query
$target_date = date('Y-m-d', strtotime( $last_date_created ) );
// Retrieve the messages
$latest_posts = Message::where('recipient_id', $user->id)
->where('created_at', '>=', $target_date . ' 00:00:00')
->where('created_at', '<=', $target_date . ' 23:59:59')
->groupBy('sender_id')
->groupBy('created_at')
->get();
return $latest_posts;
Это может быть не очень эффективно, поскольку для выполнения этой задачи потребовалось два запроса, но вы получите преимущества благодаря читаемости кода.
Я надеюсь, что это работает так, как должно быть. Я не тестировал его, но как обычно я это делаю... Приветствия!
Ответ 8
Я нашел это решение на другом форуме, думаю, это то, что вы искали. Я публикую его, чтобы он мог быть полезен другим пользователям
DB::select('
SELECT t1.*
FROM messages AS t1
INNER JOIN
(
SELECT
LEAST(sender_id, recipient_id) AS user_id,
GREATEST(sender_id, recipient_id) AS recipient_id,
MAX(id) AS max_id
FROM messages
GROUP BY
LEAST(sender_id, recipient_id),
GREATEST(sender_id, recipient_id)
) AS t2
ON LEAST(t1.sender_id, t1.recipient_id) = t2.sender_id AND
GREATEST(t1.sender_id, t1.recipient_id) = t2.recipient_id AND
t1.id = t2.max_id
WHERE t1.sender_id = ? OR t1.recipient_id = ?
', [auth()->guard('api')->user()->id, auth()->guard('api')->user()->id]);
оригинальное сообщение: https://laracasts.com/discuss/channels/laravel/get-the-latest-message-of-chat-model-with-mysql-just-cannot-get-the-idea-how-to-do-this?page=1#reply=392529
Ответ 9
Вы можете попробовать этот
Messages::where('recipient_id',**{USER_ID}**)
->group_by('sender_id')
->order_by('id','desc')
->get();