### 2018 年 11 月 22 日 发布 >[danger] 今天是感恩节,首先感谢所有关注和使用`ThinkPHP`的用户,尤其要对那些为框架做过贡献的用户说声感恩,因为有你们的大力支持,`ThinkPHP`才能坚持到今天还在不断更新完善,我也还能写下这篇博客。 本文给大家不完全的整理了一些能够提高开发效率的查询技巧,希望能给你的开发工作带来更多的方便,不过也许有些技巧可能你已经了然于胸了,就当巩固学习了。 >[info] 本文中所有的查询示例都以模型用法为例,只为为了进一步说明所有的查询构造器用法都适用于模型。 ## 查询值为Null的数据 ``` // 查询email为空,并且name不为空的用户数据 User::whereNull('email') ->whereNotNull('name') ->select(); ``` ## 多个字段同一个查询条件 快捷查询方式是一种多字段相同查询条件的简化写法,可以进一步简化查询条件的写法,在多个字段之间用`|`分割表示`OR`查询,用`&`分割表示`AND`查询,例如: ``` User::where('name|title','like','thinkphp%') ->where('create_time&update_time','>',0) ->find(); ``` ## 数组对象查询 如果你升级老版本的系统到`5.1`,由于数组查询方式的变化,而你又不希望全部换成表达式查询,那么可以使用数组对象查询。 ``` use think\db\Where; // 5.0的数组查询条件 $map = [ 'name' => ['like', 'thinkphp%'], 'title' => ['like', '%think%'], 'id' => ['>', 10], 'status' => 1, ]; User::where(new Where($map)) ->select(); ``` 只需要把原来的 ``` where($map) ``` 改成 ``` where(new Where($map)) ``` 即可完成简单的数组查询升级兼容。 ## 使用快捷方法 对于一些常用的查询,系统封装了快捷查询方法,例如: ``` User::whereIn('id', [1,2,3]) ->whereLike('name', 'think%') ->select(); ``` 相当于下面的查询 ``` User::where('id', 'in', [1,2,3]) ->where('name', 'like', 'think%') ->select(); ``` 更多的方法可以参考官方手册或者使用IDE的自动提示。 ## 获取字段值和列数据 对于一些简单的数据获取,你完全不需要查询整个表的数据,例如查询某个字段(满足条件的)值或者列数据。 ``` // 获取id为10的用户名称 User::where('id', 10) ->value('name'); // 获取状态为1的用户名称列表 User::where('status', 1) ->column('name'); // 获取分数大于80的用户分数列表,以用户ID为索引 User::where('score', '>', 80) ->column('score', 'id'); ``` ## 聚合查询 可以直接进行各种聚合查询,包括: ``` User::count(); User::max('score'); User::min('score'); User::avg('score'); Blog::sum('read_count'); ``` 如果你的`min`/`max`查询的是一个字符串类型字段,记得加上第二个参数。 ``` // 获取name字段的最大值 User::max('name', false); ``` ## 时间区间查询 时间查询主要用于时间字段的区间查询,支持所有类型的时间字段。 ``` // 大于某个时间 User::whereTime('birthday', '>=', '2008-10-1') ->select(); // 小于某个时间 User::whereTime('birthday', '<', '2000-10-1') ->select(); // 时间区间查询 User::whereBetweenTime('birthday', '1990-10-1', '2000-10-1') ->select(); // 不在某个时间区间 User::whereNotBetweenTime('birthday', '1970-10-1', '2000-10-1') ->select(); ``` 如果`whereBetweenTime`方法没有指定第三个参数,则表示查询当天的数据 ``` // 查询2000年10月1日出生的用户 User::whereBetweenTime('birthday', '2000-10-1') ->select(); ``` ## 时间表达式查询 对于一些非具体的时间查询,比较适合使用时间表达式进行查询,例如: ``` // 获取今天的博客 Blog::whereTime('create_time', 'today') ->select(); // 获取昨天的博客 Blog::whereTime('create_time', 'yesterday') ->select(); // 获取本周的博客 Blog::whereTime('create_time', 'week') ->select(); // 获取上周的博客 Blog::whereTime('create_time', 'last week') ->select(); // 获取本月的博客 Blog::whereTime('create_time', 'month') ->select(); // 获取上月的博客 Blog::whereTime('create_time', 'last month') ->select(); // 获取今年的博客 Blog::whereTime('create_time', 'year') ->select(); // 获取去年的博客 Blog::whereTime('create_time', 'last year') ->select(); ``` 高级的时间表达式查询可以使用PHP的[相对时间格式](http://php.net/manual/zh/datetime.formats.relative.php),例如: ``` // 查询两天以内的博客 Blog::whereTime('create_time','-2 days') ->select(); // 查询昨天中午后发的博客 Blog::whereTime('create_time','yesterday noon') ->select(); ``` 更多的时间表达式查询你可以自由发挥。 ## 时间字段范围查询 你可以查询当前时间是否在两个时间字段区间范围内,通常用于一些活动以及优惠券的有效期查询等等。 ``` // 查询有效期内的活动 Event::whereBetweenTimeField('start_time','end_time') ->select(); // 查询没有开始或者已经过期的活动 Event::whereNotBetweenTimeField('start_time','end_time') ->select(); ``` ## 字段比较 可以直接比较两个字段的大小进行查询 ``` User::whereColumn('update_time', '>', 'create_time') ->select(); User::whereColumn('score1', '>', 'score2') ->select(); ``` 如果需要比较两个字段相同,可以使用 ``` User::whereColumn('score1', 'score2') ->select(); ``` ## 动态查询 使用动态查询可以进一步简化你的查询条件,不过缺点是可能无法做到IDE的自动提示了,例如: ``` // 根据邮箱(email)查询用户信息 User::whereEmail('thinkphp@qq.com') ->find(); // 根据昵称(nick_name)查询用户 User::whereNickName('like', '%流年%') ->select(); // 根据邮箱查询用户信息 User::getByEmail('thinkphp@qq.com'); // 根据昵称(nick_name)查询用户信息 User::getByNickName('流年'); // 根据邮箱查询用户的昵称 User::getFieldByEmail('thinkphp@qq.com', 'nick_name'); // 根据昵称(nick_name)查询用户邮箱 User::getFieldByNickName('流年', 'email'); ``` ## 条件查询 利用条件查询你可以很方便的控制查询条件分支,你再也不需要在组装查询条件的时候写大量的`if`和`else`了。 ``` User::when($condition, function ($query) { // 满足条件后执行 $query->where('score', '>', 80)->limit(10); })->select(); ``` 并且支持不满足条件的分支查询,并且支持多次调用`when`方法。 ``` User::when($condition, function ($query) { // 满足条件后执行 $query->where('score', '>', 80)->limit(10); }, function ($query) { // 不满足条件执行 $query->where('score', '>', 60); }); ``` ## JSON查询 如果你的字段类型使用的是JSON类型,那么可以直接使用框架提供的JSON查询支持。 ``` User::where('info->nickname', 'ThinkPHP') ->find(); ``` 注意,需要在模型里面定义`JSON`字段属性。 ``` <?php namespace app\index\model; use think\Model; class User extends Model { // 设置json类型字段 protected $json = ['info']; } ``` 如果使用Db查询的话,可以改为 ``` $user = Db::name('user') ->json(['info']) ->where('info->nickname','ThinkPHP') ->find(); ``` ## SQL函数查询 如果需要对某个字段使用SQL函数表达式查询,可以使用 ``` User::whereExp('nickname', "= CONCAT(name, '-', id)") ->whereRaw('LEFT(nickname, 5) = ?', ['think']) ->select(); ``` 注意`whereExp`和`whereRaw`方法的区别,前者是对某个字段使用SQL函数表达式,后者是整个查询就是一个SQL函数表达式。 ## 字段递增/递减 单独对某个字段进行递增/递减操作,可以用: ``` // score 字段加 1 User::where('id', 1) ->setInc('score'); // score 字段加 5 User::where('id', 1) ->setInc('score', 5); // score 字段减 1 User::where('id', 1) ->setDec('score'); // score 字段减 5 User::where('id', 1) ->setDec('score', 5); ``` 可以支持延时更新 ``` // 延时30秒更新score字段 User::where('id', 1)->setInc('score', 1, 30); ``` 如果需要同时递增/递减多个字段的话,可以使用: ``` // 博客的阅读数递增1 评论数递减2 Blog::where('id', 10) ->inc('read_count') ->dec('comment_count', 2) ->update(); ``` ## 指定字段值排序 如果你需要按照指定字段的值的顺序来排序,可以使用 ``` User::where('status', 1) ->orderField('id', [1,2,3]) ->select(); ``` ## 从主库读取 如果你使用了数据库的主从分离,当刚写入数据后,数据库的主从同步可能还没来得及同步,这个时候立刻查询数据可能会出错,你可以使用下面的方法从主库读取。 ``` $user = User::create($data); $user->readMaster()->select(); ``` 你可以全局配置数据写入后自动读取主库 ``` // 模型写入后自动读取主服务器 'read_master' => true, ``` ## 获取自增ID 使用`Db`类的`insert`方法或者模型的`save`方法返回的不是自增主键,不过你可以使用。 ``` $userId = Db::name('user')->insertGetId($data); ``` 如果使用模型的话,自增主键的值会自动赋值给模型对象,可以直接获取。 ``` $user = User::create($data); echo $user->id; ``` ## 模型查询为空的处理 模型查询数据不存在的话返回值为Null,所以必须要添加返回值判断然后进行数据处理,如果你不想手动判断,可以使用下面的方法查询,如果数据不存在则返回空的模型对象。 ``` // 始终返回模型对象 $user = User::where('id', 10)->findOrEmpty(); ``` ## 自动分批写入 如果你需要一次写入大量数据,建议使用`limit`方法自动分批多次写入。 ``` // 自动分批多次写入数据库 每次最多写入1000条 Db::name('user') ->limit(1000) ->insertAll($dataList); ``` 如果是使用模型的话,建议直接使用`saveAll`方法而不需要`limit`方法。 ``` $user = new User; $user->saveAll($dataList); ``` ## 数据分批处理 对于大量数据的处理操作,可以使用`chunk`分批处理方法。 ``` // 每次处理100个数据 User::chunk(100, function($users) { foreach ($users as $user) { // 处理数据 } }); ``` ## 游标查询 对于内存开销比较大的应用,在做大量数据查询和处理的时候,使用`cursor`方法进行游标查询,可以利用PHP的生成器特性,减少内存占用。 ``` $cursor = User::cursor(); foreach($cursor as $user){ // 处理数据 } ``` ## 总结 善于运用查询构造器封装的快捷方法,可以大大提高开发效率,让你更专注于业务逻辑,而不是怎么写查询代码。 另外模型的很多功能都是为了提高开发效率而设计的,这里涉及太多的技巧,就不再一一描述了,以后专门来讲吧。