thinkphp 5.1的with和传统的join场景测试

http://events.jianshu.io/p/999cb3cb1223

1. 传统业务查询方式

传统业务查询方式在涉及连表操作的时候一般用join连表,大约有一下缺点。

有的时候连表过多,一个列表页的数据有时可能要连很多个表。

比如一个公司列表页,公司的信息在基本表里面, 但是公司的行业领域、融资阶段、所在城市等信息都是通过公司表里面的id和其他表关联的,如果用join查询的话,一定是公司表left join其他的表。连接的表过多,有时造成查询效率低。

连接过多的表导致代码不容易维护,每次要增加显示新的信息,如果信息是在其他表里面的,就需要再次left join,不同表里面的字段有不少都是相同的,查询的时候就需要手动重命名字段解决,重名很麻烦。

一旦某一天有个信息不需要的时候,既要改查询的field字段,又要改join的条件,很容易不小心改错代码,产生新的bug。

2.TP的With查询

利用TP的with查询可以高效简洁的应对需求,下面通过代码实现一个列表查询搜索类,尽量覆盖所有传统方式下吗用join实现的情况,用一个对订单表并且关联其他表的信息查询作为案例。涉及表就是商品表,优惠券表,订单表。
2.1 订单表对应的模型
class Order extends BaseModel
{

protected $name = ‘shop_order’;

/**
* 订单对应的商品数据,通过shop_id和 goods表的id关联
* @return \think\model\relation\HasOne
*/
public function goodsData()
{
return $this->hasOne(Goods::class,”id”,”shop_id”);
}
/**
* 订单所使用的优惠券的信息,通过 coupon_id和Coupon表关联
* @return \think\model\relation\HasOne
*/
public function couponData()
{
return $this->hasOne(Coupon::class,”id”,”coupon_id”);
}
/**
* 子订单的信息,其他的订单id为本条记录的parent_id的订单为它的订单信息
* @return \think\model\relation\HasMany
*/
public function childData()
{
return $this->hasMany(self::class,’parent_id’,”id”);
}
}

先根据需求在模型类定义上述的关联关系,具体是一对一,还是一对多需要根据业务判断。
2.2 各种查询测试

所有用到的with查询,在查询的field字段必须包含关联查询用到的字段,否则关联的对象不会被查询出来。即使外面with传递进去了匿名函数,query增加了where条件,但是原来的条件依旧存在,是在定义关联条件的基础上新增,而不是覆盖。

查询的字段在order表,查询的时候要关联其他信息

这类查询是最基本的查询,给with传递一个数组,数组里面每个元素都是Order模型定义的关联对象,当条件为一对一的时候 在里面用hasOne,查询出来的是一个对象。一对多的时候用hasMany,查询出来的是一个包含相应对象的数组。即使为没有查询到,两者返回的格式在json的表现分别为null,[],保证了返回类型的健壮性。

//查询商品id为36的并且已付款的订单
$where[] = [“shop_id”,”=”,”36″];
$where[] = [‘status’,”=”,2];

$list = (new BashOrder())
->with([‘coupon_data’,’goods_data’,’child_data’])
->where($where)
->order(‘id desc’)
->limit(1)
->select();

返回数据
{
“id”: 323874,
“shop_id”: 36,
“parent_id”: 0,
“coupon_id”: 0,
“price”: “0.00”,
“status”: 2,
“coupon_data”: null,
“goods_data”: {
“id”: 36,
“name”: “创业邦——创业邦会员 — 找人、找钱、找项目!”
},
“child_data”: [
{
“id”: 323875,
“parent_id”: 323874,
“shop_id”: 101
},
{
“id”: 323876,
“parent_id”: 323874,
“shop_id”: 260
},
{
“id”: 323877,
“parent_id”: 323874,
“shop_id”: 344
},
{
“id”: 323878,
“parent_id”: 323874,
“shop_id”: 496
},
{
“id”: 323879,
“parent_id”: 323874,
“shop_id”: 507
},
{
“id”: 323880,
“parent_id”: 323874,
“shop_id”: 518
},
{
“id”: 323881,
“parent_id”: 323874,
“shop_id”: 532
},
{
“id”: 323882,
“parent_id”: 323874,
“shop_id”: 647
},
{
“id”: 323883,
“parent_id”: 323874,
“shop_id”: 1009
},
{
“id”: 323884,
“parent_id”: 323874,
“shop_id”: 1184
},
{
“id”: 323885,
“parent_id”: 323874,
“shop_id”: 1486
},
{
“id”: 323886,
“parent_id”: 323874,
“shop_id”: 2869
},
{
“id”: 323887,
“parent_id”: 323874,
“shop_id”: 43
},
{
“id”: 323888,
“parent_id”: 323874,
“shop_id”: 103
},
{
“id”: 323889,
“parent_id”: 323874,
“shop_id”: 104
},
{
“id”: 323890,
“parent_id”: 323874,
“shop_id”: 154
},
{
“id”: 323891,
“parent_id”: 323874,
“shop_id”: 747
},
{
“id”: 323892,
“parent_id”: 323874,
“shop_id”: 1547
},
{
“id”: 323893,
“parent_id”: 323874,
“shop_id”: 578
}
]
}

