当前位置: 首页 > 后端技术 > PHP

Laravel更优雅的分表关联查询(更好的性能,SQL的数量=表的数量,涵盖了laravel手册推荐的更多方法)

时间:2023-03-29 22:29:56 PHP

终于被产品的各种不合理需求锤炼了写在laravel上来用更优雅的代码在这里分享给大家。先简单介绍一下基本环境。我们做的是直播app,人比较多,所以每个接口都要尽量优化(主要是SQL查询)。某天,产品问我们主播的礼卡列表是否可以倒序显示用户30天内赠送的礼物数量,显示用户是否为VIP、用户与主播的亲密度、用户等级.30天内的数据。也就是说,之前一直在积累数值的排名表不能再用了,而这30天的时间段是动态的,也就是说这个数据必须只能通过送礼流量groupby来获取。我们的送礼流量计是每月1表。下面介绍一下基本表的情况。礼表honey_log_201708(XXXX分表日期)超级VIP表svip亲昵表亲蜜(这些表的关联是不可避免的,加上分页计数查询。最优的SQL只能是查询表数+1。更合理的,laravel是完全有能力写出优雅的代码)很多人可能会想到laravel的DB原生查询。但是Eloquent有强大的associations,accessormodification,queryscope等功能,这些功能让你的代码非常简洁。先写model1吧。user表user的关键字段是id用户ID,nickanem昵称,exp经验值/**这是laravel的访问器方法。实际的user表是没有level字段的,但是可以在这里定义level是怎么来的,给user加上level属性**/publicfunctiongetLevelAttribute(){//用户的经验值是根据配置文件的level区间,section是自己的封装函数returnsection($this->exp,config('user.level.num'));}}2。用户资料表主要字段uid主键,header_name头像文件名,header_lock头像是否锁定(0,1)header_lock==1||$this->header_name==''){$headerUrl='http://www.cdn.com/'。'default_header_user.png';}else{$headerUrl='http://www.cdn.com/'.$this->header_name;}返回$headerUrl;}}3.SVIP表主字段uid主键,expire过期时间where('expire','>',LARAVEL_START);}}4.主要字段uid,beauty_uid(主播主键),亲密度值qinmi_numqinmi_num,config('qinmi.qinmi.num'));}}5.okay,重点来了honey_log表,这个是重点,因为是子表,现在我们需要封装一个联合表的方法,让这个模型自动将涉及到的子表作为一个表分配给模型查询=',$startTime],['time','<',$endTime]],$wheres);//日期时间戳$startDate=date('Y-m',$startTime);$endDate=date('Y-m',$endTime);//涉及的表数组$tables=[];//循环where数组,格式为[['field','expression','value','and|or'],['field','expression','value','and|or']]//example[['beauty_uid','=','2011654','and']]foreach($wheresas$val){//组装每个where条件$val[2]=$val[2]?$val[2]:“''”;如果(isset($val[3])){$whereConditions[]="{$val[3]}{$val[0]}{$val[1]}{$val[2]}";}else{$whereConditions[]="and{$val[0]}{$val[1]}{$val[2]}";}}//跨表循环计算开始日期和结束日期for($i=$start日期;$i<=$endDate;$i=date('Y-m',strtotime($i.'+1month'))){$tables[]='select'.implode(',',$attributes).'来自cdb_honey_log_'。日期('Yn',strtotime($i))。'哪里1'。内爆('',$whereConditions);}//会得到每个表的子查询,因为有约束条件,所以每个子查询的结果集不会很多模型//sql可能是:(selectxxx,xxxfromhoney_log_20177wheretime>=startdateandtime=startdateandtimesetTable(DB::raw('('.implode('unionall',$tables).')ascdb_honey_log'));}//关联用户资料表,需要取头像publicfunctionuserExt(){return$this->belongsTo(UserExt::class,'uid');}//关联user表,需要取昵称publicfunctionuser(){return$this->belongsTo(User::class,'uid');}//关联SVIP表判断是否为VIPpublicfunctionsvip(){return$this->belongsTo(Svip::class,'uid');}//将用户的接近度值关联到anchorpublicfunctionqinmi(){return$this->hasMany(Qinmi::class,'uid','uid');}//根据礼物数量转换礼物等级publicfunctiongetHoneyLevelAttribute(){returnsection($this->honey_num,config('beauty.honey.num'));}}以上准备工作已经完成。相信熟悉laravel的小伙伴都已经知道如何查询,并且可以做到最优化的SQL和最优雅的laravel写法。好的。看看controller是如何查询的(Request$request){//锚点ID$beauty_uid=$request->input('beauty_uid');//每页显示的数量$pageSize=$request->input('pagesize',10);//当前页面$page=$request->input('page');//缓存数据,根据查询锚点,页码作为keypage$data=Cache::remember("user_for_beauty_rank_{$beauty_uid}_{$pageSize}_{$page}",2,function()use($beauty_uid,$pageSize,$page){//计算30天前$startTime=Carbon::today()->subDays(30)->getTimestamp();//计算结束日期$endTime=Carbon::tomorrow()->getTimestamp();//实例化honeyLog模型,因为自定义的setUnionAllTable方法是一个非静态方法,如果有人知道如何在模型中定义一个非静态方法但是可以调用的话静态的,请告诉我,因为你不想改变底层,laravel使用神奇的静态方法来实例化调用,所以我们可以使用model::select()->where()->ge像t()这样的链式调用,但是模型本身定义的实体方法似乎并没有继承这种调用$honeyLog=newHoneyLog;//分页排序查询用户组$lists=$honeyLog->setUnionAllTable($startTime,$endTime,['uid','honey_num'],[['beauty_uid','=',$beauty_uid]])->select(DB::raw('uid,sum(honey_num)ashoney_num'))->groupBy('uid')->orderBy('honey_num','desc')->paginate($pageSize);//很多人可能会问为什么不使用with()预加载,因为如果使用with,模型会默认构造一个实例,导致表属性丢失。试过就知道了,那么让我们终于明白为什么laravel还是有惰性热切加载的。配置//懒加载和急加载头像,vip,亲密度,昵称//理解下面的关联约束$lists->load(['userExt'=>function($query){$query->select('uid','header_name','header_lock');},'user'=>function($query){$query->select('bid','nickname','exp');},'svip'=>function($查询){//这个validVip是模型定义的范围约束方法,相当于where('expire','>',LARAVEL_START)$query->select('uid')->validVip();},'亲蜜'=>function($query)use($beauty_uid){//这里需要传入主播ID,只求用户对这个主播的贴心值$query->select('uid','亲蜜编号')->where('beauty_uid',$beauty_uid);}]);//现在所有需要的数据都已经找到了。由于我是做API的,所以现在需要组装前端需要的格式返回。//如果是自己做的网页每一个网页,丢给视图去遍历就好$result=[];foreach($listsas$key=>$value){$result[]=[//用户id'uid'=>$value->uid,//礼物数量'honey_num'=>$value->honey_num,//头像'header'=>$value->userExt->header_url,//是否为vip'svip'=>$value->svip?1:0,//礼物等级'honey_level'=>$value->honeyLevel,//亲密度等级'qinmi_level'=>$value->qinmi->isEmpty()?0:$value->qinmi[0]->level,//昵称'nickname'=>$value->user->nickname,//用户级别'level'=>$value->user->level,];}//这是前端要求的格式,应该这样拼装没什么特别的,不过前端习惯这样的结构$data=['page'=>['last_page'=>$lists->lastPage(),'current_page'=>$lists->currentPage(),'list'=>$result,],];返回$数据;});返回响应()->json($data);}}