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

Laravel中Union与cursorPaginate联用报错及方案尝试遇阻

解决Laravel中带分页的已读/未读帖子排序展示问题

可行解决方案

方案一:修正Union分页逻辑(子查询包裹)

针对Laravel对Union查询的游标分页别名处理bug,将Union结果作为子查询,外层统一处理分页:

public function getPosts()
{
    $viewedPostsIds = ViewedPost::where('user_id', Auth::id())->pluck('post_id')->toArray();
    
    // 未读帖子查询:统一字段+标记未读状态
    $postsNotViewed = DB::table('posts as p')
        ->select([
            'p.id', 'p.user_id', 'p.content', // 明确列出需要的posts字段
            'pi.image', 
            'u.username', 
            'pro.image as profileimage',
            DB::raw('0 as viewed')
        ])
        ->whereNotIn('p.id', $viewedPostsIds)
        ->join('post_images as pi', 'p.id', '=', 'pi.post_id')
        ->join('users as u', 'p.user_id', '=', 'u.id')
        ->join('profiles as pro', 'p.user_id', '=', 'pro.user_id')
        ->whereRaw('pi.id = (select min(id) from post_images where post_id = p.id)'); // 取单帖首图
    
    // 已读帖子查询:同未读字段结构+标记已读状态
    $postsViewed = DB::table('posts as p')
        ->select([
            'p.id', 'p.user_id', 'p.content',
            'pi.image', 
            'u.username', 
            'pro.image as profileimage',
            DB::raw('1 as viewed')
        ])
        ->whereIn('p.id', $viewedPostsIds)
        ->join('post_images as pi', 'p.id', '=', 'pi.post_id')
        ->join('users as u', 'p.user_id', '=', 'u.id')
        ->join('profiles as pro', 'p.user_id', '=', 'pro.user_id')
        ->whereRaw('pi.id = (select min(id) from post_images where post_id = p.id)');
    
    // 合并查询并包裹为子查询,外层做分页
    $combinedPosts = DB::table(
        $postsNotViewed->union($postsViewed)->orderBy('viewed', 'asc')->orderBy('id', 'desc'),
        'combined'
    )->select('combined.*');
    
    $cursorData = $combinedPosts->cursorPaginate(2);
    
    return [
        'data' => $cursorData->items(),
        'next_page' => $cursorData->nextPageUrl(),
        'has_more_pages' => $cursorData->hasMorePages(),
    ];
}

方案二:使用Exists标记状态(修复参数绑定)

用Exists替代Union,通过mergeBindings解决游标分页的参数不匹配问题:

public function getPosts()
{
    $userId = Auth::id();
    // 构建已读状态子查询
    $viewedSubquery = ViewedPost::whereColumn('post_id', 'posts.id')->where('user_id', $userId);
    
    $posts = DB::table('posts')
        ->select([
            'posts.*',
            'post_images.image',
            'users.username',
            'profiles.image as profileimage',
            DB::raw('EXISTS(' . $viewedSubquery->toSql() . ') as viewed')
        ])
        ->join('post_images', function ($join) {
            $join->on('posts.id', '=', 'post_images.post_id')
                 ->whereRaw('post_images.id = (select min(id) from post_images where post_id = posts.id)');
        })
        ->join('users', 'posts.user_id', '=', 'users.id')
        ->join('profiles', 'posts.user_id', '=', 'profiles.user_id')
        ->orderBy('viewed', 'asc') // 未读优先
        ->orderByDesc('posts.id')
        ->mergeBindings($viewedSubquery); // 绑定子查询参数
    
    $cursorData = $posts->cursorPaginate(2);
    
    return [
        'data' => $cursorData->items(),
        'next_page' => $cursorData->nextPageUrl(),
        'has_more_pages' => $cursorData->hasMorePages(),
    ];
}

问题背景与错误分析

1. Union查询的别名错误

Laravel游标分页会将分页条件(如id < ?)错误应用到第二个Union子查询,引用第一个子查询的别名(nv.id),导致字段不存在错误:

SQLSTATE[42S22]: Column not found: 1054 Unknown column 'nv.id' in 'where clause'

对应原始代码:

public function getPosts()
{
    $viewedPostsIds = ViewedPost::select('post_id')->where('user_id', Auth::id())->get();
    $postsNotViewed = DB::table('posts as nv')
        ->select(['nv.*', 'pi.image', 'unv.username', 'p.image as profileimage'])
        ->whereNotIn('nv.id', $viewedPostsIds)
        ->orderByDesc('nv.id')
        ->join('post_images as pi', 'nv.id', '=', 'pi.post_id')
        ->join('users as unv', 'nv.user_id', '=', 'unv.id')
        ->join('profiles as p', 'nv.user_id', '=', 'p.user_id');

    $postsViewed = DB::table('posts as v')
        ->select(['v.*', 'piv.image', 'uv.username', 'pr.image as profileimage'])
        ->whereIn('v.id', $viewedPostsIds)
        ->orderByDesc('v.id')
        ->join('post_images as piv', 'v.id', '=', 'piv.post_id')
        ->join('users as uv', 'v.user_id', '=', 'uv.id')
        ->join('profiles as pr', 'v.user_id', '=', 'pr.user_id');

    $cursorData = $postsNotViewed->union($postsViewed)->cursorPaginate(2);

    return [
        'data' => $cursorData->groupBy('post_id')->flatten(1),
        'next_page' => $cursorData->nextPageUrl(),
        'has_more_page' => $cursorData->hasMorePages(),
    ];
}

2. 统一别名后的分页失效

将两个子查询别名统一为nv后,首次查询正常,但第二页返回0条结果——因为分页条件无法正确区分两个子查询的结果集。

3. orderByRaw的SQL风险与错误

手动拼接orderByRaw("p.id in (...)")存在SQL注入风险,且MySQL中IN返回布尔值,游标分页无法正确处理该排序后的游标逻辑。

4. Exists方案的参数绑定问题

原始Exists方案未合并子查询的绑定参数,导致Laravel生成的SQL中参数数量不匹配,触发HY093错误。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.22 19:07:33