如果想在限制关联查询的字段,或者更改with关联查询的条件,除了可以在模型内部定义的关联方法改动,也支持动态的传入。以方法名转下划线变小写作为key,传递带有query的匿名函数,直接用return query操作就可以,外面传递的会覆盖里面的。

$where[] = [“shop_id”, “=”, “36”];
$where[] = [‘status’, “=”, 2];
$where[] = [‘coupon_id’,’>’,0];
$list = (new Order())
->field(“id,shop_id,parent_id,coupon_id,price,status”)
->with([‘coupon_data’,
‘goods_data’ => function (Query $query) {
return $query->field(“id,name,tutor_info,type_id,price”);
},
‘child_data’ => function(Query $query){
return $query->field(“id,parent_id,name”)->limit(3);
}])
->where($where)
->order(‘id desc’)
->limit(1)
->select();

返回数据
{
“id”: 255505,
“shop_id”: 36,
“parent_id”: 0,
“coupon_id”: 104,
“price”: “1193.00”,
“status”: 2,
“coupon_data”: {
“id”: 104,
“coupon_id”: “OEItMzFBQy00”,
“name”: “十一国庆充电优惠劵”,
“type”: 1,
“status”: 1,
“discount”: “5.00”,
“limit_amount”: “0.00”,
“use_scope”: 0,
“start_at”: 1538150400,
“end_at”: 1538928000,
“use_start_at”: 0,
“use_end_at”: 0,
“limit_number”: 1,
“total”: 100000,
“remarks”: “”,
“created_by”: 883275,
“created_at”: 1538193256,
“updated_by”: 883275,
“updated_at”: 1538205881,
“deleted_by”: 0,
“deleted_at”: 0,
“scope_type”: 0,
“is_app_show”: 1,
“validate_days”: 10,
“shop_ids”: “”
},
“goods_data”: {
“id”: 36,
“name”: “创业邦——创业邦会员 — 找人、找钱、找项目!”,
“tutor_info”: “”,
“type_id”: 4,
“price”: “1098.00”
},
“child_data”: [
{
“id”: 255506,
“parent_id”: 255505,
“shop_id”: 101,
“name”: “CEO实战手册”
},
{
“id”: 255507,
“parent_id”: 255505,
“shop_id”: 260,
“name”: “在线投资人名录(查阅权限一年)”
},
{
“id”: 255508,
“parent_id”: 255505,
“shop_id”: 344,
“name”: “融资实战课:融资谈判过程中的陷阱和博弈”
}
]
}

默认的with用的是in查询,首先从order表查询出order数组,然后根据关联的id然后分别在其他的表执行in批量查询后,再把查询处理的信息拼接到对应的字段。比如假设查询出来10条订单信息,里面的对应的shop_id字段取出来,放在一个数组中,然后根据with对应的条件和字段在goods表查询出商品信息,然后把这些商品信息根据定义的是hasOne还是hasMany拼接到对应的字段,这里就是goods_data字段。因为是in查询,所以这样的其实对应的是传统方式的left join。

查询的字段不在order表,查询的时候要关联其他信息

查询现在现在已经下线的商品,并且已经付款的订单信息。
$where[] = [‘shop_order.status’, “=”, 2];
$where[] = [‘shop_order.coupon_id’, ‘>’, 0];
$where[] = [‘goodsData.status’, ‘=’, 0];//订单表的状态为0表示商品已经下线。
$list = (new Order())
->field(“id,shop_id,parent_id,coupon_id,price,status”)
->withJoin([
‘goods_data’ => function (Query $query) {
return $query->withField(“id,name,tutor_info,type_id,price,status”);
}])
->with([‘coupon_data’,
‘child_data’ => function (Query $query) {
return $query->field(“id,parent_id,name”)->limit(3);
}])
->where($where)
->order(‘id desc’)
->limit(1)
->select();

