Yii2 Active Record多对多嵌套关联查询及正确数据格式获取
如何通过一次Yii2 Active Record查询获取关联产品的属性及对应属性项?
问题背景
我有4张关联表:products、properties、property_items、property_product(中间表),关联逻辑如下:
- 一个
Product对应多个Properties - 一个
Property对应多个PropertyItems - 一个
Product对应多个PropertyItems(通过中间表property_product关联)
表数据
products表
Id name 1 Arrabiata 2 Bolognese
properties表
Id name 1 Pasta type 2 Size
property_items表
Id name property_id 1 Spinach spaghetti 1 2 Linguine 1 3 Spaghetti 1 4 Small 2 5 Medium 2 6 Large 2
property_product表(中间表)
product_id property_id property_item_id 1 1 1 1 1 2 1 2 4 1 2 6 2 1 2 2 1 3 2 2 5
现有模型关联
我用Gii生成了对应模型及基础关联:
class Products extends \yii\db\ActiveRecord { // 其他业务代码... public function getProperties() { return $this->hasMany(Properties::className(), ['id' => 'property_id']) ->viaTable('property_product', ['product_id' => 'id']); } } class Properties extends \yii\db\ActiveRecord { // 其他业务代码... public function getPropertyItems() { return $this->hasMany(PropertyItems::className(), ['property_id' => 'id']); } }
遇到的问题
我希望通过一次Active Record查询拿到指定格式的JSON响应,但尝试两种方式都踩了坑:
- 嵌套循环查询:性能极差,每一层循环都要发起新的数据库请求
Menus::find()->with(['products' => function ($query) { $query->with(['properties']); }]) ->where(['>', 'status', 0]) ->andWhere(['resid' => $id])->asArray()->all(); foreach ($menus as &$menu) { foreach ($menu['products'] as &$product) { foreach ($product['properties'] as &$property) { $propertyItems = PropertyItems::find()->where(['property_id' => $property['id']] ) ->leftJoin('property_product', 'property_items.id = property_product.property_item_id') ->where(['property_product.product_id' => $product['id'], 'property_product.property_id' => $property['id']]) ->asArray()->all(); $property['propertyItems'] = $propertyItems; } } } - 多层with关联查询:返回的
property_items是该属性下的所有选项,没有关联到当前Product,不符合需求Menus::find()->with(['products' => function ($query) { $query->with(['properties' => function ($query) { $query->with(['propertyItems']); } ]); } ])->asArray()->all();
期望的JSON格式
[ { "id": "1", "name": "Arrabiata", "properties": [ { "id": "1", "name": "Pasta type", "property_items" : [ { "id": "1", "name": "Spinach spaghetti" }, { "id": "2", "name": "Linguine" } ] }, { "id": "2", "name": "Size", "property_items" : [ { "id": "4", "name": "Small" }, { "id": "6", "name": "Large" } ] } ] }, { "id": "2", "name": "Bolognese", "properties": [ { "id": "1", "name": "Pasta type", "property_items" : [ { "id": "2", "name": "Linguine" }, { "id": "3", "name": "Spaghetti" } ] }, { "id": "2", "name": "Size", "property_items" : [ { "id": "5", "name": "Medium" } ] } ] } ]
请问如何通过一次Active Record查询实现该需求?
解决方案
核心思路是给Properties模型新增一个带上下文条件的关联方法,专门用来关联「当前属性下属于指定Product的PropertyItems」,然后通过嵌套with加载这个关联,利用Yii2的关联上下文自动绑定对应的Product ID,既保证性能又能拿到符合要求的数据结构。
步骤1:修改Properties模型,新增关联方法
在Properties类中添加getProductPropertyItems方法,这个方法会通过中间表过滤出属于当前关联Product的属性项:
class Properties extends \yii\db\ActiveRecord { // 原有代码... public function getPropertyItems() { return $this->hasMany(PropertyItems::className(), ['property_id' => 'id']); } // 新增:关联当前属性下属于对应Product的PropertyItems public function getProductPropertyItems() { // 通过中间表关联,同时利用Yii的关联上下文获取当前绑定的Product ID return $this->hasMany(PropertyItems::className(), ['id' => 'property_item_id']) ->viaTable('property_product', ['property_id' => 'id'], function ($query) { // 从关联上下文拿到当前的Product模型ID $product = $this->getRelatedRecords()['products'][0] ?? null; if ($product) { $query->andWhere(['product_id' => $product['id']]); } }); } }
步骤2:调整查询语句,加载新关联
修改查询代码,在加载properties时指定加载我们新增的productPropertyItems,最后调整键名匹配期望的JSON格式:
$result = Menus::find() ->with(['products' => function ($query) { $query->with(['properties' => function ($query) { // 加载带Product过滤条件的属性项关联 $query->with(['productPropertyItems']); }]); }]) ->where(['>', 'status', 0]) ->andWhere(['resid' => $id]) ->asArray() ->all(); // 把关联名重命名为期望的property_items foreach ($result as &$menu) { foreach ($menu['products'] as &$product) { foreach ($product['properties'] as &$property) { $property['property_items'] = $property['productPropertyItems']; unset($property['productPropertyItems']); } } } // 输出符合要求的JSON echo json_encode($result, JSON_PRETTY_PRINT);
原理说明
Yii2的Active Record关联机制会自动维护嵌套关联的上下文关系,当我们在Properties的关联方法中调用getRelatedRecords()时,能拿到当前绑定的Products实例,从而动态过滤出属于该Product的属性项。整个查询只会发起3-4次SQL请求(远少于嵌套循环的N次),性能得到极大提升,同时返回的数据结构完全匹配需求。
内容的提问来源于stack exchange,提问作者user3500294
相关产品推荐
相关产品推荐

