You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Laravel查询last_messages显示错误,修改后报语法错误如何解决?

修复Laravel Eloquent聊天对话列表last_message获取错误的问题

问题说明

使用Laravel Eloquent查询聊天对话列表时,通过MAX(messages.body)获取的last_message内容不正确(MAX()是按字符串字典序取最大值,并非按时间取最新消息)。尝试修改为messages.body ORDER BY messages.created_at DESC LIMIT 1 last_message时,触发PostgreSQL语法错误:syntax error at or near "last_message"。

原查询代码:

$users = Message::join('users',  function ($join) {
    $join->on('messages.from_id', '=', 'users.id')
        ->orOn('messages.to_id', '=', 'users.id');
    })
    ->where(function ($q) {
        $q->where('messages.from_id', auth()->user()->id)
            ->orWhere('messages.to_id', auth()->user()->id);
    })
    ->where('users.id','!=',auth()->user()->id)
    ->select([
        'users.id',
        'users.name',
        'users.avatar',
        DB::raw('MAX(messages.created_at) max_created_at'),
        DB::raw('MAX(messages.body) last_message'),
        DB::raw('CASE WHEN(COUNT(messages.is_read) FILTER (WHERE is_read = false
            AND messages.from_id != '.auth()->user()->id.') = 0) THEN true ELSE false END is_read'),
        DB::raw('COUNT(messages.is_read) FILTER (WHERE is_read = false
            AND messages.from_id != '.auth()->user()->id.') count_unread')
    ])
    ->orderBy('max_created_at', 'desc')
    ->groupBy('users.id')
    ->paginate($request->per_page ?? 20)
    ->withQueryString();

错误原因

直接在SELECT中写messages.body ORDER BY ... LIMIT 1不符合PostgreSQL语法规范,GROUP BY分组后必须使用聚合函数,或通过DISTINCT ON、窗口函数等方式获取分组内的特定行数据。

解决方案

方法1:使用PostgreSQL专属的DISTINCT ON

该方式简洁高效,直接按用户分组并取每组最新的消息记录:

$users = Message::select([
        'users.id',
        'users.name',
        'users.avatar',
        'messages.created_at as max_created_at',
        'messages.body as last_message',
        DB::raw('CASE WHEN(COUNT(messages.is_read) FILTER (WHERE is_read = false
            AND messages.from_id != '.auth()->user()->id.') = 0) THEN true ELSE false END is_read'),
        DB::raw('COUNT(messages.is_read) FILTER (WHERE is_read = false
            AND messages.from_id != '.auth()->user()->id.') count_unread')
    ])
    ->join('users', function ($join) {
        $join->on('messages.from_id', '=', 'users.id')
            ->orOn('messages.to_id', '=', 'users.id');
    })
    ->where(function ($q) {
        $q->where('messages.from_id', auth()->user()->id)
            ->orWhere('messages.to_id', auth()->user()->id);
    })
    ->where('users.id', '!=', auth()->user()->id)
    ->distinctOn('users.id')
    ->orderBy('users.id')
    ->orderBy('messages.created_at', 'desc')
    ->orderBy('max_created_at', 'desc')
    ->paginate($request->per_page ?? 20)
    ->withQueryString();

注意:distinctOn是Laravel对PostgreSQL的适配特性,必须先按分组字段(users.id)排序,再按时间倒序,确保每组第一条为最新消息。

方法2:使用窗口函数ROW_NUMBER()

适合复杂分组场景,通过窗口函数标记每组内的消息排序,筛选出最新的一条:

// 子查询:给每个对话的消息按时间排序,标记行号
$subquery = Message::select([
        '*',
        DB::raw('ROW_NUMBER() OVER (PARTITION BY CASE WHEN from_id = '.auth()->user()->id.' THEN to_id ELSE from_id END ORDER BY created_at DESC) as rn')
    ])
    ->where(function ($q) {
        $q->where('from_id', auth()->user()->id)
            ->orWhere('to_id', auth()->user()->id);
    });

// 关联用户表,筛选出行号为1的最新消息
$users = DB::table('users')
    ->joinSub($subquery, 'latest_messages', function ($join) {
        $join->on('users.id', '=', DB::raw('CASE WHEN latest_messages.from_id = '.auth()->user()->id.' THEN latest_messages.to_id ELSE latest_messages.from_id END'));
    })
    ->where('users.id', '!=', auth()->user()->id)
    ->select([
        'users.id',
        'users.name',
        'users.avatar',
        'latest_messages.created_at as max_created_at',
        'latest_messages.body as last_message',
        DB::raw('CASE WHEN(COUNT(latest_messages.is_read) FILTER (WHERE is_read = false
            AND latest_messages.from_id != '.auth()->user()->id.') = 0) THEN true ELSE false END is_read'),
        DB::raw('COUNT(latest_messages.is_read) FILTER (WHERE is_read = false
            AND latest_messages.from_id != '.auth()->user()->id.') count_unread')
    ])
    ->where('latest_messages.rn', 1)
    ->groupBy('users.id', 'latest_messages.created_at', 'latest_messages.body')
    ->orderBy('max_created_at', 'desc')
    ->paginate($request->per_page ?? 20)
    ->withQueryString();

方法3:子查询获取最新消息ID(兼容更多数据库)

如果需要兼容非PostgreSQL数据库,可先查询每个用户的最新消息ID,再关联获取内容:

// 子查询:获取每个对话的最新消息ID
$latestMsgIds = Message::select(
        DB::raw('MAX(id) as msg_id'),
        DB::raw('CASE WHEN from_id = '.auth()->user()->id.' THEN to_id ELSE from_id END as user_id')
    )
    ->where(function ($q) {
        $q->where('from_id', auth()->user()->id)
            ->orWhere('to_id', auth()->user()->id);
    })
    ->groupBy('user_id');

// 关联用户表和最新消息表
$users = Message::join('users', function ($join) {
        $join->on('messages.from_id', '=', 'users.id')
            ->orOn('messages.to_id', '=', 'users.id');
    })
    ->joinSub($latestMsgIds, 'latest_ids', function ($join) {
        $join->on('messages.id', '=', 'latest_ids.msg_id');
    })
    ->where(function ($q) {
        $q->where('messages.from_id', auth()->user()->id)
            ->orWhere('messages.to_id', auth()->user()->id);
    })
    ->where('users.id', '!=', auth()->user()->id)
    ->select([
        'users.id',
        'users.name',
        'users.avatar',
        'messages.created_at as max_created_at',
        'messages.body as last_message',
        DB::raw('CASE WHEN(COUNT(messages.is_read) FILTER (WHERE is_read = false
            AND messages.from_id != '.auth()->user()->id.') = 0) THEN true ELSE false END is_read'),
        DB::raw('COUNT(messages.is_read) FILTER (WHERE is_read = false
            AND messages.from_id != '.auth()->user()->id.') count_unread')
    ])
    ->groupBy('users.id', 'messages.created_at', 'messages.body')
    ->orderBy('max_created_at', 'desc')
    ->paginate($request->per_page ?? 20)
    ->withQueryString();

内容的提问来源于stack exchange,提问作者BoboBoy

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.14 18:43:12