不积跬步无以至千里

记录精彩的程序人生

  • 首页
  • Java
  • Golang
  • PHP
  • Python
  • Nodejs
  • Lua
  • Docker
  • DevOps
  • 文章归档
  • 默认分类
  • 关于
  • 标签

  • 搜索
PostgreSQL hbase 时间同步 nexus 开机启动 nexus, 开机启动 jenkins安装配置 gitlab安装配置 gitlab安装 文件系统 fastdfs gcc切换 gcc升级 mysql8 交换空间 虚拟内存 tcp thrift lua tag test VPN SoftEtherVPN homebrew asm spring tomcat maven jdk ios mac 图案字符 figlet mysql半同步复制 mysql主从同步 一主多从 一主一从 主从同步 反向代理 密码重置 test 虚拟机扩容 swap 虚拟空间 docker ldocker grpc-gateway protobuf 微服务 go grp GRPC 授权登录 OAuth2.0 SOA supervisord supervisor RPC CentOS rabbitmq 环境变量 php-fpm php.ini error php7 lnmp 编译安装 mysql nginx linux java php python redis 字符串操作 mysql5.7 Solo

Yii2中多表关联查询(join、joinwith)

发表于 2021-07-26 | 分类于 默认分类 | 0 | 阅读次数 1786

我们用实例来说明这一部分

表结构

现在有客户表、订单表、图书表、作者表,

  • 客户表Customer   (id  customer_name)
  • 订单表Order          (id  order_name       customer_id   book_id)
  • 图书表                    (id  book_name       author_id)
  • 作者表                    (id  author_name)

模型定义

下面是这4个个模型的定义,只写出其中的关联

Customer 

  1. class Customer extends \yii\db\ActiveRecord
  2. {
  3.     // 这是获取客户的订单,由上面我们知道这个是一对多的关联,一个客户有多个订单
  4.     public function getOrders()
  5.     {
  6.         // 第一个参数为要关联的子表模型类名,
  7.         // 第二个参数指定 通过子表的customer_id,关联主表的id字段
  8.         return $this->hasMany(Order::className(), ['customer_id' => 'id']);
  9.     }
  10. }
复制代码

Order 

  1. class Order extends \yii\db\ActiveRecord
  2. {
  3.     // 获取订单所属用户
  4.     public function getCustomer()
  5.     {
  6.         //同样第一个参数指定关联的子表模型类名
  7.         //
  8.         return $this->hasOne(Customer::className(), ['id' => 'customer_id']);
  9.     }
  10.     // 获取订单中所有图书
  11.     public function getBooks()
  12.     {
  13.         //同样第一个参数指定关联的子表模型类名
  14.         //
  15.         return $this->hasMany(Book::className(), ['id' => 'book_id']);
  16.     }
  17. }
复制代码

Book 

  1. class Book extends \yii\db\ActiveRecord
  2. {
  3.     // 获取图书的作者
  4.     public function getAuthor()
  5.     {
  6.         //同样第一个参数指定关联的子表模型类名
  7.         return $this->hasOne(Author::className(), ['id' => 'author_id']);
  8.     }
  9. }
复制代码

Author 

  1. class Autor extends \yii\db\ActiveRecord
  2. {
  3. }
复制代码

hasMany、hasOne使用

Yii2中的表之间的关联有2种,它们用来指定两个模型之间的关联。

  • 一对多:hasMany
  • 一对一:hasOne

  • 返回结果:这两个方法的返回结果都为yii\db\ActiveQuery对象
  • 第一个参数:所关联的模型的类名称。
  • 第二个参数:是一个数组,其中键为所关联的模型中的属性,值为当前模型中的属性。

关联的使用

现在我们获取一个客户的所有的订单信息

  1. // 获取一个客户信息
  2. $customer = Customer::findOne(1);
  3. $orders = $customer->orders;  // 通过在Customer中定义的关联方法(getOrders())来获取这个客户的所有的订单。
复制代码

上面的两行代码会生成如下sql语句

  1. SELECT * FROM customer WHERE id=1;
  2. SELECT * FROM order WHERE customer_id=1;
复制代码

关联结果缓存

如果客户的订单改变了,我们再重新调用

  1. $orders = $customer->orders;
