Yii2中Union查询结合分页并返回ActiveRecord结果的问题
嘿,太懂你这个痛点了!用Products和Categories两个ActiveRecord模型做Union查询,结果塞到ActiveDataProvider里,不管怎么配置分页参数都完全失效,对吧?我之前也踩过这个坑,咱们一步步来搞定它。
为什么分页会失效?
问题出在ActiveDataProvider的底层逻辑上:它默认是基于单个ActiveRecord模型的查询来处理分页的——包括计算总条数、生成LIMIT/OFFSET语句。但Union查询是两个独立的AR查询合并后的结果,ActiveDataProvider没法直接识别这种复合查询的结构,自然也就没法正确应用分页规则。
另外还有个前提:如果两个查询返回的字段结构(字段名、数量、数据类型)不一致,不仅Union会报错,就算成功了,ActiveRecord也没法正确映射结果,这也会间接导致分页逻辑混乱。
解决方案:把Union查询包装成子查询,用AR模型重新查询
核心思路是:先构建好字段对齐的Union查询,把它当作一个临时子表,再用一个ActiveRecord模型(可以是现有模型,也可以自定义)去查询这个子表,这样ActiveDataProvider就能像处理普通AR查询一样正常分页了。
步骤1:构建字段对齐的Union查询
首先要确保两个查询返回完全一致的字段结构,字段名和类型都要匹配,必要时用别名统一:
// 产品查询:选需要的字段,加source字段区分来源 $productsQuery = Product::find() ->select([ 'id', 'name', 'description', 'source' => "'product'" // 用字符串常量标记这条记录来自Product表 ]); // 分类查询:对齐字段,同样加source标记 $categoriesQuery = Category::find() ->select([ 'id', 'name', 'description', 'source' => "'category'" ]); // 生成Union查询(注意:union()默认是UNION DISTINCT,要去重的话不用改,要保留重复用unionAll()) $unionSubQuery = (new \yii\db\Query()) ->from(['union_temp' => $productsQuery->union($categoriesQuery)]);
步骤2:用AR模型查询子表,生成可分页的AR查询
这里有两种选择:
选项A:用现有AR模型(如果字段匹配)
如果你的Union结果字段和某个现有模型(比如Product)的属性完全匹配,直接用它的find()方法查询子表即可:
// 用Product模型查询Union子表,返回的是Product类型的AR对象 $arQuery = Product::find() ->from(['t' => $unionSubQuery]);
选项B:自定义通用AR模型(如果字段不匹配现有模型)
如果两个表的字段差异较大,或者你需要更清晰的类型定义,可以创建一个专门的AR模型来映射Union结果:
// 在你的models目录下新建UnionResult.php class UnionResult extends \yii\db\ActiveRecord { // 不需要实际数据库表,只是用来映射子查询结果 public static function tableName() { return 'union_result'; } // 定义对应Union查询的属性 public $id; public $name; public $description; public $source; } // 用自定义模型查询子表 $arQuery = UnionResult::find() ->from(['union_result' => $unionSubQuery]);
步骤3:配置ActiveDataProvider并启用分页
现在把处理好的AR查询传给ActiveDataProvider,分页就会正常生效了:
$dataProvider = new \yii\data\ActiveDataProvider([ 'query' => $arQuery, 'pagination' => [ 'pageSize' => 10, // 每页10条 'pageParam' => 'page', // 分页参数名,默认是page 'pageSizeParam' => 'per-page', // 每页条数参数名,默认是per-page ], ]); // 获取分页后的AR结果集 $results = $dataProvider->getModels();
关键注意事项
- 字段必须完全对齐:Union查询要求两个子查询的字段数量、顺序、数据类型完全一致,否则数据库会直接报错,AR也没法正确映射。
- 总条数计算:ActiveDataProvider会自动对Union子查询执行
COUNT(*)来计算总条数,确保子查询没有复杂的GROUP BY或HAVING逻辑,否则COUNT结果可能不准确。 - 去重控制:默认的
union()方法是UNION DISTINCT(自动去重),如果需要保留重复记录,改用unionAll()即可。
这样处理后,你不仅能得到分页生效的结果,还能保持结果是ActiveRecord对象,完全符合你的业务需求。
内容的提问来源于stack exchange,提问作者mrateb

