ThinkPHP5.1模型关联查询实践(订单-订单详情-产品详情-库存-仓库-汇率-国家-发货方式-发货仓库-销售员-销售账号)

实践场景

线上老代码,订单查询页面,查询一百个订单的详情,产生上万条SQL语句,使用模型预加载,解决n+1次查询的问题();

  • 查询100个订单详情的SQL数量(以及统计各个状态总数)
    优化前:上万条sql,耗时数秒
    优化后:Prepare(与Close stmt一体) 语句22个 Execute 语句10个 SQL总查询时间0.465左右
tips:
Prepare在execute阶段可以节省硬解析的时间。如果sql只执行一次,且以prepare的方式执行,
那么sql执行需两次与服务器交互(Prepare和execute), 而以普通(非prepare)方式,
只需要一次交互。这样使用prepare带来额外的网络开销,可能得不偿失。我们再来看同一sql执行多次的情况,
比如以prepare方式执行10次,那么只需要一次硬解析。这时候  额外的网络开销就显得微乎其微了。
因此prepare适用于频繁执行的SQL。
Prepare的另一个作用是防止sql注入

实践总结

//hidden()方法,在查询的时候加了field()方法的情况下,对所有数据可用(5.1.39版的ThinkPHP)
//关联方法,关联键,一定要包含在field()方法里面
//模型的getTypeAttr()方法,会覆盖type字段(原先存在type字段);
//模型的getCnTypeAttr()方法,需要在查询后添加append()方法(原先不存在cn_type)字段
//考虑传入''或者 '0'的影响使用is_null(),不可使用empty()
//思考:闭包条件重用------方法调用(放到模型中,组成一个条件,返回一个$query)
//模型中的count函数与SQL中的count不一致,要想达到SQL中count结果,使用field()->select();
//知识盲区  Order::where()为啥不能复用,必须重新调用一次test48()?
// ->cache('orders',100)//若使用缓存结果,键名设计,需要与查询条件挂钩