复制代码

再次得到订单的时候你会发现没有变化。原因是只会在第一次执行$customer->orders的时候才会去数据库里面查询,然后会把结果缓存起来,以后查询的时候都不会再执行sql。

那么如果我想再次执行sql如何做呢?可以执行

  1. unset($customer->orders);
  2. $customer->orders;
复制代码

然后就可以从数据库里面取数据了。

定义多个关联

同样,我们还可以在Customer里面定义多个关联。
如返回总数大于100的订单。

  1. class Customer extends \yii\db\ActiveRecord
  2. {
  3.     public function getBigOrders($threshold = 100)
  4.     {
  5.         return $this->hasMany(Order::className(), ['customer_id' => 'id'])
  6.             ->where('subtotal > :threshold', [':threshold' => $threshold])
  7.             ->orderBy('id');
  8.     }
  9. }
复制代码

关联的两种访问方式

如上面的,如果使用

  1. $customer->bigOrders
复制代码

将会得到大于100的所有的订单。如果要返回大于200的订单可以这样写

  1. $orders = $customer->getBigOrders(200)->all();
复制代码

从上面可以看出访问一个关联的时候有两种方法

  • 如果以函数的方式调用,会返回一个 ActiveQuery 对象($customer->getOrders()->all())
  • 如果以属性的方式调用,会直接返回模型的结果($customer->orders)

with的使用看如下代码,是取一个客户的订单

  1. // 执行sql语句: SELECT * FROM customer WHERE id=1
  2. $customer = Customer::findOne(1);
  3. //执行sql:SELECT * FROM order WHERE customer_id=1
  4. $orders1 = $customer->orders;
  5. //这个不会执行sql,直接使用上面的缓存结果
  6. $orders2 = $customer->orders;
复制代码

如果现在我们要取出100个用户,然后访问每个用户的订单,从上面的了解我们可能会写出如下代码

  1. // 执行sql语句: SELECT * FROM customer LIMIT 100
  2. $customers = Customer::find()->limit(100)->all();
  3. foreach ($customers as $customer) {
  4.     // 执行sql: SELECT * FROM order WHERE customer_id=...
  5.     $orders = $customer->orders;
  6.     // 处理订单。。。
  7. }
复制代码

然而,如果真要这样写的话,会在foreach的每个循环里面都执行一次sql去数据库里面查询数据。因为每个$customer对象都是不一样的。

为了解决上面的问题 可以使用 yii\db\ActiveQuery::with()。
其中width的参数为关系的名称,也就在model里面定义的getOrders,getCustomer中的orders和customer

  1. // 先执行sql: SELECT * FROM customer LIMIT 100;
  2. //               SELECT * FROM orders WHERE customer_id IN (1,2,...)
  3. $customers = Customer::find()->limit(100)
  4.     ->with('orders')->all();
  5. foreach ($customers as $customer) {
  6.     // 在这个循环的时候就不会再执行sql了
  7.     $orders = $customer->orders;
  8.     // ...handle $orders...
  9. }
复制代码

如果使用了select来指定返回的列,一定要确保返回的列里面包含所关联的模型的关联字段,否则将不会返回关联的表的Model

  1. $orders = Order::find()->select(['id', 'amount'])->with('customer')->all();
  2. // $orders[0]->customer 的结果将会是null
  3. // 因为上面的select中没有返回所关联的模型(customer)中的指定的关联字段。
  4. // 如果加上customer_id,$orders[0]->customer就可以返回正确的结果
  5. $orders = Order::find()->select(['id', 'amount', 'customer_id'])->with('customer')->all();
复制代码

给with加过滤条件

查询一个客户大于100的订单

  1. //首先执行sql: SELECT * FROM customer WHERE id=1
  2. $customer = Customer::findOne(1);
  3. // 再执行查询订单的sql语句:SELECT * FROM order WHERE customer_id=1 AND subtotal>100
  4. $orders = $customer->getOrders()->where('subtotal>100')->all();
复制代码

