必须用 DB::table() 手动构造 UNION 查询,因 Eloquent 不支持 union();需字段对齐、显式别名、外层包装才能排序分页,大数据量禁用 collection 分页。

要在 Laravel 中实现嵌套查询与 UNION 合并的组合逻辑,比如先对多个分表做各自聚合再纵向合并结果,或先对关联子集分别统计再统一展示,就必须绕过 Eloquent 的模型层限制,直接在查询构造器层面手动拼接结构一致的子查询,并确保字段对齐、别名显式、外层包裹可排序可分页的壳。
用 DB::table() 手动构造 UNION 查询
UNION 不是 Eloquent 支持的操作,【Eloquent 模型调用 union() 会直接报错】,必须从 DB::table() 出发构建每个子查询。
第一步:写出第一个子查询,明确 select 字段数量、类型和顺序,例如 DB::table('orders_2025_q1')->select('user_id', DB::raw('SUM(amount) as total'), DB::raw('"Q1" as quarter'))。
第二步:写出第二个子查询,字段数必须与第一步完全一致,缺失字段用 NULL 占位,例如 DB::table('orders_2025_q2')->select('user_id', DB::raw('SUM(amount) as total'), DB::raw('"Q2" as quarter'))。
第三步:用 unionAll() 连接两个子查询(去重不必要时优先选它),注意 unionAll() 返回的是新查询实例,需链式继续操作:->unionAll($q2)。
第四步:在外层再套一层 DB::table() 包装整个 UNION 结果,并赋予别名,否则无法加 order by 或 limit:DB::table(DB::raw("({$unionSql}) as u"))。
分表统计后合并并排序
当 users 表按年份拆分为 users_2023、users_2024、users_2025 三张结构相同的表,要统计每张表的注册人数并按年份升序排列,就得靠 UNION ALL + 外层包装。
方法一:手写 SQL 字符串拼接
用 PHP 组装包含三段 SELECT 的完整 UNION ALL 语句,每段都带 SELECT COUNT(*) as cnt, '2023' as year FROM users_2023 这类固定值字段,确保列数、类型、顺序严格一致。
方法二:用循环动态构建子查询
定义年份数组 [2023, 2024, 2025],遍历中用 DB::table("users_{$year}")->selectRaw("COUNT(*) as cnt, ? as year", [$year]) 构建每个子查询,全部 push 到数组,再用 array_reduce() 把它们用 unionAll() 串起来。
最后一步必须加 ->orderBy('year'),因为 UNION 内部不能排序,【排序只能在外层查询执行】。
合并后分页的两种可行方案
Laravel 不支持对 union 结果直接调用 paginate(),这是硬性限制,强行写会抛出 BadMethodCallException。
方案1:用子查询包装后手动分页
把完整的 UNION 查询作为子查询,用 DB::table(DB::raw("(...) as t")) 包裹,再在其上调用 skip($offset)->take($limit),配合 count() 手动算总数。
方案2:改用 unionAll() + values() + collection 分页(仅限数据量小)
先用 get() 拉全量结果,转成 Collection,再调用 forPage($page, $perPage),适合后台导出或低频管理页,不适合列表接口。
这一步操作起来很简单,直接把 union 查询的 get() 结果传给 collect() 就行,但【大数据量下会 OOM】,切勿在用户请求中使用。
集合层 concat 替代 UNION 的适用场景
如果数据已经查出、只是需要合并内存中的多个 Collection(比如查了 users 和 admins 两个模型结果),那就该用 concat(),不是 union()。
collect($users)->concat($admins) 会把两个数组摊平,键名不冲突也不覆盖,纯追加。
注意:concat() 不重置索引,若后续要 json_encode 输出为标准数组,必须链上 ->values(),否则含字符串键会导致 JSON 变对象。
别用 merge()——它对数字键重索引、对字符串键覆盖,完全不符合“拼接”语义。


