控制器代码

    public function test45()
    {
        $page = $this->request->param('page', 1);
        $page_size = $this->request->param('page_size', 100);
        $status = $this->request->param('status', 1);
        $type = $this->request->param('type', null);
        $order_code = $this->request->param('order_code', null);
        $sale_record_number = $this->request->param('sale_record_number', null);
        $sale_user = $this->request->param('sale_user', null);
        $sale_account = $this->request->param('sale_account', null);
        $t1 = microtime(true);
        $order_query = $this->test48($order_code, $sale_record_number, $type, $sale_user, $sale_account);
        //获取某个状态的详情列表
        $re = $order_query->where('status', '=', $status)
            ->field('id,order_code,sale_id,account_id,shipping_service_id,buyer_name,buyer_tel,buyer_email,
            buyer_address1,buyer_address2,buyer_country_id,buyer_city,buyer_state,buyer_postcode,status,
            sale_record_number,customer_code,tracking_number,currency_id,add_time,paid_time,send_time,remarks,type,pp_transaction_id')
            ->order('id', 'desc')
            ->page($page, $page_size)
            ->select();
        //知识盲区,为啥这里必须重新调用一次
        $order_query = $this->test48($order_code, $sale_record_number, $type, $sale_user, $sale_account);
        //获取各个对应条件下, 各状态的统计值
        $count_status = $order_query->group('status')->field('status,count(status) as count')->select()->append(['cn_status'])->toArray();
        $t2 = microtime(true);
        $time1 = $t2 - $t1;
        $data = $re->hidden(['id', 'sale_id', 'account_id', 'shipping_service_id', 'buyer_country_id',
            'detail' => ['id', 'order_id', 'product_id',
                'product' => ['id',
                    'inventory' => ['id', 'product_id', 'warehouse_id',
                        'warehouse' => ['id']]]],
            'shipping' => ['id', 'warehouse_id', 'warehouse' => ['id']],
            'country' => ['id'],
            'currency' => ['id'],
            'account' => ['id'],
            'user' => ['id']])
            ->append(['cn_type', 'cn_status'])->toArray();
        $re = [
            'code' => 200,
            'msg' => 'success',
            'data' => ['time' => $time1, 'count' => $count_status, 'orders' => $data,]
        ];
        return json($re);
    }   

模型关系构造(让获取数据的条件,和统计的条件一致)

private function test48($order_code, $sale_record_number, $type, $sale_user, $sale_account)
   {
       $order_query = Orders::with(['detail' => function ($query_d) {
           $query_d->with(['product' => function ($query_p) {
               $query_p->with(['inventory' => function ($query_i) {
                   $query_i->with(['warehouse' => function ($query_wa) {
                       $query_wa->field('id,name');
                   }])->field('id,product_id,warehouse_id,quantity,quantity_lock')->where('quantity', '>', 0);
               }])->field('id,sku,cn_name');
           }])->field('id,order_id,product_code,product_id,quantity,sale_price,other_fee,total_price,item_number,transaction_id');
       }, 'shipping' => function ($query_s) {
           $query_s->with(['warehouse' => function ($query_w) {
               $query_w->field('id,name');
           }])->field('id,code,cn_name,warehouse_id');
       }, 'country' => function ($query_c) {
           $query_c->field('id,cn_name,en_name');
       }, 'currency' => function ($query_cu) {
           $query_cu->field('id,code,name');
       }, 'account' => function ($query_a) {
           $query_a->field('account,id');
       }, 'user' => function ($query_u) {
           $query_u->field('id,name');
       }])->where(function ($query) use ($order_code, $sale_record_number, $type, $sale_user, $sale_account){
               if (!is_null($order_code)) {
                   $query->where('order_code', '=', $order_code);
               }
               if (!is_null($sale_record_number)) {
                   $query->where('sale_record_number', '=', $sale_record_number);
               }
               if (!is_null($type)) {
                   $query->where('type', '=', $type);
               }
               if (!is_null($sale_user)) {
                   $query->where('sale_id', '=', $sale_user);
               }
               if (!is_null($sale_account)) {
                   $query->where('account_id', '=', $sale_account);
               }
           });
       return $order_query;
   }

订单模型代码

<?php
namespace app\index\model;
use think\Model;

class Orders extends Model
{
    public static $types=[0=>'普通订单',1=>'重寄订单',2=>'线下交易',3=>'退件订单',4=>'退款订单',5=>'第三方赔偿'];
    public static $status=[0=>'已删除',1=>'未确定',2=>'待检查',3=>'仓储缺货',4=>'直发缺货',
        5=>'已确定',6=>'已发货',8=>'已核算',100=>'售价异常',101=>'客户取消'];
    //订单与订单子项 hasMany()(订单子项中有order_id)
    public function detail(){
        return $this->hasMany('OrdersDetail','order_id','id');
    }
    //订单与国家,belongsTo() (订单中有buyer_country_id)
    public function country(){
        return $this->belongsTo('Country','buyer_country_id','id');
    }
    public function shipping(){
        return $this->belongsTo('ShippingService','shipping_service_id','id');
    }
    public function currency(){
        return $this->belongsTo('Currency','currency_id','id');
    }
    public function account(){
        return $this->belongsTo('SaleAccount','account_id','id');
    }
    public function user(){
        return $this->belongsTo('User','sale_id','id');
    }
    //添加额外字段(不覆盖原有的type字段,新增cn_type字段)
    public function getCnTypeAttr($value,$data){
        return self::$types[$data['type']];
    }
    public function getCnStatusAttr($value,$data){
        return self::$status[$data['status']];
    }
}

接口返回的一个数据

 {
            "order_code": "DE********21",
            "buyer_name": "F******n",
            "buyer_tel": "6*****5",
            "buyer_email": "fr*****1@cox.net",
            "buyer_address1": "2*****ve",
            "buyer_address2": "",
            "buyer_city": "Peoria",
            "buyer_state": "AZ",
            "buyer_postcode": "8****8",
            "status": 1,
            "sale_record_number": "R****8",
            "customer_code": "q***r",
            "tracking_number": "",
            "currency_id": 2,
            "add_time": "2019-09-16 03:20:57",
            "paid_time": "2019-09-15 21:30:16",
            "send_time": "0000-00-00",
            "remarks": "",
            "type": 0,
            "pp_transaction_id": null,
            "detail": [
                {
                    "product_code": "V****B",
                    "quantity": 1,
                    "sale_price": "12.99",
                    "other_fee": "0.00",
                    "total_price": "12.99",
                    "item_number": "333******77",
                    "transaction_id": "16*****14",
                    "product": {
                        "sku": "VB****B",
                        "cn_name": "充*******",
                        "inventory": [
                            {
                                "quantity": 28,
                                "quantity_lock": 0,
                                "warehouse": {
                                    "name": "东莞仓库"
                                }
                            },
                            {
                                "quantity": 3,
                                "quantity_lock": 0,
                                "warehouse": {
                                    "name": "东莞次品仓"
                                }
                            },
                            {
                                "quantity": 708,
                                "quantity_lock": 3,
                                "warehouse": {
                                    "name": "美国达拉斯二仓"
                                }
                            },
                            {
                                "quantity": 38,
                                "quantity_lock": 0,
                                "warehouse": {
                                    "name": "美国LA仓"
                                }
                            }
                        ]
                    }
                }
            ],
            "country": {
                "cn_name": "美国",
                "en_name": "United States"
            },
            "currency": {
                "code": "USD",
                "name": "美元"
            },
            "account": {
                "account": "R***"
            },
            "user": {
                "name": "肖*****"
            },
            "cn_type": "普通订单",
            "cn_status": "未确定"
        },
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值