查询100个客户的,每个客户的总合大于100的订单

  1. // 下面的代码会执行sql语句:
  2. // SELECT * FROM customer LIMIT 100
  3. // SELECT * FROM order WHERE customer_id IN (1,2,...) AND subtotal>100
  4. $customers = Customer::find()->limit(100)->with([
  5.     'orders' => function($query) {
  6.         $query->andWhere('subtotal>100');
  7.     },
  8. ])->all();
复制代码

在这里width的参数为数组,键为关联的名称,值为回调函数。

也就是说 对orders这个关联返回的ActiveQuery,再执行一次$query->andWhere('subtotal>100');

使用joinWith进行表关联

我们都知道可以用join on来写多个表之间的关联。先看看yii2中joinWit的声明
joinWith( $with, $eagerLoading = true, $joinType = 'LEFT JOIN' )

  • $with 数据类型为字符串或数组, 如果为字符串,则为模型中定义的关联的名称(可以为子关联)。 如果为数组,键为model中以getXXX格式定义的关联,值为对这个关联的进一步的回调操作。
  • $eagerLoading 是否加载在$with中关联的模型的数据。
  • $joinType 联接类型,可用值为:LEFT JOIN、INNER JOIN,默认值为LEFT JOIN

  1. // 订单表和客户表以Left join的方式关联。
  2. // 查找所有订单,并以客户 ID 和订单 ID 排序
  3. $orders = Order::find()->joinWith('customer')->orderBy('customer.id, order.id')->all();
  4. // 订单表和客户表以Inner join的方式关联
  5. // 查找所有的订单和书
  6. $orders = Order::find()->innerJoinWith('books')->all();
复制代码
  1. // 使用inner join 连接order中的 books关联和customer关联。
  2. // 并对custmer关联再次进行回调过滤:找出24小时内注册客户包含书籍的订单
  3. $orders = Order::find()->innerJoinWith([
  4.     'books',
  5.     'customer' => function ($query) {
  6.         $query->where('customer.created_at > ' . (time() - 24 * 3600));
  7.     }
  8. ])->all();
  9. // 使用left join连接 books关联,books关联再用left join 连接 author关联
  10. $orders = Order::find()->joinWith('books.author')->all();
复制代码

在实现上,Yii 先执行满足JOIN查询条件的SQL语句,把结果填充到主模型中, 然后再为每个关联执行一条查询语句, 并填充相应的关联模型。

  1. // Order和books关联 inner join ,但不获取books关联对应的数据
  2. $orders = Order::find()->innerJoinWith('books', false)->all();
复制代码

On条件

在定义关联的时候还可以指定on条件

  1. class User extends ActiveRecord
  2. {
  3.     public function getBooks()
  4.     {
  5.         return $this->hasMany(Item::className(), ['owner_id' => 'id'])->onCondition(['category_id' => 1]);
  6.     }
  7. }
复制代码

在joinWith中使用

  1. //先查询主模型(User)的数据, SELECT user.* FROM user LEFT JOIN item ON item.owner_id=user.id AND category_id=1
  2. // 然后再根据关联条件查询相关模型数据SELECT * FROM item WHERE owner_id IN (...) AND category_id=1
  3. // 这两个在查询的过程中都使用了 on条件。
  4. $users = User::find()->joinWith('books')->all();
复制代码

如果没有使用join操作,即使用的是with 或者 直接以属性来访问关联。这个时候on条件会作为where 条件。

  1. // SELECT * FROM user WHERE id=10
  2. $user = User::findOne(10);
复制代码

总结

  • 首先需要在模型中定义好关联(如getOrders中的Orders为一个关联)
  • 然后在with或者joinWith中使用在模型中定义的关联。
  • 其中在使用关联的时候还可以指定回调方法。
  • 再有就是可以给关联、with、joinWith指定where或者on条件

这一部分其实非常多,也有点乱,有些功能没说说完,如三个表关联、逆关联等。
最基本的操作也就大体是这些。如果还有哪个地方想了解的,可以回帖交流。

nginx基本配置与参数说明
CentOS下的FTP设置
  • 文章目录
  • 站点概览
ken

ken

记录精彩的程序人生

498 日志
9 分类
77 标签
RSS
Creative Commons
Links
  • 酷壳
0%
© 2010 — 2025 ken
由 Halo 强力驱动
鄂ICP备18013899号-1