大家好,我是?WeiyiGeek,一名深耕安全运维开发(SecOpsDev)领域的技术从业者,致力于探索DevOps与安全的融合(DevSecOps),自动化运维工具开发与实践,企业网络安全防护,欢迎各位道友一起学习交流、一起进步 ,若此文对你有帮助,一定记得倒点个关注?与小红星??,收藏学习不迷路??。
0x00 前言简述
描述:前面讲解了 MySQL 数据库中基础 DDL 、DML 语句,想必各位看友都已经有所了解,或者已实践过数据库的创建操作、表的创建操作,数据的增删改操作。在此基础上,我们还需要对数据库中的数据进行查询过滤操作,所以此文将针对 DQL (数据查询语言)基础 SELECT 语句及其常用查询字句关键字进行讲解,附带实践查询操作示例,最后还使用SQL语句演示查询子句各个的执行顺序。
温馨提示:若文章代码块中存在乱码,请通过文末的阅读原文链接,在知识星球中阅读,或者直接访问?https://articles.zsxq.com/id_ejvei8kvqnd2.html
在详细讲解 DQL 语句之前,我们先来看看本小节,究竟要学习实践那些查询关键字,以及基础 SQL 查询语法。
SELECT?字段列表 ?-- 字段列表可以理解为:表中的列名,多个列名使用逗号分隔
FROM?表名列表 ? ?-- 表名列表可以理解为:一个或多个表的名字,多个表名使用逗号分隔
WHERE?条件列表 ??-- 条件列表:指的是对字段设置过滤条件,多个条件使用 AND 或 OR 进行连接
GROUP?BY?分组字段列表 ??-- 分组字段列表:指的是将查询结果按照某一个字段进行分组
HAVING?分组后的条件列表?-- 分组后的条件列表:指的是对分组的结果设置过滤条件
ORDER?BY?排序字段列表 ??-- 排序字段列表:指的是对查询结果进行正序、倒序排序
LIMIT?起始索引, 查询记录数; ?-- 分页查询,起始索引指的是从哪一条记录开始取数据,查询记录数指的是取出多少条记录
温馨提示:本文后续所实践的员工表?employees
,可在《DBA | MySQL数据库基础数据操作学习实践笔记》文章中获取,若没有创建的童鞋请自行创建并插入数据。
weiyigeek.top-演示SQL查询的表数据图
温馨提示:为了方便各位看友由浅入深的学习,复杂的多表查询语句将在后续的单独文章中进行讲解。
0x01 DQL 数据查询语句
1.查询单、多个或全部字段
描述:SQL 语句中,查询语句的字段列表指的是要查询哪些列的数据。在实际的业务系统中,不建议使用 * 符号来查询所有字段,而是指定需要查询哪些字段。
语法
-- # 语法:查询单个字段
SELECT?字段名?FROM?表名;
-- # 语法:查询多个字段
SELECT?字段名1, 字段名2?FROM?表名;
-- # 语法:查询 employees 表中的所有字段,特别注意:在实际业务系统开发中,不建议使用*查询所有字段,而是指定查询哪些字段
SELECT?*?FROM?表名;
示例
-- # 查询employees表中的所有字段
mysql>?SELECT?*?FROM?employees;
| id | uid ?| name ? | gender | age | phone_number | skills ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?| id_card ? ? ? ? ? ?| city ? | entry_date | salary ? |
+----+------+--------+--------+-----+--------------+-------------------------------------------+--------------------+--------+------------+----------+
| ?1 | 001 ?| 张三 ? | 男 ? ? | ?30 | 13800138000 ?| {"编程": "高级", "设计": "中级"} ? ? ? ? ?| 500102199001010001 | 重庆 ? | 2020-01-01 | ?7500.00 |
| ?2 | 002 ?| 李四 ? | 女 ? ? | ?28 | 13900139000 ?| {"测试": "高级", "项目管理": "中级"} ? ? ?| 500102199201010002 | 北京 ? | 2020-02-15 | 15800.00 |
| ?3 | 003 ?| 王五 ? | 男 ? ? | ?35 | 13700137000 ?| {"架构": "高级", "数据库": "专家"} ? ? ? ?| 500102198801010003 | 上海 ? | 2019-05-10 | 16000.00 |
| ?4 | 004 ?| 赵六 ? | 女 ? ? | ?26 | 13600136000 ?| {"前端": "高级", "UI设计": "中级"} ? ? ? ?| 500102199601010004 | 杭州 ? | 2021-03-20 | 12000.00 |
| ?5 | 005 ?| 钱七 ? | 男 ? ? | ?32 | 13500135000 ?| {"运维": "高级", "网络安全": "中级"} ? ? ?| 500102199001010005 | 成都 ? | 2018-08-05 | ?9000.00 |
| ?6 | 006 ?| 孙八 ? | 女 ? ? | ?29 | 13400134000 ?| {"数据分析": "高级", "机器学习": "中级"} ?| 500102199301010006 | 深圳 ? | 2020-11-12 | 13500.00 |
| ?7 | 007 ?| 周九 ? | 男 ? ? | ?27 | 13300133000 ?| {"后端": "中级", "移动开发": "高级"} ? ? ?| 500102199501010007 | 广州 ? | 2021-07-30 | 11500.00 |
| ?8 | NULL | 吴十 ? | 女 ? ? | ?31 | 13200132000 ?| {"产品经理": "高级", "市场分析": "专家"} ?| 500102199101010008 | 成都 ? | 2019-09-18 | 12800.00 |
-- # 查询employees表中的 id、name 字段
mysql>?SELECTid,?nameFROM?employees;
| id | name ? |
+----+--------+
| ?1 | 张三 ? |
| ?2 | 李四 ? |
| ?3 | 王五 ? |
| ?4 | 赵六 ? |
| ?5 | 钱七 ? |
| ?6 | 孙八 ? |
| ?7 | 周九 ? |
| ?8 | 吴十 ? |
2.设置别名
描述:AS 关键字用于为字段设置别名,别名可以使用单引号括起来,
-- # 语法:为字段设置别名
SELECT?字段名?AS?别名, ...?FROM?表名;
SELECT?字段名 别名, ...?FROM?表名; ??-- 设置别名也可以忽略 AS 关键字
SELECT?字段名1AS?别名1, 字段名2AS?别名2FROM?表名;
-- # 示例:
-- 查询employees表中的uid、name字段,并为这两个字段设置别名
mysql>?SELECT?uid?AS'员工ID',?name'员工姓名'FROM?employees;
| 员工ID ? | 员工姓名 ? ? |
+----------+--------------+
| 001 ? ? ?| 张三 ? ? ? ? |
| 002 ? ? ?| 李四 ? ? ? ? |
| 003 ? ? ?| 王五 ? ? ? ? |
| 004 ? ? ?| 赵六 ? ? ? ? |
| 005 ? ? ?| 钱七 ? ? ? ? |
| 006 ? ? ?| 孙八 ? ? ? ? |
| 007 ? ? ?| 周九 ? ? ? ? |
| NULL ? ? | 吴十 ? ? ? ? |
3.去除重复记录
描述:DISTINCT 关键字用于去除查询结果中的重复记录。
语法示例
-- # 语法:去除重复记录
SELECT?DISTINCT?字段名?FROM?表名;
-- # 示例:
-- 查询employees表中的 city 字段,去除重复记录,可查看到原始有 8 条记录,除重复后只剩 7 条
mysql>?SELECT?DISTINCT?city?FROM?employees;
重庆
北京
上海
杭州
成都
深圳
广州
4.条件过滤查询
描述:WHERE 子句用于过滤记录,只返回满足条件的记录,常与比较运算符、逻辑运算符、以及其他条件关键字IN、BETWEEN .. AND ...、IS NULL、LIKE
等一起使用。
语法
-- # 语法:条件过滤查询
SELECT?字段名?FROM?表名?WHERE?条件;
条件
比较运算符 | 功能 |
---|---|
> |
大于 |
>= |
大于等于 |
< |
小于 |
<= |
小于等于 |
= |
等于 |
<> ?或者?!= |
不等于 |
逻辑运算符 | 功能 |
---|---|
AND ?或者?&& |
并且,两边条件同时满足时结果为真 |
OR ?或者?|| |
或者,两边条件满足一个时结果为真 |
NOT ?或者?! |
非,对条件取反 |
其它运算符 | 功能 |
---|---|
IN(值1,值2,...) |
在指定列表中,相当于多个 OR 条件 |
BETWEEN .. AND .. |
在某个范围内(包括边界值) |
IS NULL |
判断是否为空值 |
LIKE |
模糊匹配,% ?表示任意多个字符,_ ?表示一个字符 |
示例
-- 例1.查询employees表中的员工 uid、name、age 字段,条件为:年龄超过30岁
mysql>?SELECT?uid,?name, age?FROM?employees?WHERE?age >?30;
+------+--------+-----+
| uid ?| name ? | age |
+------+--------+-----+
| 003 ?| 王五 ? | ?35 |
| 005 ?| 钱七 ? | ?32 |
| NULL | 吴十 ? | ?31 |
+------+--------+-----+
-- 例2.查询employees表中的员工 uid、name、gender、salary 字段,条件为: 性别为男 且 薪资大于等于 10000?
mysql>?SELECT?uid,?name, gender, salary?FROM?employees?WHERE?gender =?'男'AND?salary >=?10000;
+------+--------+--------+----------+
| uid ?| name ? | gender | salary ? |
+------+--------+--------+----------+
| 003 ?| 王五 ? | 男 ? ? | 16000.00 |
| 007 ?| 周九 ? | 男 ? ? | 11500.00 |
+------+--------+--------+----------+
-- 例3.查看 employees 表中的城市地点为重庆或程度的员工信息
mysql>?SELECT?*?FROM?employees?WHERE?city?IN?('北京',?'上海');
| id | uid ?| name ? | gender | age | phone_number | skills ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? | id_card ? ? ? ? ? ?| city ? | entry_date | salary ? |
+----+------+--------+--------+-----+--------------+------------------------------------------------+--------------------+--------+------------+----------+
| ?2 | 002 ?| 李四 ? | 女 ? ? | ?28 | 13900139000 ?| {"测试": "高级", "项目管理": "中级"} ? ? ? ? ? | 500102199201010002 | 北京 ? | 2020-02-15 | 15800.00 |
| ?3 | 003 ?| 王五 ? | 男 ? ? | ?35 | 13700137000 ?| {"架构": "高级", "数据库": "专家"} ? ? ? ? ? ? | 500102198801010003 | 上海 ? | 2019-05-10 | 16000.00 |
-- 例4.查询employees表中的员工 uid、name、gender 字段,条件为:员工编号为空的。
mysql>?SELECT?uid,?name, gender?FROM?employees?WHERE?uid?ISNULL;
| uid ?| name ? | gender |
+------+--------+--------+
| NULL | 吴十 ? | 女 ? ? |
-- 例5.查询employees表中的员工 uid、name、salary 字段,条件为:性别为男,年龄小于30岁,薪资大于等于10000 小于15000 。
SELECT?uid,?name, gender, salary?FROM?employees?WHERE?gender =?'男'AND?age <?30AND?(salary >=?10000AND?salary <?15000);
-- 或者使用 BETWEEN 关键字进行范围查询
SELECT?uid,?name, gender, salary?FROM?employees?WHERE?gender =?'男'AND?age <?30AND?(salary?BETWEEN10000AND15000); ?
| uid ?| name ? | gender | salary ? |
+------+--------+--------+----------+
| 007 ?| 周九 ? | 男 ? ? | 11500.00 |
-- 例6.查询employees表中的员工 uid、name、skills 字段,条件为:skills 字段包含“专家”关键字
SELECT?uid,?name, skills?FROM?employees?WHERE?skills?LIKE'%专家%';
| uid ?| name ? | skills ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? |
+------+--------+------------------------------------------------------+
| 003 ?| 王五 ? | {"架构": "高级", "数据库": "专家"} ? ? ? ? ? ? ? ? ? |
| NULL | 吴十 ? | {"产品经理": "高级", "市场分析": "专家"} ? ? ? ? ? ? |
-- 例7.显示employees表中的员工 uid、name 字段,条件为:uid 不为空
SELECT?uid,?nameFROM?employees?WHERE?uid?ISNOTNULL;
-- 或者使用 NOT 关键字进行取反操作
SELECT?uid,?nameFROM?employees?WHERENOT?(uid?ISNULL);
| uid ?| name ? |
+------+--------+
| 001 ?| 张三 ? |
| 002 ?| 李四 ? |
| 003 ?| 王五 ? |
| 004 ?| 赵六 ? |
| 005 ?| 钱七 ? |
| 006 ?| 孙八 ? |
| 007 ?| 周九 ? |
5.分组聚合查询
描述:在学习分组查询之前,我们先来了解一下聚合函数的概念,以及相关函数的介绍, 然后再学习分组查询过滤的语法及其示例。
聚合函数
聚合函数是对一组值执行计算并返回单个值的函数,常见的聚合函数包括?COUNT
、SUM
、AVG
、MAX
?和?MIN
?等。
COUNT()
-
- :计算行数
SUM()
-
- :计算和
AVG()
-
- :计算平均值
MAX()
-
- :计算最大值
MIN()
- :计算最小值
温馨提示:若字段中存在?NULL
?值,聚合函数会忽略这些值进行计算。
语法示例
-- # 语法
SELECT?聚合函数(字段名)?FROM?表名 [WHERE?条件] [GROUPBY?分组依据列名] [HAVING?过滤条件];
-- # 示例
-- 例1.查询employees表中的员工总数
mysql>?SELECTCOUNT(*)?AS'员工总数'FROM?employees;
| 员工总数 ? ? |
+--------------+
| ? ? ? ? ? ?8 |
-- 例2.查询employees表中的员工总数,并按性别分组
mysql>?SELECT?gender,?COUNT(*)?AS'员工数'FROM?employees?GROUPBY?gender;
| gender | 员工数 ? ?|
+--------+-----------+
| 男 ? ? | ? ? ? ? 4 |
| 女 ? ? | ? ? ? ? 4 |
-- 例3.查询employees表中的男、女员工平均、最高、最低薪资
mysql>?SELECT?gender,?AVG(salary)?AS'平均薪资'FROM?employees?GROUPBY?gender;
| gender | 平均薪资 ? ? |
+--------+--------------+
| 男 ? ? | 11000.000000 |
| 女 ? ? | 13525.000000 |
mysql>?SELECT?gender,?MAX(salary)?AS'最高薪资'FROM?employees?GROUPBY?gender;
| gender | 最高薪资 ? ? |
+--------+--------------+
| 男 ? ? | ? ? 16000.00 |
| 女 ? ? | ? ? 15800.00 |
mysql>?SELECT?gender,?MIN(salary)?AS'最低薪资'FROM?employees?GROUPBY?gender;
| gender | 最低薪资 ? ? |
+--------+--------------+
| 男 ? ? | ? ? ?7500.00 |
| 女 ? ? | ? ? 12000.00 |
-- 例4.查询employees表中的员工总数,并按性别分组,条件为:薪资大于等于10000
mysql>?SELECT?gender,?COUNT(*)?AS'员工数'FROM?employees?WHERE?salary >=?10000GROUPBY?gender;
| gender | 员工数 ? ?|
+--------+-----------+
| 女 ? ? | ? ? ? ? 4 |
| 男 ? ? | ? ? ? ? 2 |
-- 例5.查询employees表中的各城市员工数,并且只显示员工数小于2的城市,这里特别注意,在HAVING后面使用的是别名,而不是聚合函数。
mysql>?SELECT?city,?COUNT(*)?AS'员工数'FROM?employees?GROUPBY?city?HAVING?员工数 <?2;
--- 其他示例,因为HAVING后面也直接使用聚合函数
mysql>?SELECT?city,?COUNT(*)?AS'员工数'FROM?employees?GROUPBY?city?HAVINGCOUNT(*) ?<?2;
| city ? | 员工数 ? ?|
+--------+-----------+
| 重庆 ? | ? ? ? ? 1 |
| 北京 ? | ? ? ? ? 1 |
| 上海 ? | ? ? ? ? 1 |
| 杭州 ? | ? ? ? ? 1 |
| 深圳 ? | ? ? ? ? 1 |
| 广州 ? | ? ? ? ? 1 |
-- 例6.查询入职时间在 2018~2019 年间,并根据工资地址分组,获取员工数量大于等于2的工作地址
SELECT?city,?COUNT(*)?AS'员工数'FROM?employees?WHERE?entry_date?BETWEEN'2018-01-01'AND'2019-12-31'GROUPBY?city?HAVING`员工数`?>=?2;
| city ? | 员工数 ? ?|
+--------+-----------+
| 成都 ? | ? ? ? ? 2 |
-- 例7.验证聚合函数对 NULL 值的处理, 由于吴十员工的 uid 为 NULL,因聚合函数会忽略 NULL ,所以 COUNT(uid) 只统计了 7 条数据(特别注意)。
SELECTCOUNT(uid)?FROM?employees;
| COUNT(uid) |
+------------+
| ? ? ? ? ?7 |
温馨提示:HAVING
?子句用于对分组后的结果进行过滤,它与?WHERE
?不同之处在于,WHERE
?在数据分组前进行条件过滤,而?HAVING
?则在数据分组后进行,因此 WHERE 不能对聚合函数进行判断,执行顺序:WHERE -> 聚合函数 -> HAVING
。
6.排序查询
描述:ORDER BY 子句用于对查询结果进行排序,可以按照一个或多个列的值进行升序(ASC)或降序(DESC)排列。默认情况下,如果没有指定 ASC 或 DESC,则默认为 ASC 升序排列。
-- # 语法
SELECT?字段名?FROM?表名 [WHERE?条件] [GROUPBY?分组依据列名] [HAVING?过滤条件]?ORDERBY?排序依据列名 [ASC|DESC];
-- # 示例
-- 例1.查询employees表中的所有员工信息,并按薪资降序排列
mysql>?SELECTname,salary?FROM?employees?ORDERBY?salary?DESC;
| name ? | salary ? |
+--------+----------+
| 王五 ? | 16000.00 |
| 李四 ? | 15800.00 |
| 孙八 ? | 13500.00 |
| 吴十 ? | 12800.00 |
| 赵六 ? | 12000.00 |
| 周九 ? | 11500.00 |
| 钱七 ? | ?9000.00 |
| 张三 ? | ?7500.00 |
-- 例2.查询员工表中薪资大于等于10000的员工信息,并按年龄降序,入职时间升序排列
mysql>?SELECTname,age,salary,entry_date?FROM?employees?WHERE?salary >=?10000ORDERBY?age?DESC,entry_date?ASC;
| name ? | age | salary ? | entry_date |
+--------+-----+----------+------------+
| 王五 ? | ?35 | 16000.00 | 2019-05-10 |
| 吴十 ? | ?31 | 12800.00 | 2019-09-18 |
| 孙八 ? | ?29 | 13500.00 | 2020-11-12 |
| 李四 ? | ?28 | 15800.00 | 2020-02-15 |
| 周九 ? | ?27 | 11500.00 | 2021-07-30 |
| 赵六 ? | ?26 | 12000.00 | 2021-03-20 |
7.分页查询
描述:limit 子句用于对查询结果进行分页,可以指定返回的记录数和起始位置。特别注意,起始索引从0开始,起始索引
=(查询页码-1)*每页显示记录数
,若查询的是第一页,则起始索引为0。
-- # 语法
SELECT?字段名?FROM?表名 [WHERE?条件] [GROUPBY?分组依据列名] [HAVING?过滤条件]?ORDERBY?排序依据列名 [ASC|DESC]?LIMIT?开始索引,每页显示记录数;
-- # 示例
-- 例1.查询employees表中的前5条记录
mysql>?SELECT?uid,name,gender,age,phone_number,id_card,entry_date?FROM?employees?LIMIT0,5;
| uid ?| name ? | gender | age | phone_number | id_card ? ? ? ? ? ?| entry_date |
+------+--------+--------+-----+--------------+--------------------+------------+
| 001 ?| 张三 ? | 男 ? ? | ?30 | 13800138000 ?| 500102199001010001 | 2020-01-01 |
| 002 ?| 李四 ? | 女 ? ? | ?28 | 13900139000 ?| 500102199201010002 | 2020-02-15 |
| 003 ?| 王五 ? | 男 ? ? | ?35 | 13700137000 ?| 500102198801010003 | 2019-05-10 |
| 004 ?| 赵六 ? | 女 ? ? | ?26 | 13600136000 ?| 500102199601010004 | 2021-03-20 |
| 005 ?| 钱七 ? | 男 ? ? | ?32 | 13500135000 ?| 500102199001010005 | 2018-08-05 |
-- 例2.查询employees表中的第6条到第10条记录,即第二页
mysql>?SELECT?uid,name,gender,age,phone_number,id_card,entry_date?FROM?employees?LIMIT5,5;
| uid ?| name ? | gender | age | phone_number | id_card ? ? ? ? ? ?| entry_date |
+------+--------+--------+-----+--------------+--------------------+------------+
| 006 ?| 孙八 ? | 女 ? ? | ?29 | 13400134000 ?| 500102199301010006 | 2020-11-12 |
| 007 ?| 周九 ? | 男 ? ? | ?27 | 13300133000 ?| 500102199501010007 | 2021-07-30 |
| NULL | 吴十 ? | 女 ? ? | ?31 | 13200132000 ?| 500102199101010008 | 2019-09-18 |
温馨提示:分页查询是数据库方言,不同的数据库管理系统(DBMS)在处理分页查询时可能会有所不同,例如 MySQL 使用?LIMIT
?关键字进行分页,而 MsSQL 使用?OFFSET
?和?FETCH NEXT
?关键字,Oracle 使用?ROWNUM
?关键字等,这一点需要注意。
总结
描述:上面小节中,主要介绍了 SQL 查询的基础语法,包括选择、过滤、分组、排序和分页等操作。这些基础操作是构建复杂 SQL 查询语句的基石,掌握它们对于进行有效的数据检索和分析至关重要,使用下图简单总结一下:
weiyigeek.top-DQL基础查询语句总结图
SQL 查询语子句执行顺序
描述:在编写基础 SQL 查询语句时,子句关键字执行顺序如下所示:
-
- 首先是
FROM
-
- ?子句,确定数据来源其次是
WHERE
-
- ?子句,对数据进行条件过滤再次是
GROUP
-
- ?BY 子句,对数据进行分组接着是
HAVING
-
- ?子句,对分组后的数据进行过滤之后是
SELECT
-
- ?子句,确定输出哪些列紧接着是
ORDER BY
-
- ?子句,对结果进行排序最终是
LIMIT
- ?子句,对结果进行分页
weiyigeek.top-DQL 语句及关键字执行顺序图
那如何来验证这个执行顺序呢?下面便跟随作者一起动手使用 SQL 语句来实践吧。
-- # 例如,查询employees表中年龄大于25的员工姓名和年龄,并按年龄降序排列
SELECTname,age?FROM?employees?WHERE?age >?25ORDERBY?age?DESC;?
| name ? | age |
+--------+-----+
| 王五 ? | ?35 |
| 钱七 ? | ?32 |
| 吴十 ? | ?31 |
| 张三 ? | ?30 |
| 孙八 ? | ?29 |
| 李四 ? | ?28 |
| 周九 ? | ?27 |
| 赵六 ? | ?26 |
-- # 验证 FROM 与 WHERE 子句的执行顺序,可以使用 age 表别名的方式来验证。
SELECTname,age?FROM?employees e?WHERE?e.age >?25ORDERBY?age?DESC; ?-- 执行成功的SQL语句,说明FROM子句先执行。
-- # 当然查询语句中没有分组、分组过滤字句时,将直接跳到 SELECT 中,同样验证 SELECT 子句的执行顺序,可以使用表列名来测试。
-- 执行成功的SQL语句,说明 SELECT 子句在 FROM 之后执行。
SELECT?e.name, e.age?FROM?employees e?WHERE?e.age >?25ORDERBY?age?DESC; ?
-- # 验证 ORDER BY 子句的执行顺序,使用 SELECT 指定的字段别名来测试。
-- 执行成功的SQL语句,说明 ORDER BY 子句在 SELECT 之后执行。
SELECT?e.name, e.age?AS'年龄'FROM?employees e?WHERE?e.age >?25ORDERBY?年龄?DESC;?
-- 报错:ERROR 1054 (42S22): Unknown column '年龄' in 'where clause' 由于在 WHERE 子句在 SELECT 之前就执行了,所以无法获取 SELECT 子句定义的别名。
SELECT?e.name, e.age?AS'年龄'FROM?employees e?WHERE?年龄 >?25ORDERBY?年龄?DESC;?
最后,通过实践上述示例,你可以更好地理解如何在不同的场景下应用这些基本查询技巧。