💎一站式轻松地调用各大LLM模型接口,支持GPT4、智谱、星火、月之暗面及文生图 广告
[TOC] # 介绍 Yii提供了一个[yii\\db\\Query](http://www.yiichina.com/doc/api/2.0/yii-db-query)让我们方便地进行数据查询 这不是模型,而是一个查询器,基本兼容各种主流关系型数据库 示例: ~~~php $user = (new \yii\db\Query())->select(['id', 'name'])->from('user')->one(); print_r($user); ~~~ 和db->createCommand('select id,name from user limit 1')的区别就是:createCommand是直接写一个SQL语句来创建命令的,但Query是根据参数和数据库类型生成不同了最终SQL语句,所以用Query会提升项目的数据库可迁移性,而且代码看起来也比较好阅读 下面是一些常用的查询方式 # all 查询指定表的所有记录 ~~~php $query = new \yii\db\Query(); // SELECT * FROM user $users = $query->from('user')->all(); print_r($users); ~~~ 就是实例化一个`\yii\db\Query`对象来执行查询,`from`方法用于指定表 * * * # where 指定条件 ~~~php $query = new \yii\db\Query(); // SELECT * FROM user WHERE status = 1 $users = $query->from('user')->where(['status' => 1])->all(); print_r($users); ~~~ 其中这个where方法的条件写法将在后面陆续扩展增强演示,各种条件都能表达,除非我写漏 # one 查询单条记录 根据ID查一条记录 ~~~php $query = new \yii\db\Query(); // SELECT * FROM user WHERE id = 1 $data = $query->from('test')->where(['id' => 1])->one(); print_r($data); ~~~ **注意one方法不会limit 1,而只是在编程语言级别获取第1条,所以比较适合条件本身就只有唯一的情况** 提示:后面我的演示代码都不再new了,直接以$query这个变量作为查询构造器开始演示,以上代码只是方便你直接复制调试 # 判断记录是否存在 ~~~php $query = new \yii\db\Query(); //SELECT EXISTS(SELECT * FROM user WHERE type = 2) $exists = $query->from('user')->where(['default_patient_id' => 2])->exists(); if($exists == true){ //... } ~~~ # select 指定查询字段 ~~~php // SELECT id,name FROM user $query->select(['id', 'name'])->from('user')->all(); ~~~ # 定义字段别名 查找年龄为50的用户的id,名字和邮箱 ~~~php // SELECT id,email AS mail FROM user $query->select(['id', 'mail' => 'email'])->from('user')->all(); ~~~ 其中注意第2个字段`'mail' => 'email'`,意思是查询email字段,但返回值时以mail作为key # orderBy排序,limit限制筛选记录数 查找最新注册的一个用户 ~~~php //SELECT * FROM user ORDER BY add_time DESC limit 1 $query->from('user')->orderBy(['add_time' => SORT_DESC])->limit(1)->one(); ~~~ `orderBy`设定排序字段,以`数组`作参数,`key是要排序的字段`,排序方式是PHP自带常量`SORT_ASC`或`SORT_DESC` `limit`用于限制输出几条记录 需要增加更多排序字段则增加更多`KEY => VALUE`的数组单元,比如`orderBy(['id' => SORT_ASC, 'age' => SORT_DESC])` **SORT\_ASC**和**SORT\_DESC**是PHP自带的常量 # 获取SQL语句 ~~~php $query = new \yii\db\Query(); $query->from('user')->where(['id' => 999]); //注意不要加all或one echo $query->createCommand()->sql; //获取 参数化的 SQL原型 echo $query->createCommand()->rawSql; //最终会生成的SQL语句 ~~~ 在还没有执行all或one之前不会进行查询操作,此时再通过createCommand方法创建一个`yii\db\Command`对象,再访问这个对象的rawSql就能得到SQL语句 `\yii\db\Query`只是一个查询命令的**构造器**,底层最终其实还是由createCommand创建一个`yii\db\Command`来执行的) # 获取执行过的语句 在获取执行过的SQL方面,我并没有在Yii里看到简单快速的方式来获取,大家平时调试比较需要,所以我继承yii\\db\\Connection封装了一个方法来获取,[参考代码](https://github.com/kk8686/xoa/blob/master/server/common/ext/db/Connection.php) 使用方法是:下载这个类的代码放到你的项目中,按照你的需求修改命名空间,调用示例 `echo Yii::$app->db->getLastSqls();`获取上次执行的一条SQL语句 `echo Yii::$app->db->getLastSqls(3);`获取上次执行的三条SQL语句 `echo Yii::$app->db->getLastSqls(2, 'from`user`');`获取上两条包含'from`user`'关键字的SQL语句 # 多个and条件 查找年龄为60的女性用户 ~~~php // SELECT * FROM user WHERE age = 50 AND sex = 2 $query->from('user')->where([ 'age' => 50, 'sex' => 2, //假设是女性的类型标识 ])->all(); ~~~ 和大多数框架一样,`where`的参数是数组,每一对`KEY => VALUE`就是一个`AND条件`单元了 好了后面我的代码不执行one或all了,为了让大家方便地直接复制代码看到生成的SQL我都用**createCommand()->rawSql**了 # in条件 查找ID为1,2,3并且年龄为30的用户 ~~~php // SELECT * FROM user WHERE age = 30 AND id in(1, 2, 3) echo $query->from('user')->where([ 'age' => 30, 'id' => [1, 2, 3], ])->createCommand()->rawSql; ~~~ 要实现in条件语句则为这个字段的值传入数组即可 # count 统计 统计(年龄为50)的(男性 和 人妖)的用户数量`假设sex的1=男,2=女,3=人妖` ~~~php // SELECT count(*) FROM user WHERE type = 1 $query->from('user')->where(['type' => 1])->count(); ~~~ # 大于小于条件比较+offset分页 以10个用户显示一页的话,查找出第2页未成年用户 ~~~php // SELECT * FROM user WHERE age < 18 OFFSET 10 LIMIT 10 echo $query->from('user')->where(['<', 'age', 18])->offset(10)->limit(10)->createCommand()->rawSql; ~~~ 这时候你发现where的条件的每一个元素都没有key了,第一个参数表示比较逻辑符,第二个是比较字段,第三个是比较的值 # 大于等于,小于等于条件比较 查询所有成年人 ~~~php // SELECT * FROM user WHERE age >= 18 echo $query->from('user')->where(['>=', 'age', 18])->createCommand()->rawSql; ~~~ # like查询 ~~~php // SELECT * FROM user WHERE name LIKE '%abc%' echo $query->from('user')->where(['like', 'name', 'abc'])->createCommand()->rawSql; // SELECT * FROM user WHERE name LIKE '%abc%' AND name LIKE '%xyz%' echo $query->from('user')->where(['like', 'name', ['abc', 'xyz']])->createCommand()->rawSql; // SELECT * FROM user WHERE name LIKE '%abc' echo $query->from('user')->where(['like', 'name', '%abc'])->createCommand()->rawSql; // SELECT * FROM user WHERE name LIKE 'abc%' echo $query->from('user')->where(['like', 'name', 'abc%'])->createCommand()->rawSql; // SELECT * FROM user WHERE name NOE LIKE '%abc%' echo $query->from('user')->where(['not like', 'name', 'abc'])->createCommand()->rawSql; ~~~ # between筛选和group by分组结果 查询各性别的未成年用户 ~~~php // SELECT * FROM user WHERE age BETWEEN 1 AND 18 GROUP BY sex echo $query->from('user')->where(['between', 'age', 1, 18])->groupBy(['sex']) ->createCommand()->rawSql; ~~~ 要groupBy多个字段的话就是`['sex', 'country']`这样,继续增加数组元素就好了 # having二级筛选 查询某分类的文章发表数量超过100的用户 ~~~php // SELECT count(user_id) as arc_count FROM article WHERE category = 33 HAVING arc_count > 100 $query->select(['arc_count' => 'count(user_id)'])->from('article')->where(['category' => 33])->having('arc_count > 100')->createCommand()->rawSql; ~~~ # or逻辑条件构造 查询名字为lily或者性别为男的用户 ~~~php // SELECT * FROM user WHERE name = 'abc' OR sex = 1 $name = 'lily'; echo $query->from('user')->where([ 'or', ['name' => 'abc'], ['sex' => 1], ])->createCommand()->rawSql; ~~~ 之前where方法传数组是以键值对来表达`key1=val1 and key2 = val2`这样的 但你要表达复杂逻辑关系时,数组的`第一个元素`必须先声明你是`什么逻辑关系`,比如`or` 再以`第二个元素`表达or`左边的条件`是什么,然后`第三个元素`表达or`右边的条件`是什么 这里要注意第二和第三个参数,第一个是**字符串**,表达了逻辑关系,第二和第三个为什么是数组?很简单的,因为逻辑关系的左边和右边可能会有很多条件,比如 ~~~sql (a=1 and b=2) or (右边那一块...) ~~~ 所以左边和右边都可能是多个条件,意味着如果左右边都有多条件的话就应该这样写了: ~~~php where([ 'or', [ //or左边的条件块 'name' => 'abc', ['<', 'age', 18], //当不是 字段 = 值 的条件而是大于小于之类的比较时,请注意这里要用数组了,前面介绍过大于小于,between那些都可以这样用 'xxx' => 6, ], [ //or右边的条件块 'sex' => 1, 'email' => 'abc@dd.com', ], ]) ~~~ # and、or条件嵌套 查询用户表**(年龄为$age)并且(性别为女 或者 2014年注册)**的用户 ~~~php // SELECT * FROM user WHERE age = 33 AND (sex = 2 OR add_time > '2014-01-01 00:00:00') echo $query->from('user')->where([ 'and', ['age' => 33], [ 'or', ['sex' => 2], ['>', 'add_time', '2014-01-01 00:00:00'], ], ])->createCommand()->rawSql; ~~~ 回到数组的条件传达方式,之前说过,where条件的数组第一维度,第一元素是逻辑关键字,第二元素是逻辑的左边条件,第三元素是逻辑的右边条件 而如果右边条件又是一个多条件表达式,那么则用数组表达,这个数组还是第一元素是逻辑关键字,第二元素是左边逻辑,第三元素是你懂的逻辑 那么反过来,逻辑左边条件是复合型条件的话又怎么写第二元素呢? # 追加and条件 ~~~php $title = 'xx'; $query->from('user')->where(['status' => 2]); if($title){ //重点 $query->andWhere(['like', 'title', $title]); } //SELECT * FROM user WHERE status AND title LIKE '%xx%' echo $query->createCommand()->rawSql; ~~~ 这样后面andWhere的会跟前面where的组合成and逻辑,如果要换or那就用orWhere方法也可以 # 追加or条件 和andWhere道理是一样的 ~~~php $title = 'xx'; $query->from('user')->where(['status' => 2]); if($title){ //重点 $query->orWhere(['like', 'title', $title]); } //SELECT * FROM user WHERE status OR title LIKE '%xx%' echo $query->createCommand()->rawSql; ~~~ # 自动过滤空值条件 你曾经可能经常写这样的代码: ~~~php if($location){ $where['location'] = $location; } ~~~ 这样的判断逻辑在很多程序里都有,yii提供了这样的办法: ~~~php $category = 0; $location = ''; // SELECT * FROM user WHERE (category = 0 AND size = 33) AND name LEKE '%xx%' echo $query->from('user')->filterWhere([ 'category_id' => $category, 'location' => $location, 'size' => 33, ])->andWhere(['like', 'name', 'xx'])->createCommand()->rawSql; ~~~ 由于location是**空字符串**所以该字段的条件不会生成,只会生成其它非空字符串的条件 不过如果还是很喜欢自己写SQL的话那请一定要做好参数绑定工作防注入!(参数绑定方法可以自己抽时间另外学习yii官方的教程) # 表别名、左联接查询以及联接表别名的使用 查询所有文章的标题和发布人的名称 ~~~php // SELECT * a.title as title, u.name as username FROM article as a LEFT JOIN user as u ON u.id = a.user_id echo $query->select([ 'title' => 'a.title', 'username' => 'u.name', ])->from(['a' => 'article'])->leftJoin(['u' => 'user'], 'u.id = a.user_id')->createCommand()->rawSql; ~~~ # 更新积分+1或-1什么的 这个要靠[yii\\db\\Expression](http://www.yiichina.com/doc/api/2.0/yii-db-exception)来实现 ~~~php $expression = new \yii\db\Expression('score + 9999'); echo $query->createCommand() ->update('user', ['score' => $expression]) ->rawSql; // UPDATE `user` SET `score` = score + 9999; ~~~ # 批量查询 ->大数据维护处理或统计 (哈哈,说得好高大上~~~反正这数据就是大,数量多!) 有没有过这样的经历,某类数据运营一年半年后产生了上千万条记录,且不这么说,就是上百万条吧 然后这个数据的A字段是一个复合数据,里面保存了一些具体的信息,比如里面包含了10个别的地方的ID集 然后某天的产品需求造成程序要根据这些ID集的数量来进行查询,那总不能count这个字段啊 因为它对于MYSQL而言就是一个字符串而已,要查询就要where这个数据的数量是否大于指定值来查询了 而且还要搞个索引来优化查询速度,没办法,那就要在别的专用查询表或这个表上加一个xxx\_count这样的字段 然后将所有现存数据的A字段ID集个数统计一下存入到xxx\_count字段以便查询。 这是一种需求,别的还有比如说简单地就是查出所有XXX字段大于多少的用户,坑爹了有时候就算你分表了 在前面的需求和这个需求的情况下你都能遇到一个问题:PHP内存不足,不能一下子读出所有的记录来处理。试过了吧? 然后你的办法可能就是做一些分页查询控制,比如第一次查先查1000条,处理完再查第2000条…做完一次再刷新,做完一次再刷新…... 或者还有别的流程,反正就是要你很麻烦地去搭建代码 Yii提供了底层的分页查询处理,不用你再写这些大数据转换的非核心逻辑代码。下面引用官方文档的代码足矣: ~~~php use yii\db\Query; $query = (new Query())->from('user') ->orderBy('id'); foreach($query->batch() as $users) { //这样会先查出100条记录放到$users里,在第二次for循环的时候再查第二百条,第三次就查第三百条…但关于这个100条如何控制数量变成1000条等,暂时未在文档中找到控制参数,然而这个问题不大,毕竟最终会遍历整个表。除了数据维护,其实前端要为用户统计一些数据的时候,也避免了先查出所有记录一齐遍历统计的麻烦,统计一批数据就丢弃一批,再查下一批 } foreach ($query->each() as $user) { //用each时每次for循环都会查询下一条出来 } ~~~