Yii2-多表关联的用法示例
本篇博客是基于《活动记录(Active Record)》中对于AR表关联用法的介绍。
我会构造一个业务场景,主要是测试我比较存疑的各种表关联写法,而非再次介绍基础用法。
构造场景
订单ar_order
| order_id | 订单id(主键) |
| user_id | 用户id |
用户ar_user
| user_id | 用户id(主键) |
| user_name | 用户名 |
订单商品清单ar_order_goods
| id | 自增id(主键) |
| order_id | 所属订单id |
| goods_id | 所买商品id |
商品ar_goods
| goods_id | 商品id(主键) |
| goods_name | 商品名称 |
商品库存ar_stock
| stock_id | 库存id(主键) |
| goods_id | 商品id(唯一键) |
| stock_count | 库存量 |
表关系如下图所示:

我们接下来的测试,均以"订单"为主体,通过AR的ORM关联来查询出依赖的数据。
环境准备
除了建表,还需要用gii生成所有的AR类,另外日志至少需要开启db相关的category才能在日志里看见执行的SQL是什么。
'log' => [
'traceLevel' => YII_DEBUG ? 3 : 0,
'targets' => [
[
'class' => 'yii\log\FileTarget',
'levels' => ['info', 'error', 'warning', 'trace'],
'categories' => ['yii\db\*'],
],
],
],
简单关联
订单与用户 1:1
数据:
ar_order: 
ar_user: 
给ArOrder添加关联:
public function getUser() {
return $this->hasOne(ArUser::className(), ['user_id' => 'user_id']);
}
测试lazyload:
public function actionHasOne()
{
// 查订单
$orders = ArOrder::find()->all();
foreach ($orders as $order) {
// 查订单关联的用户
$user = $order->user;
// 打印用户名
echo $user->user_name . PHP_EOL;
}
}
lazyload sql:
SELECT * FROM ar_order SELECT * FROM `ar_user` WHERE `user_id`=1 SELECT * FROM `ar_user` WHERE `user_id`=2
测试eagerload:
public function actionHasOne()
{
// 查订单
$orders = ArOrder::find()->with('user')->all();
foreach ($orders as $order) {
// 查订单关联的用户
$user = $order->user;
// 打印用户名,输出:owen
echo $user->user_name . PHP_EOL;
}
}
eagerload sql:
SELECT * FROM `ar_order`
SELECT * FROM `ar_user` WHERE `user_id` IN (1, 2)
订单与商品清单 1:n
数据:
ar_order_goods:
给ArOrder添加关联:
public function getOrderGoods() {
return $this->hasMany(ArOrderGoods::className(), ['order_id' => 'order_id']);
}
lazyload测试:
public function actionHasMany()
{
// 查订单
$orders = ArOrder::find()->all();
foreach ($orders as $order) {
// 查订单关联的商品清单
$orderGoodsArr = $order->orderGoods;
// 打印每个商品ID
foreach ($orderGoodsArr as $orderGoods) {
echo $orderGoods->goods_id . PHP_EOL;
}
}
}
lazyload sql:
SELECT * FROM `ar_order`
SELECT * FROM `ar_order_goods` WHERE `order_id`=1
SELECT * FROM `ar_order_goods` WHERE `order_id`=2
eagerload测试:
public function actionHasMany()
{
// 查订单
$orders = ArOrder::find()->with('orderGoods')->all();
foreach ($orders as $order) {
// 查订单关联的商品清单
$orderGoodsArr = $order->orderGoods;
// 打印每个商品ID,输出:1,2
foreach ($orderGoodsArr as $orderGoods) {
echo $orderGoods->goods_id . PHP_EOL;
}
}
}
eagerload sql:
SELECT * FROM `ar_order`
SELECT * FROM `ar_order_goods` WHERE `order_id` IN (1, 2)
跨中间表关联
订单 与 商品表 跨 商品清单表 1:n关联
数据:
ar_goods:
给ArOrder添加关联:
public function getOrderGoods() {
return $this->hasMany(ArOrderGoods::className(), ['order_id' => 'order_id']);
}
public function getGoods() {
return $this->hasMany(ArGoods::className(), ['goods_id' => 'goods_id'])->
via('orderGoods');
}
注:getGoods中的第一个goods_id是指getOrderGoods关联的ArOrderGoods中的goods_id,第二个goods_id是指ArGoods中的goods_id。
lazyLoad测试:
public function actionVia()
{
// 查订单
$orders = ArOrder::find()->all();
foreach ($orders as $order) {
// 查订单关联的商品(跨中间表orderGoods)
$goodsArr = $order->goods; // 中间表$order->orderGoods的数据在此也被拉回来
echo count($order->orderGoods) . PHP_EOL; // 打印每个商品的名称
foreach ($goodsArr as $goods) {
echo $goods->goods_name . ' ' . PHP_EOL;
}
}
}
lazyload sql:
SELECT * FROM `ar_order`
SELECT * FROM `ar_order_goods` WHERE `order_id`=1
SELECT * FROM `ar_goods` WHERE `goods_id` IN (1, 2) SELECT * FROM `ar_order_goods` WHERE `order_id`=2
SELECT * FROM `ar_goods` WHERE `goods_id` IN (1, 2)
eagerload测试:
public function actionVia()
{
// 查订单
$orders = ArOrder::find()->with("goods")->all();
foreach ($orders as $order) {
// 查订单关联的商品(跨中间表orderGoods)
$goodsArr = $order->goods; // 中间表$order->orderGoods的数据在此也被拉回来
echo count($order->orderGoods) . PHP_EOL; // 打印每个商品的名称
foreach ($goodsArr as $goods) {
echo $goods->goods_name . ' ' . PHP_EOL;
}
}
}
eagerload sql:
SELECT * FROM `ar_order`
SELECT * FROM `ar_order_goods` WHERE `order_id` IN (1, 2)
SELECT * FROM `ar_goods` WHERE `goods_id` IN (1, 2)
发现with仅指定goods关联,则中间关联orderGoods的查询也被eager处理了。
简单关联之级联
和跨中间表关联实现的功能一致,但是不通过via实现,而是通过定义若干级联的1:1或1:n关联来加载数据。
上述中间表关联中,ArOrder是主体,orderGoods和goods都被注入在ArOrder对象身上,这样的优点是eagerload可以优化整个查询流程,减少db交互,同时冗余表达的goods对象少(只需要2个goods对象,由2个order共享,下面代码可以测试):
$orders[0]->goods[0] === $orders[1]->goods[0]
另一种表达这种关系的方式是:arOrder->orderGoods->goods这种间接访问的方式,这样仅需要维护arOrder和orderGoods间的1:n关系以及orderGoods和Goods间的1:1关系既可,优点是访问方式更能体现表关联的间接性,但是缺点就是eagerload无法完整优化整个流程,同时goods对象冗余多。
订单 ,商品表 ,商品清单表 级联
ArOrderGoods添加关联:
public function getGoods() {
return $this->hasOne(ArGoods::className(), ['goods_id' => 'goods_id']);
}
lazyload测试:
public function actionNoVia()
{ $orders = ArOrder::find()->all();
foreach ($orders as $order) {
$orderGoodsArr = $order->orderGoods;
foreach ($orderGoodsArr as $orderGoods) {
$goods = $orderGoods->goods;
echo $goods->goods_name . PHP_EOL;
}
}
}
lazyload sql:
SELECT * FROM `ar_order` SELECT * FROM `ar_order_goods` WHERE `order_id`=1
SELECT * FROM `ar_goods` WHERE `goods_id`=1
SELECT * FROM `ar_goods` WHERE `goods_id`=2 SELECT * FROM `ar_order_goods` WHERE `order_id`=2
SELECT * FROM `ar_goods` WHERE `goods_id`=1
SELECT * FROM `ar_goods` WHERE `goods_id`=2
eagerload测试:
public function actionNoVia()
{
// 第一级关系eagerload
$orders = ArOrder::find()->with('orderGoods')->all();
foreach ($orders as $order) {
// 第二级关系eagerload
$orderGoodsArr = $order->getOrderGoods()->with('goods')->all();
foreach ($orderGoodsArr as $orderGoods) {
$goods = $orderGoods->goods;
echo $goods->goods_name . PHP_EOL;
}
}
}
eagerload sql:
SELECT * FROM `ar_order`
SELECT * FROM `ar_order_goods` WHERE `order_id` IN (1, 2) SELECT * FROM `ar_order_goods` WHERE `order_id`=1
SELECT * FROM `ar_goods` WHERE `goods_id` IN (1, 2) SELECT * FROM `ar_order_goods` WHERE `order_id`=2
SELECT * FROM `ar_goods` WHERE `goods_id` IN (1, 2)
可见,级联方式的交互总是比中间表方式要多,内存占用也要多,虽然经过eagerload优化可以减少几次交互。
joinWith 多表关联
Yii2支持数据库的join语法,不过在编程的时候不是a表join b表这样的表达方式,而是a表通过哪个关联进行join,这个关联就是我们之前定义的hasOne和hasMany,它们是不需要变动的。
不过Yii2的JOIN并不是你想的那样:"一句SQL查回所有的关联数据,填充到关联关系里",这是非常特殊的地方,文档里这样提到:
joinWith() 和 with() 的差别在于前者是联合查询,即通过把查询条件应用于主表和关联表来获取主表记录,而后者是关联查询,即只是针对主表查询条件获取主表记录。
因为这个差别,你可以应用JOIN SQL语句特有的查询条件。比如你可以通过限定关联表的条件来过滤主表记录,如上述例子所示。你还可以通过关联表列值来对主表记录进行排序。
说白了,joinWith虽然是使用数据库的join语法实现的多表联查,但是它不会一次性的将依赖表的数据保存起来,与with相比,仅仅是额外提供了一个根据依赖表的数据过滤主表数据的机会,依赖表的数据依旧会通过再次交互的方式进行查询,是不是既失望又好奇呢?
订单,商品清单,商品 JOIN
测试:
public function actionJoin() {
$orders = ArOrder::find()->innerJoinWith([
'user' => function($query) {
$query->onCondition([
'!=', 'user_name', 'john'
]);
},
'goods' => function ($query) {
$query->onCondition([
'and',
[
'!=', 'goods_name', '雪碧'
],
]);
}
])->all();
foreach ($orders as $order) {
$goodsArr = $order->goods;
foreach ($goodsArr as $goods) {
echo $goods->goods_name . PHP_EOL;
}
}
}
sql:
SELECT `ar_order`.* FROM `ar_order` INNER JOIN `ar_user` ON (`ar_order`.`user_id` = `ar_user`.`user_id`) AND (`user_name` != 'john') INNER JOIN `ar_order_goods` ON `ar_order`.`order_id` = `ar_order_goods`.`order_id` INNER JOIN `ar_goods` ON (`ar_order_goods`.`goods_id` = `ar_goods`.`goods_id`) AND ((`goods_name` != '雪碧')) SELECT * FROM `ar_user` WHERE (`user_id`=1) AND (`user_name` != 'john') SELECT * FROM `ar_order_goods` WHERE `order_id`=1 SELECT * FROM `ar_goods` WHERE (`goods_id` IN (1, 2)) AND ((`goods_name` != '雪碧'))
分析:
你会发现,joinWith的确不是我们所想的一次SQL交互拉回所有依赖数据,而是用于缩小主体数据的规模,这也是为什么后续拉取依赖的时候,需要将依赖表的过滤条件再次套用的原因。
通过最后的例子,我们可以明显的感受出:ORM背后的行为并不一定是我们预期的那样!
所以,当我们使用ORM进行表关联的时候,需要认真考虑一下是不是裸写SQL的方式性能更佳,但是也别忘记ORM给我们带来的抽象性和编程效率。
Yii2-多表关联的用法示例的更多相关文章
- Mongo数据两表关联创建视图示例
表tblCard: {"cNo":"11","oRDate":ISODate("2017-08-01T00:00:00.000+0 ...
- yii2 ActiveRecord多表关联以及多表关联搜索的实现
作者:白狼 出处:http://www.manks.top/yii2_many_ar_relation_search.html 本文版权归作者,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明 ...
- Yii2中多表关联查询(join、joinwith)
我们用实例来说明这一部分 表结构 现在有客户表.订单表.图书表.作者表, 客户表Customer (id customer_name) 订单表Order (id order_name ...
- [moka同学笔记]Yii2中多表关联查询(join、joinwith) (摘录)
表结构 现在有客户表.订单表.图书表.作者表, 客户表Customer (id customer_name) 订单表Order (id order_name cu ...
- Yii2.0中文开发向导——Yii2中多表关联查询(join、joinwith)(转)
我们用实例来说明这一部分 表结构 现在有客户表.订单表.图书表.作者表, 客户表Customer (id customer_name) 订单表Order (id order_ ...
- Yii2.0中文开发向导——Yii2中多表关联查询(join、joinwith)
我们用实例来说明这一部分 表结构 现在有客户表.订单表.图书表.作者表, 客户表Customer (id customer_name) 订单表Order (id order_ ...
- yii2表关联实例
yii2表关联 1.两张表关联,以“商品表关联品牌表”为例 控制器中: $goods_model=new Goods(); $goods_info=$goods_model::find()->j ...
- Yii2中多表关联查询(hasOne、hasMany、join、joinwith)
表结构 现在有客户表.订单表.图书表.作者表, 客户表Customer (id customer_name) 订单表Order (id order_name customer_id ...
- Yii2中多表关联查询(with、join、joinwith)
表结构 现在有客户表.订单表.图书表.作者表, 客户表Customer (id customer_name) 订单表Order (id order_name custome ...
随机推荐
- 【刷题记录】 && 【算法杂谈】折半枚举与upper_bound 和 lower_bound
[什么是upper_bound 和 lower_bound] 简单来说lower_bound就是你给他一个非递减数列[first,last)和x,它给你返回非递减序列[first, last)中的第一 ...
- 技能收获与C语言学习
你有什么技能比大多人(超过90%以上)更好? 我会的东西很多,喜欢的东西太多,但是很遗憾广而不专,会而不精.学了很多东西我都是为了娱乐,因为以前我们那里过于强调学习,很多爱好也都被扼杀在摇篮里.我觉得 ...
- 4.3 多线程进阶篇<中>(GCD)
更正:队列名称的作用的图中,箭头标注的有些问题,已修正 本文并非最终版本,如有更新或更正会第一时间置顶,联系方式详见文末 如果觉得本文内容过长,请前往本人 “简书” 本文源码 Demo 详见 Gith ...
- 统计学习方法 --- 感知机模型原理及c++实现
参考博客 Liam Q博客 和李航的<统计学习方法> 感知机学习旨在求出将训练数据集进行线性划分的分类超平面,为此,导入了基于误分类的损失函数,然后利用梯度下降法对损失函数进行极小化,从而 ...
- 基于shell脚本比较数字加减乘除
让用户输入两个数来比较他们的大小 先用touch命令新建一个2.sh文件 在用vi进入i进入编辑状态 输入 保存后检查
- 用VB实现SmartQQ机器人
这里为了便于介绍程序设计的流程,更多以代码形式给出,具体可用火狐浏览器的firebug插件来抓包分析,或者用谷歌浏览器的开发者工具进行抓包.抓包地址是:http://w.qq.com 第一步,是二维码 ...
- C#委托与事件的简单使用
前言:上一篇博文从原理和定义的角度介绍了C#的委托和事件.本文通过一个简单的小故事,来说明C#委托与事件的使用方法及其方便之处. 在阅读本文之前,需要你对委托和事件的基本概念有所了解.如果你是初次接触 ...
- CentOS下Hadoop-2.2.0集群安装配置
对于一个刚开始学习Spark的人来说,当然首先需要把环境搭建好,再跑几个例子,目前比较流行的部署是Spark On Yarn,作为新手,我觉得有必要走一遍Hadoop的集群安装配置,而不仅仅停留在本地 ...
- homework-01
我的GitHub账户名是Firedamp. 其实我最一开始看到最大子序列的和这个题目,最先想到的就是最简单的O(n^3)的算法,在课堂上教的也确实是这个程序,但是这种算法的时间复杂度必然是最高的,在数 ...
- opencv算法学习
1.改变图像的亮度和对比度: 算法介绍:对每一点像素值的r,g,b,值进行乘法和加法的运算. 代码使用: ; y < image.rows; y++ ) { ; x < image.col ...