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

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条件,但是原来的条件依旧存在,是在定义关联条件的基础上新增,而不是覆盖。

  1. 查询的字段在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。

  1. 查询的字段不在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的中间表的别名。

  1. 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
    }
}

转载于:https://www.jianshu.com/p/999cb3cb1223

猜你喜欢

转载自blog.csdn.net/weixin_34007879/article/details/91327652