返回结果
{
“data”: [
{
“id”: 311556,
“shop_id”: 352,
“parent_id”: 0,
“coupon_id”: 57,
“price”: “79.00”,
“status”: 2,
“coupon_data”: {
“id”: 57,
“coupon_id”: “NEJGLUJEQjct”,
“name”: “2018创业邦会员专属优惠券”,
“type”: 1,
“status”: 1,
“discount”: “100.00”,
“limit_amount”: “0.00”,
“use_scope”: 0,
“start_at”: 1518537600,
“end_at”: 1552492800,
“use_start_at”: 1527782400,
“use_end_at”: 1564070400,
“limit_number”: 0,
“total”: 10000,
“remarks”: “”,
“created_by”: 770274,
“created_at”: 1518342307,
“updated_by”: 770274,
“updated_at”: 1530168864,
“deleted_by”: 0,
“deleted_at”: 0,
“scope_type”: 0,
“is_app_show”: 0,
“validate_days”: 0,
“shop_ids”: “”
},
“child_data”: [],
“goods_data”: {
“id”: 352,
“name”: “创业邦——《499套企业财务必备管理手册》 | 上新”,
“tutor_info”: “”,
“type_id”: 10,
“price”: “249.00”,
“status”: 0
}
}
]
}

对于这种需要必须要连表的查询,使用withjoin方法,变化并不大,仅仅是将原来在with的参数放在了withjoin方法里面,同时限定字段用的是withfield方法,不在是field方法。注意field的字段要往前放,不然会报错。这样在获取goods_data的时候不再是采用传统的in方法,而是采用的join方法,但是返回的数据形式依然没有变。既然中间查询tp用了join的方式,就可以将条件写入查询,比如上面的例子,goodsData.status = 0 就是指定join以后商品表的状态是0,goodsData也就是定义在模型内部关联方法名字,就是实际join的中间表的别名。

order by的字段一个在A表,另一个字段在B表。

这种情况应该和上面的情况一样,用的是withJoin,但是根据实际情况不同,join的类型不同。withjoin默认的设置是inner join,我们可以根据实际情况修改为left,或者right join。比如上面的例子,我希望按照商品创建时间递增,并且订单id递减的顺序排序,完全可以采用这样的order by条件。
$where[] = [‘shop_order.status’, “=”, 2];
$where[] = [‘shop_order.coupon_id’, ‘>’, 0];
$where[] = [‘goodsData.status’, ‘=’, 0];
$list = (new Order())
->field(“id,shop_id,parent_id,coupon_id,price,status”)
->withJoin([
‘goods_data’ => function (Query $query) {
return $query->withField(“id,name,tutor_info,type_id,price,status”);
}])
->with([‘coupon_data’,
‘child_data’ => function (Query $query) {
return $query->field(“id,parent_id,name”)->limit(3);
}])
->where($where)
->order(‘goodsData.created_at desc,id asc’)
->limit(3)
->select();

对于有的时候需要不同的join类型,可以在withJoin第二个参数指定。经测试,withjoin只能出现一次,下面写法是错误的。
->withJoin([
‘goods_data’ => function (Query $query) {
return $query->withField(“id,name,tutor_info,type_id,price,status”);
}])
->withJoin([‘coupon_data’],’left’)

也就是withJoin一次查询能设置一次,不能设置某些关系是inner join,某些是left。但是影响似乎不大,left join和用in几乎是等同的,right join用的不多。

所以无法满足A join B并且left join C,同时用B,C两个个表的字段排序的场景

4.某些场景下,就希望把with处理的数据放在和主体数据的统一层级,比如就是希望显示优惠券的名字,完全不需要返回一个copon_data对象,只需要在和订单信息并列的地方多一个coupon _name字段,可以类似下面的设置,key为需要在父级显示的字段名称,value为被关联对象的表对应的字段名。
public function couponData()
{
return $this->hasOne(Coupon::class,”id”,”coupon_id”)
->bind([‘coupon_name’ => ‘name’]);
}

返回值如下,多了一个coupon_name,之前的coupon_data不见了。
{
“id”: 289693,
“shop_id”: 6328,
“parent_id”: 0,
“coupon_id”: 57,
“price”: “0.00”,
“status”: 2,
“coupon_name”: “2018创业邦会员专属优惠券”,
“child_data”: [],
“goods_data”: {
“id”: 6328,
“name”: “创业邦——12节带货体质养成课——素人也能变身带货王,教你月赚10万零花钱”,
“tutor_info”: “”,
“type_id”: 3,
“price”: “99.00”,
“status”: 0
}
}

 


发表评论

电子邮件地址不会被公开。 必填项已用*标注