在 Laravel 中获取每个用户的最新消息(行)
Posted
技术标签:
【中文标题】在 Laravel 中获取每个用户的最新消息(行)【英文标题】:Get latest message (row) per user in Laravel 【发布时间】:2016-11-02 09:55:39 【问题描述】:TL;DR:需要每个发件人的最新消息。
在我的 Laravel 应用程序中,我有两个表:
用户:
身份证 姓名消息:
身份证 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
那么 ID 为 1 (recipient_id) 的用户应该只能看到 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 技能还不够,经过几天的尝试,我没有解决方案。
谢谢。
【问题讨论】:
我认为这个解决方案完全没问题。这个解决方案到底有什么让您烦恼的地方? 只是我认为在 Laravel 中有一种更正确、更干净的方法。 我会更关心你的函数需要多少查询以及这些查询需要多长时间。 简单的 \Auth::user()->messages()->orderBy('created_at', 'DESC')->first() 不起作用? 您正在考虑的解决方案不起作用,我认为如果 id 1 的用户回复 id 2 或 id 3,将显示的最后一条消息将是从 id 2 或id 3,那么最后它不会显示对话的最后一条消息 【参考方案1】:从this post 中汲取灵感,最有效的方法如下:
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 最友好,但它是基于性能的最佳解决方案,正如上面这个答案中链接的帖子所强调的那样
【讨论】:
与问题作者的回答相比,这个解决方案有什么好处? 在帖子的第一次查询时,OP的解决方案完全相同(执行时间:1分17.89秒)。而我提到的查询(帖子的第二个查询)要快得多(0.28 秒)。这是超过 275 倍的性能提升!【参考方案2】:为什么不直接访问messages
,像这样 -
// get the authenticated user
$user = \Auth::user();
// find the messages for that user
return User::with('message')->find($user->id)->messages;
【讨论】:
因为它会返回用户收到的所有消息。但我只需要每个发件人的最新邮件。 好的,你之前的问题不是很清楚。无论如何,如果您愿意,另一种解决方案可能是在您的messages
表中保留一个“is_read”标志,默认值为N
。稍后,阅读该消息后,您可以将其更新为Y
。这样你就只能过滤is_read = 'N'
记录,避免max()
查询
其实我有这个标志(问题简化)。但是,它仍然没有用。如果用户从其他几个用户那里收到了几条新消息,他们都会有这个标志(未读)。所以基本上这是同样的问题,只是结果中有另一列。
您的回答仍会返回所有消息。您正在做的是“获取第一个用户的所有消息”,这与“获取每个用户的最新消息”不同【参考方案3】:
这可能是一个解决方案(虽然未经测试)
User::with([
'messages' => function ($q)
$q->select('sender_id', 'body')->groupBy('sender_id')->orderBy('created_at', 'desc');
])->find(1);
【讨论】:
这是最好的解决方案。【参考方案4】:我尝试了一些类似的方法,发现你只需要 orderBy created_at
立即找到User
的所有消息,然后你可以使用 Laravel 的Collection
将它们按sender_id
分组。因此,据我了解,以下方法应该可行,即给您用户从发件人s收到的最后一条消息:
所以假设你有一个 Authenticated User
as $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:请注意,我不能说这可能有多有效,但可以肯定的是,数据库上的查询数量有限。
希望对你有帮助:)
更新:
我会在尝试时将其分解。 ~
它将属于用户的messages
的所有记录提取到一个集合中(已按 created_at 排序),然后使用laravel's groupBy(),您将获得与该文档中给出的示例类似的结果。
这次我没有转换成数组。相反,它是集合的集合。像 collection(collection(Messages))
然后您在每个索引处选择第一个模型。我已经传递给get()
方法的参数确保只有那些字段存在(即->get(['sender_id', 'body', 'created_at'])
。这与mysql groupBy() 完全不同,因为它不会为每个组返回一行,而是简单地将所有记录分组通过给定的标识符。
另一个更新
我后来发现,对生成的有序消息调用 unique()
方法实际上可以完成这项工作,但在这种情况下,唯一标识符将是 sender_id
。 (参考:Laravel's unique)即:
$messages = $user->messages()
->orderBy('created_at', 'desc')
->get(['sender_id', 'body', 'created_at'])
->unique('sender_id'); //unique replaces `groupBy`
结果是我们不需要foreach
和groupBy
我们在这里得到结果。
如果在多个地方需要,避免重复(上述)的另一种方法是使用Laravel's query scope,即在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:仍然不确定哪一个比另一个更好。
【讨论】:
laravel的收集方法groupBy如何定义返回哪些值?我的意思是在“body”、“created_at”中返回哪个字段?我们也可以使用与mysql方法GROUP BY类似的方法,但不建议在严格的mysql中使用。 laravel 的做法有问题吗? 我会在我的回答中给出一个更新的答案。 我明白你的意思。您从数据库中获取所有原始数据并在后端过滤它们。我想,当数据增长时,这种方法的效果会降低。无论如何感谢您的回答+为清楚起见。 @Sabine 我还有另一个更新答案(您可以查看)。在玩这个问题时发现了这一点。【参考方案5】:我喜欢here提到的更简单的方法。
在您的 User
模型中,除了现有的 messages()
关系之外,添加此关系
public function latestMessage()
return $this->hasOne(Message::class, 'recipient_id')->latest();
那么当你查询时,只需像这样查询。
$messages = User::with('latestMessage')->get();
$messages
包含每个用户的最新消息。
编辑
为了按日期时间/ID 对结果进行排序,您可以这样做。
$messages = User::with(['latestMessage' => function($message)
$message->orderBy('id', 'desc');
])->get();
$messages
包含由id
订购的每个用户的最新消息。 Refer this answer
【讨论】:
AFAIU,它不会根据最后一条消息的数据时间/id 对集合中的用户进行排序,我错了吗? @Sabine 感谢您指出这一点。我用如何订购结果更新了答案。请看一看。【参考方案6】:我在另一个论坛上找到了这个解决方案,我想这就是你要找的。我发布它是为了对其他用户有用
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
【讨论】:
【参考方案7】:这是我发现的最“雄辩的方式”:
在用户模型中:
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
【讨论】:
这仍然是一个子查询,而不是一个连接。像我强调的那样表现缓慢 @Paras 好吧,那篇文章说“关于性能,一种或另一种解决方案可能会更好,具体取决于数据的性质。所以你应该测试这两个查询并使用一个更好的给定数据库的性能。”此外,此解决方案不进行连接,而是使用EXISTS
运算符,该运算符非常快,因为它不加载结果,并且一旦找到匹配项就会“返回 true”。话虽如此,我不得不说我没有做基准测试,OP 无论如何都应该做自己的测试,以找到适合他们情况的最佳方法。
同意@Alejandro。但是,在大多数情况下,由于 O(n2) 是空比较,连接具有更好的性能。您的帖子使用了存在运算符,但仍然是subquery
。通过添加DB::listen
来记录查询来检查它【参考方案8】:
也许你可以试试这个:
$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;
这可能不是很有效,因为它需要两个查询来完成这项工作,但您将通过代码可读性获得好处。
我希望它能按应有的方式工作。虽然我还没有测试过,但我通常是这样做的……干杯!
【讨论】:
【参考方案9】:你可以试试这个
Messages::where('recipient_id',**USER_ID**)
->group_by('sender_id')
->order_by('id','desc')
->get();
【讨论】:
这不起作用,因为它首先按发件人分组,为每个发件人提供具有最低 id 的消息(尽管我不相信这种行为是一致的),然后它对该结果进行排序。这就是存在更复杂解决方案的原因:***.com/questions/1313120/…以上是关于在 Laravel 中获取每个用户的最新消息(行)的主要内容,如果未能解决你的问题,请参考以下文章