yii中联合查询必须用query builder而非activerecord,因activequery不支持union()方法;需确保列数、类型、顺序及别名一致,并用子查询包裹order by和limit;推荐unionall()提升性能。

在Yii中用模型做联合查询(UNION)时,不能直接调用ActiveRecord的find()链式方法拼接多个SELECT再union——ActiveRecord不支持原生UNION语法生成,必须退回到Query Builder或createCommand()层面手动构造。
Union必须用Query Builder构造,不能用ActiveRecord
ActiveRecord的find()返回的是ActiveQuery对象,它封装了表映射和自动字段绑定,但内部没有实现UNION、UNION ALL、INTERSECT等集合操作。试图在ActiveQuery上调用→union()会报Fatal error: Call to undefined method yii\db\ActiveQuery::union()。
必须切换到yii\db\Query类,它提供union()、unionAll()、distinct()等集合操作方法,并保持参数绑定与SQL安全。
第一步:实例化Query对象 → 设置第一个SELECT的字段与表 → 调用union()传入第二个Query对象
注意:两个子查询的列数、列类型、列顺序必须严格一致,否则MySQL执行时报错ERROR 1222 (HY000): The used SELECT statements have a different number of columns。
用Query Builder写UNION的两种写法
方法一:分步构建两个Query,再union
$query1 = (new \yii\db\Query())->select(['id', 'name as title', 'created_at as time'])->from('post')->where(['status' => 1]);
$query2 = (new \yii\db\Query())->select(['id', 'title', 'updated_at as time'])->from('article')->where(['published' => 1]);
$unionQuery = $query1->union($query2);
$rows = $unionQuery->all();
方法二:链式写法,更紧凑
$rows = (new \yii\db\Query())
->select(['id', 'name as title', 'created_at as time'])
->from('post')
->where(['status' => 1])
->union((new \yii\db\Query())
->select(['id', 'title', 'updated_at as time'])
->from('article')
->where(['published' => 1]))
->all();
这一步操作起来很简单,直接把两个Query对象串起来就行;但【列别名必须完全一致,比如都叫title、time,不能一个叫name、一个叫title】,否则ORDER BY或后续处理会出错。
带ORDER BY和LIMIT的UNION必须包在子查询里
MySQL要求UNION后的ORDER BY和LIMIT只能作用于整个结果集,不能只对某一个分支生效。所以如果要排序+分页,必须把UNION整体当做一个子查询:
$subQuery = (new \yii\db\Query())
->select(['id', 'title', 'time'])
->from(['t' => (new \yii\db\Query())
->select(['id', 'name as title', 'created_at as time'])
->from('post')
->where(['status' => 1])
->union((new \yii\db\Query())
->select(['id', 'title', 'updated_at as time'])
->from('article')
->where(['published' => 1]))]);
$finalQuery = $subQuery->orderBy(['time' => SORT_DESC])->limit(10);
$rows = $finalQuery->all();
直接在union()后面跟orderBy()会失效——MySQL语法不允许,PHP层也不会报错,但SQL执行时被忽略。
UNION ALL比UNION快,去重逻辑由业务控制
UNION默认去重,底层执行DISTINCT,IO和CPU开销明显高于UNION ALL。如果两个子查询数据天然不重叠(比如post表和article表主键无交集),或你已在PHP层做过去重,就该强制用unionAll():
$query1->unionAll($query2)->all();
这能减少约30%~50%的执行时间,尤其在百万级结果集上差异显著。
不要依赖UNION自动去重来简化逻辑——它可能掩盖ID冲突、时间戳重复等真实问题,反而让前端展示错乱。











