终于被产品的各种刁钻不合常理的需求磨炼出用laravel写出较为优雅的代码,在这里给大家分享一下。

先简单介绍一下基本环境,我们是做一款直播APP的,人很多,所以每个接口都必须尽量优化(主要是SQL的查询)。

有一天,产品跟我们说,那个针对主播的送礼牌行榜能否显示30天内的用户送礼数倒序排列,显示用户是否VIP,用户对主播的亲密度,还有用户的等级。

30天内的数据。也就是说之前那张一直累计数值的排行表不能使用了,而且这个30天是个动态的,也就是说这个数据必须只能利用送礼流水group by出来。我们的送礼流水表是1个月1张表的

介绍一下基本表的情况
用户表user
用户资料表user_ext(你大爷的头像竟然放这张表,谁搞的站出来,看我不弄死你)
礼物表honey_log_201708(XXXX分表日期)
超级VIP表svip
亲密度表qinmi
(这几张表的关联是无法避免的,加上分页count查询。SQL最优就只能是查询表的数量+1才算是比较合理,laravel完全有足够的能力写出优雅的代码)

很多人可能会想到laravel的DB原生查询了么。但是Eloquent有强大的关联,访问器修改,查询范围等等这些功能让你的代码非常简洁。

我们先写model
1.用户表user
关键字段是id用户ID,nickanem昵称,exp经验值

