union查询后不能直接paginate(),因tp5底层不识别子查询别名,需用buildsql()转为带别名的子查询再套table()才能分页。

union 查询后不能直接 paginate() 是因为底层不识别子查询别名
ThinkPHP5 的 paginate() 方法默认只支持单表或简单关联查询,遇到 union() 构造的复合 SQL 时,会尝试对原始表执行 COUNT(*),但此时数据库并不知道那个 UNION 结果集的总行数——它不是一张物理表,也没有元信息可查。直接调用 Db::table('...')->union([...])->paginate(10) 会报错或返回空结果。
必须用 buildSql() 转成子查询再套一层 table()
核心解法是把 union 结果“固化”为一个临时逻辑表,让 TP 把它当普通表处理。关键步骤只有两步:
- 每个分支先用
buildSql()生成带括号的子查询字符串(如(SELECT ...)) - 把所有子查询用
union()拼接,再用buildSql()得到完整 union 字符串 - 把这个字符串传给
Db::table($sql . ' a'),注意末尾加别名a(否则字段解析失败) - 之后就可以放心调用
field()、where()、paginate()了
示例片段:
$a = Db::table('user')->field('id,name,created_at')->where('status', 1)->buildSql();
$b = Db::table('admin')->field('id,name,created_at')->where('level', '>', 0)->buildSql();
$unionSql = Db::table($a)->union([$b])->buildSql(); // 注意:这里 $a 已是子查询,union 后仍需 buildSql()
$list = Db::table($unionSql . ' a')->field('id,name,created_at')->paginate(10);
多数据库 union 需手动指定连接,且字段必须严格一致
如果 union 的表来自不同数据库(比如 db1.user 和 db2.admin),TP 默认无法跨库 union。这时不能依赖模型自动连接,必须显式指定:
- 用
Db::connect('db1')和Db::connect('db2')分别构建子查询 - 每个子查询的
field()必须字段数、类型、顺序完全一致;建议显式写全字段,不要用* - 字符串拼接时注意引号和括号层级,TP 不会帮你校验 SQL 合法性,错误只在执行时报数据库错
- 分页时 count 性能差:TP 会对整个 union 结果执行一次
SELECT COUNT(*) FROM ($unionSql) a,大数据量下可能慢,没缓存机制
query 参数丢失?必须手动透传搜索条件
union 后的分页链接默认不携带原请求参数(如关键词、状态筛选),因为 paginate() 只读取当前 query 构造器里的 where,不感知外层 HTTP 请求。解决方法只有一个:
- 在调用
paginate()时显式传入['query' => request()->param()] - 或者更稳妥地,只传需要的键:
['query' => ['keyword' => input('keyword'), 'type' => input('type')]] - 避免用
request()->param()直接透传,防止敏感参数(如 token)被带到分页 URL 上
漏掉这一步,点第二页就丢搜索条件,用户会以为功能坏了。
union 分页真正麻烦的从来不是语法,而是字段对齐、连接隔离、count 性能和参数透传这四点。每少处理一个,线上就多一个难排查的“分页不生效”工单。
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