<?php
namespace App\Models;use Illuminate\Database\Eloquent\Model;class User extends Model
{protected $table = 'user';/**这个是laravel的访问器方法,实际user表是没有等级这个字段的但是可以在这里定义出等级是怎么来的,给user添加了level这个属性**/public function getLevelAttribute(){//用户的经验值根据配置文件的等级区间计算用户等级,section是自己封装函数return section($this->exp, config('user.level.num'));}
}

2.用户资料表
主要字段uid主键,header_name头像文件名,header_lock头像是否被锁(0,1)

<?php
namespace App\Models;use Illuminate\Database\Eloquent\Model;class UserExt extends Model
{protected $table = 'user_ext';protected $primaryKey = 'uid';//添加头像属性public function getHeaderUrlAttribute(){//头像放在了cdn上,先判断一下头像是空的话给默认,或者头像被管理员锁了的话也给默认if ($this->header_lock == 1 || $this->header_name == '') {$headerUrl = 'http://www.cdn.com/' . 'default_header_user.png';} else {$headerUrl = 'http://www.cdn.com/' . $this->header_name;}return $headerUrl;}
}

3.SVIP表
主要字段uid主键,expire过期时间

<?php
namespace App\Models;use Illuminate\Database\Eloquent\Model;class Svip extends Model
{protected $table = 'svip';protected $primaryKey = 'uid';//这里用到了laravel的查询范围,可以快速调用判断是否VIP,不用每次都写一次wherepublic function scopeValidVip($query){return $query->where('expire', '>', LARAVEL_START);}
}

4.亲民度qinmi表
主要字段uid,beauty_uid(主播主键),qinmi_num亲密度值

<?php
namespace App\Models;use Illuminate\Database\Eloquent\Model;class Qinmi extends Model
{protected $table = 'qinmi';public function getLevelAttribute(){//转换亲密度等级                return section($this->qinmi_num, config('qinmi.qinmi.num'));}
}

5.好了,重点来了。honey_log表,这个是重点,因为它是分表的,现在我们要封装一个union表的方法,让这个model自动把涉及的分表作为一张表赋予model查询

<?php
namespace App\Models;use Illuminate\Database\Eloquent\Model;
use DB;class HoneyLog extends Model
{/*传入查询的开始日期和结束日期用于计算跨越的表和达到约束表数据的目的。外部可以调整查询的列,还可以添加where条件*/public function setUnionAllTable($startTime = LARAVEL_START, $endTime = LARAVEL_START, $attributes = ['*'], $wheres = []){//约束条件$whereConditions = [];$wheres = array_merge([['time', '>=', $startTime], ['time', '<', $endTime]], $wheres);//时间戳转日期$startDate = date('Y-m', $startTime);$endDate = date('Y-m', $endTime);//涉及的表数组$tables = [];//循环where数组,格式是[['字段','表达式','值',' and|or '],['字段','表达式','值',' and|or ']]//例子[['beauty_uid', '=', '2011654', 'and']]foreach ($wheres as $val) {//组装每个where条件$val[2] = $val[2] ? $val[2] : "''";if (isset($val[3])) {$whereConditions[] = " {$val[3]} {$val[0]} {$val[1]} {$val[2]}";} else {$whereConditions[] = " and {$val[0]} {$val[1]} {$val[2]}";}}//循环开始日期和结束日期计算跨越的表for ($i = $startDate; $i <= $endDate; $i = date('Y-m', strtotime($i . '+1month'))) {$tables[] = 'select ' . implode(',', $attributes) . ' from cdb_honey_log_' . date('Yn', strtotime($i)) . ' where 1' . implode('', $whereConditions);}//会得到每一个表的子查询,因为都有约束条件,所以每一个子查询得结果集都不会很多//用setTable的方法把这个子查询union all 后 as一个表名作为model的table属性//sql大概会是:(select xxx,xxx from honey_log_20177 where time >= 开始日期 and time < 结束日期 and xxx union all select xxx,xxx from honey_log_20178 where time >= 开始日期 and time < 结束日期 and xxx) as cdb_honey_log//核心是看你输入的开始日期和结束日期和约束条件,组装成一个union all的子查询然后作为table赋予modelreturn $this->setTable(DB::raw('(' . implode(' union all ', $tables) . ') as cdb_honey_log'));}//关联用户资料表,要拿头像public function userExt(){return $this->belongsTo(UserExt::class, 'uid');}//关联用户表,要拿昵称public function user(){return $this->belongsTo(User::class, 'uid');}//关联SVIP表,要判断是否VIPpublic function svip(){return $this->belongsTo(Svip::class, 'uid');}//关联用户对于主播的亲民值public function qinmi(){return $this->hasMany(Qinmi::class, 'uid', 'uid');}//转化送礼等级,按送礼金额转化public function getHoneyLevelAttribute(){return section($this->honey_num, config('beauty.honey.num'));}
}

以上准备工作都有了。相信熟悉laravel的人已经知道怎么查询了,可以达到最优化的SQL,和最优雅的laravel写法。
好。我们来看看控制器如何查询

<?php
namespace App\Http\Api\Controllers;use Illuminate\Http\Request;
use DB;
use Cache;
use Carbon\Carbon;
use App\Models\HoneyLog;class AAHoneyLogController extends Controller
{public function index(Request $request){// 主播ID$beauty_uid = $request->input('beauty_uid');// 每页显示数量$pageSize = $request->input('pagesize', 10);// 当前页$page = $request->input('page');// 缓存数据,按查询的主播,页数作为key分页$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方法是非静态方法,如果谁知道如何在model定义非静态方法但是可以通过静态调用的话,请告诉我,因为不想改底层,laravel是用了魔法静态方法实例化调用的,所以我们才可以使用model::select()->where()->get()这样的链式调用,但是在model自己定义的实体方法好像并没有继承到这种调用$honeyLog = new HoneyLog;// 查询该主播ID30天有亲密值的用户group by 排序 用分页paginate$lists = $honeyLog->setUnionAllTable($startTime, $endTime, ['uid', 'honey_num'], [['beauty_uid', '=', $beauty_uid]])->select(DB::raw('uid, sum(honey_num) as honey_num'))->groupBy('uid')->orderBy('honey_num', 'desc')->paginate($pageSize);// 很多人可能会问为什么不用with()渴求式加载,因为用了with的话,model会默认去构造一次实例,导致table属性丢失,你们试试就知道了,所以下面我们终于理解到laravel为什么会还有个懒惰渴求式加载了,简直绝配// 懒惰渴求式加载头像,vip,亲密值,昵称// 好好理解下面的关联约束$lists->load(['userExt' => function ($query) {$query->select('uid', 'header_name', 'header_lock');},'user' => function ($query) {$query->select('bid', 'nickname', 'exp');},'svip' => function ($query) {// 这个validVip是模型定义的范围约束方法,相当于where('expire', '>', LARAVEL_START)$query->select('uid')->validVip();},'qinmi' => function ($query) use ($beauty_uid) {// 这里需要传入主播ID,只查找用户对于这个主播的亲密值$query->select('uid', 'qinmi_num')->where('beauty_uid', $beauty_uid);}]);// 现在需要的数据都已经全部查出来了,由于我做的是API,现在要组装前端需要的格式return出去就可以了,// 如果是自己做的web网页,就直接丢给视图遍历就可以了$result = [];foreach ($lists as $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,],];return $data;});return response()->json($data);}}

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

  1. (探讨)MyBatis中的N+1问题,多表关联查询效率高点还是多次单表查询效率高

    MySQL多表关联查询效率高点还是多次单表查询效率高,为什么? https://blog.csdn.net/weixin_33869377/article/details/86265527 MyBat ...

  2. MySQL多表关联查询效率高点还是多次单表查询效率高,为什么?

    这里写目录标题 MySQL多表关联查询对比多次单表查询,哪个效率高? 疑问: 高手解答: <阿里巴巴JAVA开发手册>里面写超过三张表禁止join 这是为什么?这样的话那sql要怎么写? ...

  3. mysql 子表 关联查询语句_MySQL基本SQL语句之单表查询、多表查询和子查询

    一.简单查询: 基本语法:SELECT * FROM tb_name;查询全部 SELECT field1,field2 FROM tb_name; 投影 SELECT [DISTINCT] * FR ...

  4. 面试官:为什么mysql不建议执行超过3表以上的多表关联查询?

    概述 前段时间在跟其他公司DBA交流时谈到了mysql跟PG之间在多表关联查询上的一些区别,相比之下mysql只有一种表连接类型:嵌套循环连接(nested-loop),不支持排序-合并连接(sort ...

  5. 为什么 MySQL 不建议执行超过 3 表以上的多表关联查询?

    目录 一.概述 二.讨论 一.概述 前段时间在跟其他公司 DBA 交流时谈到了 MySQL 与 PG之间在多表关联查询上的一些区别,相比之下 MySQL 只有一种表连接类型:嵌套循环连接 (neste ...

  6. mysql一次查询无关联多个表_面试官:为什么mysql不建议执行超过3表以上的多表关联查询?...

    点关注,不迷路:持续更新Java架构相关技术及资讯热文!!! 概述 前段时间在跟其他公司DBA交流时谈到了mysql跟PG之间在多表关联查询上的一些区别,相比之下mysql只有一种表连接类型:嵌套循环 ...

  7. 为什么mysql不建议执行超过3表以上的多表关联查询?

    概述 前段时间在跟其他公司DBA交流时谈到了mysql跟PG之间在多表关联查询上的一些区别,相比之下mysql只有一种表连接类型:嵌套循环连接(nested-loop),不支持排序-合并连接(sort ...

  8. 为什么不建议执行超过3表以上的多表关联查询?

    概述:前段时间在跟其他公司DBA交流时谈到了mysql跟PG之间在多表关联查询上的一些区别,相比之下mysql只有一种表连接类型:嵌套循环连接(nested-loop),不支持排序-合并连接(sort ...

  9. MyBatis实现中间表关联查询

    MyBatis实现中间表关联查询 通常构建数据多对多模型的关系时,我们需要再建立一张中间表来关联另外两张表. 下面介绍一下,怎么用MyBatis来实现中间表关联查询并封装到实体类对象中. 现在有三张表 ...

  10. asp多表查询并显示_SpringBoot系列(五):SpringBoot整合Mybatis实现多表关联查询

    本文我们将继续分享介绍Spring Boot在整合Mybatis开发企业级应用时其他典型的业务场景,即Mybatis是如何实现多表关联查询时将查询结果集与对象进行映射的,主要的内容包含"一对 ...

最新文章

  1. 阿里某p9哭穷:300万年薪,依然挣扎在温饱线上!
  2. 常见的网络***有哪些?
  3. GCC选项_-Wl,-soname
  4. js获取当前页面url网址等信息
  5. 软设考试成绩查询结果
  6. Docker的一些理解(二)
  7. 一个七年的老测试给想入行软件测试这个行业的二十条建议
  8. php 中文手册下载
  9. matlab差分法案例,怎么用matlab实现向前差分法,向后差分法,中心差分法等,最好举个例子,谢谢啦...
  10. oracle修改执行计划,调整执行计划的三种方法
  11. java获取pdf文字坐标_Java 获取PDF关键字坐标
  12. 随想录一期 day2 [977.有序数组的平方|209. 长度最小的子数组|59.螺旋矩阵II(剥洋葱)]
  13. abcd选项后的数据分析_引入新的数据abcs
  14. 【老生谈算法】matlabAP近邻传播聚类算法源码——聚类算法
  15. 外贸询盘、对话、常用话术英语(上)
  16. 分布式算法原理(转)
  17. JVM: java虚拟机
  18. 代码审计工具Checkmarx安装环境和安装过程
  19. NVIDA-TensorRT部署(一)
  20. 腾讯云新用户怎么配置服务器的方法教程

热门文章

  1. 《计算机系统基础》复习——简答题 01
  2. android悬浮窗服务卡死,Android 悬浮窗兼容问题谈
  3. css一些需要注意的东西
  4. 电子阅读器行业市场研究分析及未来趋势预测分析
  5. nginx集群部署,实现负载均衡和nfs共享
  6. idea查找当前方法的实现_intellij idea快速查看当前类中的所有方法(推荐)
  7. 惯量比多少合适_惯量比折算问题
  8. C#通过NOPI读写Excel,并插入图片,VS2019
  9. 64位Windows2003下如何正确发布VesnData.Net(VDN)
  10. 电子签章(Electronic Signature)在C#中的实现方法