
4.2 单表查询
单表查询是指从一张表数据中查询所需的数据。本节将介绍单表查询中各种基本的查询方式,主要有查询所有字段、查询指定字段、查询指定记录、查询空值、多条件的查询、对查询结果进行排序等。
4.2.1 查询所有字段
1. 在SELECT语句中使用星号(*)通配符查询所有字段
SELECT查询记录最简单的形式是从一个表中检索所有记录,实现的方法是使用星号(*)通配符指定查找所有字段的名称。语法格式如下:
SELECT * FROM 表名;
【例4.1】从fruits表中检索所有字段的数据,SQL语句如下:

可以看到,使用星号(*)通配符时,将返回所有字段,各列按照定义表时的顺序显示。
2. 在SELECT语句中指定所有字段
下面介绍另外一种查询所有字段值的方法。根据前面SELECT语句的格式,SELECT关键字后面的字段名为将要查找的数据,因此可以将表中所有字段的名称跟在SELECT子句后面,如果忘记了字段名称,可以使用DESC命令查看表的结构。有时候,表中的字段可能比较多,不一定能记得所有字段的名称,因此该方法会很不方便,不建议使用。例如,查询fruits表中的所有数据,SQL语句也可以编写如下:
SELECT f_id, s_id ,f_name, f_price FROM fruits;
查询结果与【例4.1】相同。
提示
一般情况下,除非需要使用表中所有字段的数据,最好不要使用通配符'*'。使用通配符虽然可以节省输入查询语句的时间,但是获取不需要的字段数据通常会降低查询和所调用的应用程序的效率。通配符的优势是,当不知道所要的字段或列的名称时,可以通过通配符来获取它们。
4.2.2 查询指定字段
1. 查询单个字段
查询表中的某一个字段,语法格式为:
SELECT 字段名 FROM 表名;
【例4.2】查询fruits表中f_name字段中的所有水果名称,SQL语句如下:
SELECT f_name FROM fruits;
该语句使用SELECT声明从fruits表中获取名称为f_name字段中的所有水果名称,指定字段的名称紧跟在SELECT关键字之后,查询结果如下:

输出结果显示了fruits表中f_name字段中的所有数据。
2. 查询多个字段
使用SELECT声明,可以获取多个字段中的数据,只需要在关键字SELECT后面指定要查找的字段的名称,不同字段名称之间用逗号(,)分隔开,最后一个字段后面不需要加逗号,语法格式如下:
SELECT 字段名1,字段名2,…,字段名n FROM 表名;
【例4.3】例如,从fruits表中获取f_name和f_price两个字段中的所有数据,SQL语句如下:
SELECT f_name, f_price FROM fruits;
该语句使用SELECT声明从fruits表中获取名称为f_name和f_price两个字段中的所有水果名称和价格,两个字段之间用逗号分隔开,查询结果如下:

输出结果显示了fruits表中f_name和f_price两个字段中的所有数据。
提示
MySQL中的SQL语句是不区分字母大小写的,因此SELECT和select的作用是相同的,只是许多开发人员习惯将关键字大写、数据字段和数据表的名称小写。读者也应该养成一个良好的编程习惯,这样编写出来的代码更容易阅读和维护。
4.2.3 查询指定记录
数据库中包含了大量的数据,根据特殊要求,可能只需要查询数据表中的指定数据,即对数据进行筛选。在SELECT语句中,通过WHERE子句可以对数据进行筛选,语法格式为:
SELECT 字段名1,字段名2,…,字段名n FROM 表名 WHERE 查询条件
在WHERE子句中,查询条件就是筛选查询结果的条件,是条件判断表达式,其中会用到MySQL提供的比较运算符和逻辑运算符等,在第3章已经做了详细的介绍,表4.1中列出的是WHERE子句常用的一些运算符,更多运算符可以查阅第3章的相关内容。
表4.1 WHERE子句常用的运算符

【例4.4】查询价格为10.2元的水果的名称,SQL语句如下:
SELECT f_name, f_price FROM fruits WHERE f_price = 10.2;
该语句使用SELECT声明从fruits表中获取价格等于10.2的水果的名称。从查询结果可以看到,价格为10.2的水果的名称是blackberry,其他的均不满足查询条件:

本例采用了简单的相等比较运算符来筛选,查询指定字段f_price的值等于10.20的记录(即数据表中的行),并显示这条记录f_name和f_price字段中的值。
相等比较运算符还可以用来比较字符串,例如:
【例4.5】查找名称为'apple'的水果的价格,SQL语句如下:
SELECT f_name, f_price FROM fruits WHERE f_name = 'apple';
该语句使用SELECT声明从fruits表中获取名称为'apple'的水果的价格,从查询结果可以看到只有名称为'apple'的记录返回,其他的均不满足查询条件。

【例4.6】查询价格小于10的水果的名称,SQL语句如下:
SELECT f_name, f_price FROM fruits WHERE f_price < 10;
该语句使用SELECT声明从fruits表中获取价格低于10的水果名称,即f_price小于10的水果信息被返回,查询结果如下:

从查询结果中可以看到,所有记录的f_price字段的值均小于10.00元,而大于或等于10.00元的记录没有被返回。
4.2.4 带IN关键字的查询
IN操作符用来查询满足指定条件范围内的记录。使用IN操作符,将所有检索条件用括号括起来,检索条件之间用逗号分隔开,只要满足条件范围内的一个值即为匹配项。
【例4.7】查询所有s_id为101和102的记录,SQL语句如下:
SELECT s_id,f_name, f_price FROM fruits WHERE s_id IN (101,102) ORDER BY f_name;
查询结果如下:

相反,可以使用关键字NOT来检索不在条件范围内的记录。
【例4.8】查询所有s_id不等于101也不等于102的记录,SQL语句如下:
SELECT s_id,f_name, f_price FROM fruits WHERE s_id NOT IN (101,102) ORDER BY f_name;
查询结果如下:

可以看到,该语句在IN关键字前面加上了NOT关键字,这使得查询的结果与前面例4.7的查询结果正好相反。
4.2.5 带BETWEEN AND的范围查询
BETWEEN AND用来查询某个范围内的值,该运算符需要两个参数,即范围的开始值和结束值,如果数据表中记录的字段值满足指定的范围查询条件,则满足条件的这些记录被返回。
【例4.9】查询价格在2.00元到10.20元之间的水果之名称和价格,SQL语句如下:
SELECT f_name, f_price FROM fruits WHERE f_price BETWEEN 2.00 AND 10.20;
查询结果如下:

可以看到,返回结果包含了价格从2.00元到10.20元之间的水果的名称,并且范围的边界值10.20对应的记录也包括在返回结果中,即BETWEEN匹配范围中的所有值包括了范围的开始值和结束值。
BETWEEN AND运算符的前面可以加关键字NOT,表示指定范围之外的值,意思是如果数据表中记录的指定字段值不在指定的范围内,则这些记录被返回。
【例4.10】查询价格在2.00元到10.20元之外的水果之名称和价格,SQL语句如下:
SELECT f_name, f_price FROM fruits WHERE f_price NOT BETWEEN 2.00 AND 10.20;
查询结果如下:

从查询结果可以看到,返回的只有f_price字段大于10.20的记录。其实,f_price字段小于2.00的记录也满足查询条件。因此,如果表中有f_price字段小于2.00的记录,也应当作为查询结果返回。
4.2.6 带LIKE的字符匹配查询
在前面的查询操作中,讲述了如何查询多个字段的记录,如何进行比较查询或者是查询一个条件范围内的记录。如果要查找所有的包含字符“ge”的水果名称,该如何查找呢?简单的比较查询面对这种要求就行不通了,因为需要使用通配符进行匹配查找,通过创建查找模式对数据表中的数据进行比较。执行这种查询任务的关键字是LIKE。
通配符是SQL的WHERE条件子句中拥有特殊含义的一种字符,SQL语句支持多种通配符,可以和LIKE一起使用的通配符有'%'和'_'。
1. 百分号通配符'%',匹配零个或任意个数的字符
【例4.11】查找所有以'b'字母开头的水果名称,SQL语句如下:
SELECT f_id, f_name FROM fruits WHERE f_name LIKE 'b%';
查询结果如下:

该语句查询的结果返回所有以'b'开头的水果的id和name,其中的'%'就是告诉MySQL返回数据表所有以字母'b'开头的水果名称对应的记录,不管'b'后面有多少个字符。
在搜索匹配时通配符'%'可以放在不同位置,如例4.12。
【例4.12】在fruits表中,查询f_name中包含字母'g'的所有记录,SQL语句如下:
SELECT f_id, f_name FROM fruits WHERE f_name LIKE '%g%';
查询结果如下:

该语句查询水果名字字符串中包含字母'g'的所有记录,只要名字中有字符'g',不管前面或后面有多少个字符都满足这个查询的条件。
【例4.13】查询以'b'开头并以'y'结尾的水果的名称,SQL语句如下:
SELECT f_name FROM fruits WHERE f_name LIKE 'b%y';
查询结果如下:

通过以上查询结果可以看到,'%'用于匹配在指定的位置的任意数目的字符。
2. 下划线通配符'_',一次只能匹配任意一个字符
另一个非常有用的通配符是下划线通配符'_',该通配符的用法和'%'相同,区别是'%'可以匹配多个字符,而'_'只能匹配任意单个字符,如果要匹配多个字符,则需要使用相同个数的'_'。
【例4.14】在fruits表中,查询以字母'y'结尾且'y'前面只有4个字符的记录,SQL语句如下:
SELECT f_id, f_name FROM fruits WHERE f_name LIKE '____y';
查询结果如下:

从查询结果可以看到,以'y'结尾且前面只有4个字符的记录只有一条。其他记录的f_name字段也有以'y'结尾的,但它们总的字符串长度不是5(即'y'作为结尾字符之前没有正好4个字符),因此不在返回结果中。
4.2.7 查询空值
数据表创建的时候,设计者可以指定某字段中是否可以包含空值(NULL)。空值不同于0,也不同于空字符串。空值一般表示数据未知、不适用或将在以后添加数据。在SELECT语句中使用IS NULL子句,可以查询某字段内容为空的记录。
下面在数据库中创建数据表customers,该表中包含了本章中需要用到的数据。

为了演示需要插入数据,请读者执行以下语句。

【例4.15】查询customers表中c_email为空的记录的c_id、c_name和c_email字段值,SQL语句如下:
SELECT c_id, c_name, c_email FROM customers WHERE c_email IS NULL;
查询结果如下:

可以看到,查询结果显示出customers表中字段c_email的值为NULL的记录,满足查询条件。
与IS NULL相反的是NOT IS NULL,该关键字查找字段不为空的记录。
【例4.16】查询customers表中字段c_email的值不为空的记录的c_id、c_name和c_email字段值,SQL语句如下:
SELECT c_id, c_name, c_email FROM customers WHERE c_email IS NOT NULL;
查询结果如下:

可以看到,查询出来的记录的c_email字段都不为空值。
4.2.8 带AND的多条件查询
使用SELECT查询时,可以增加查询的限制条件,这样可以使查询的结果更加精确。MySQL在WHERE子句中使用AND操作符限定只有满足所有查询条件的记录才会被返回。可以使用AND连接两个甚至多个查询条件,也就是多个条件表达式之间用AND连接。
【例4.17】在fruits表中查询s_id = 101并且f_price大于等于5的水果的价格和名称,SQL语句如下:
SELECT f_id, f_price, f_name FROM fruits WHERE s_id = '101' AND f_price >=5;
查询结果如下:

从查询结果可以看出,这条语句列出了s_id = 101的水果供应商所有价格大于等于5元的水果的名称和具体价格。这条WHERE子句中的条件分为两部分,AND关键字指示MySQL返回同时满足两个条件所有的记录。如果是id=101的水果供应商提供的水果,但是价格<5,或者是id不等于'101'的水果供应商提供的水果(不管它的价格是多少),均不是要查询的结果。
提示
上述例子的WHERE子句中只包含了一个AND关键字,把两个筛选条件组合在一起。实际上可以添加多个AND筛选条件,增加条件的同时增加一个AND关键字即可。
【例4.18】在fruits表中查询s_id = 101或者102,且f_price大于5、f_name = 'apple'的水果的价格和名称,SQL语句如下:
SELECT f_id, f_price, f_name FROM fruits WHERE s_id IN('101', '102') AND f_price >= 5 AND f_name = 'apple';
查询结果如下:

可以看到,符合查询条件的记录只有一条。
4.2.9 带OR的多条件查询
与AND相反,在WHERE声明中使用OR运算符,表示查询的记录只需要满足其中一个条件即可返回。OR也可以连接两个甚至多个查询条件,也就是多个条件表达式之间用OR连接。
【例4.19】查询s_id=101或者s_id=102的水果供应商的f_price和f_name,SQL语句如下:
SELECT s_id,f_name, f_price FROM fruits WHERE s_id = 101 OR s_id = 102;
查询结果如下:

结果显示了s_id=101和s_id=102的水果供应商的水果之名称和价格。OR运算符告诉MySQL,检索的时候只需要满足其中的一个条件,不需要全部都满足。如果这里使用AND,就检索不到符合条件的数据。
也可以使用IN操作符实现与OR相同的功能,如例4.20所示。
【例4.20】查询s_id=101或者s_id=102的水果供应商的f_price和f_name,SQL语句如下:
SELECT s_id,f_name, f_price FROM fruits WHERE s_id IN(101,102);
查询结果如下:

在这里可以看到,在这两个例子中OR运算符和IN运算符的结果是一样的,它们可以实现相同的功能。不过使用IN操作符使得检索语句更加简洁明了,并且IN执行的速度要快于OR。更重要的是,使用IN操作符,可以执行更加复杂的嵌套查询(后面章节将会讲述)。
提示
OR可以和AND一起使用,但是在使用时要注意这两者的优先级;AND的优先级高于OR,因此会先对AND两边的操作数进行运算,运算结果再与OR中的操作数结合。
4.2.10 查询结果不重复
从前面的例子可以看到,SELECT查询返回所有匹配的记录或行。例如,查询fruits表中所有的s_id,其结果为:

可以看到查询结果返回了16条记录,其中有一些重复的s_id值,有时出于对数据分析的要求,需要消除重复的记录值。如何使查询结果没有重复呢?在SELECT语句中,可以使用DISTINCT关键字指示MySQL消除重复的记录值。语法格式为:
SELECT DISTINCT 字段名 FROM 表名;
【例4.21】查询fruits表中s_id字段的值,返回s_id字段值且不得重复,SQL语句如下:
SELECT DISTINCT s_id FROM fruits;
查询结果如下:

可以看到,这次查询结果只返回了7条记录的s_id值,且不再有重复的值,SELECT DISTINCT s_id告诉MySQL只返回不重复的s_id记录。
4.2.11 对查询结果排序
从前面的查询结果,读者会发现有些字段的值是没有任何顺序的。可以在SELECT语句中使用ORDER BY子句来对查询的结果排序。
1. 单字段排序
例如,查询f_name字段,查询结果如下:

可以看到,查询的结果并没有以一种特定的顺序显示,因为这些记录是按照插入到数据表中的顺序来显示的。
下面使用ORDER BY子句对指定的字段数据进行排序。
【例4.22】查询fruits表的f_name字段值,并对其进行排序,SQL语句如下:

该语句查询的结果和前面的语句相同,不同的是,通过指定ORDER BY子句,MySQL对查询的name字段中的数据按字母表的顺序进行了升序排列。
2. 多字段排序
有时,需要根据多个字段的值进行排序。比如,如果要显示一个学生列表,可能会有多个学生的姓氏是相同的,因此还需要根据学生的名字进行排序。对多个字段的数据进行排序,要将需要排序的字段名用逗号隔开。
【例4.23】查询fruits表中的f_name和f_price字段,先按f_name排序,再按f_price排序,SQL语句如下:
SELECT f_name, f_price FROM fruits ORDER BY f_name, f_price;
查询结果如下:

提示
在对多个字段进行排序时,只有当排序的第一个字段有相同的字段值时才会对第二个字段进行排序。如果第一个字段中所有值都是唯一的,将不再对第二个字段进行排序。
3. 指定排序方向
默认情况下,查询数据按字母升序进行排序(A~Z)。但数据的排序并不仅限于此,还可以使用ORDER BY对查询结果进行降序排序(Z~A),可通过关键字DESC来实现。下面的例子说明了如何进行降序排序。
【例4.24】查询fruits表中的f_name和f_price字段,对结果按f_price降序方式排序,SQL语句如下:
SELECT f_name, f_price FROM fruits ORDER BY f_price DESC;
查询结果如下:

提示
与DESC相反的是ASC(升序排序),将字段中的数据按字母表顺序进行升序排序。实际上,在排序的时候ASC是默认的排序方式,所以加不加都可以。
也可以对多个字段进行不同顺序的排序,如例4.25所示。
【例4.25】查询fruits表,先按f_price降序排序,再按f_name字段升序排序,SQL语句如下:
SELECT f_price, f_name FROM fruits ORDER BY f_price DESC, f_name;
查询结果如下:

从这个结果可以看出,DESC排序方式只作用到直接位于DESC这个关键词前面的字段上。
提示
DESC关键字只对其前面的字段进行降序排序,在这里只对f_price排序,而并没有对f_name进行排序,因此,f_price按降序排序,而f_name字段仍按升序排序。如果要对多个字段都进行降序排序,必须在每一字段的字段名后面加DESC关键字。
4.2.12 分组查询
分组查询是对查询的结果数据按照某个字段或多个字段进行分组。MySQL中使用GROUP BY关键字对数据进行分组,基本语法形式为:
[GROUP BY 字段] [HAVING <条件表达式>]
字段值为进行分组时所依据的字段名称;“HAVING <条件表达式>”指定满足表达式限定条件的查询结果将被显示出来。
1. 创建分组
GROUP BY关键字通常和集合函数一起使用,例如MAX()、MIN()、COUNT()、SUM()、AVG()。例如,要返回每个水果供应商提供的水果种类,这时就要在分组过程中用到COUNT()函数,把数据分为多个逻辑组,并对每个组进行集合计算。
【例4.26】根据s_id对fruits表中的数据进行分组,SQL语句如下:
SELECT s_id, COUNT(*) AS Total FROM fruits GROUP BY s_id;
查询结果如下:

查询结果显示,s_id表示供应商的ID,Total字段使用COUNT()函数计算得出,GROUP BY子句按照s_id排序并对数据分组,可以看到ID为101、102、105的供应商分别提供3种水果,ID为103、104、107的供应商分别提供2种水果,ID为106的供应商只提供1种水果。
如果要查看每个供应商提供的水果的种类名称,该怎么办呢?MySQL中可以在GROUP BY字节中使用GROUP_CONCAT()函数,将每个分组中各个字段的值显示出来。
【例4.27】根据s_id对fruits表中的数据进行分组,将每个供应商的水果名称显示出来,SQL语句如下:
SELECT s_id, GROUP_CONCAT(f_name) AS Names FROM fruits GROUP BY s_id;
查询结果如下:

从结果可以看到,GROUP_CONCAT()函数将每个分组中的名称显示出来了,其名称的个数与COUNT()函数计算出来的相同。
2. 使用HAVING筛选分组
GROUP BY可以和HAVING一起限定显示记录所需满足的条件,只有满足条件的分组才会被显示。
【例4.28】根据s_id对fruits表中的数据进行分组,并显示水果种类大于1的分组信息,SQL语句如下:
SELECT s_id, GROUP_CONCAT(f_name) AS Names FROM fruits GROUP BY s_id HAVING COUNT(f_name) > 1;
查询结果如下:

从结果可以看到,ID为101、102、103、104、105、107的供应商提供的水果种类大于1,满足HAVING子句条件,因此出现在返回结果中;而ID为106的供应商的水果种类等于1,不满足限定条件,因此不在返回结果中。
提示
HAVING关键字与WHERE关键字都是用来筛选数据的,两者有什么区别呢?其中的重点是,HAVING在数据分组之后进行筛选来选择分组,而WHERE在分组之前用来选择记录。另外,WHERE排除的记录不再包括在分组中。
3. 在GROUP BY子句中使用WITH ROLLUP
使用WITH ROLLUP关键字之后,在所有查询出的分组记录之后会增加一条记录,该记录计算查询出的所有记录的总和,即统计记录数量。
【例4.29】根据s_id对fruits表中的数据进行分组,并显示记录数量,SQL语句如下:
SELECT s_id, COUNT(*) AS Total FROM fruits GROUP BY s_id WITH ROLLUP;
查询结果如下:

从结果可以看到,通过GROUP BY分组之后,在显示结果的最后面新添加了一行,该行的Total字段的值正好是上面所有数值之和。
4. 多字段分组
使用GROUP BY可以对多个字段进行分组,GROUP BY关键字后面跟需要分组的字段,MySQL根据多个字段的值来进行层次分组,分组层次从左到右,即先按第1个字段分组,然后在第1个字段值相同的记录中,再根据第2个字段的值进行分组,以此类推。
【例4.30】根据s_id和f_name字段对fruits表中的数据进行分组,SQL语句如下:
mysql> SELECT * FROM fruits group by s_id,f_name;
查询结果如下:

从结果可以看到,查询记录先按照s_id进行分组,再对f_name字段按不同的取值进行分组。
5. GROUP BY和ORDER BY一起使用
某些情况下需要对分组进行排序。在前面的介绍中,ORDER BY用来对查询的记录排序,如果和GROUP BY一起使用,则可以完成对分组的排序。
为了演示效果,首先创建数据表,SQL语句如下:

然后插入演示数据,SQL语句如下:

【例4.31】查询订单价格大于100的订单号和总订单价格,SQL语句如下:

查询结果如下:

可以看到,返回的结果中orderTotal字段的总订单价格并没有按照一定的顺序显示,接下来使用ORDER BY关键字按总订单价格排序来显示结果,SQL语句如下:

查询结果如下:

从结果可以看到,GROUP BY子句按订单号对数据进行分组,SUM()函数便可以返回总的订单价格,HAVING子句对分组数据进行筛选,使得只返回总价格大于100的订单,最后使用ORDER BY子句排序输出。
提示
当使用ROLLUP时,不能同时使用ORDER BY子句对查询结果进行排序,即ROLLUP和ORDER BY是互相排斥的。
4.2.13 使用LIMIT限制查询结果的数量
SELECT返回所有匹配的记录(也称为行),有可能是数据表中所有的记录,如仅仅需要返回第一条记录或者前几条记录,可使用LIMIT关键字,基本语法格式如下:
LIMIT [位置偏移量,] 记录数或行数
第一个“位置偏移量”参数指示MySQL从哪一条记录开始显示,是一个可选参数,如果不指定“位置偏移量”,将会从数据表中的第一条记录开始(第一条记录的位置偏移量是0,第二条记录的位置偏移量是1,以此类推);第二个参数“记录数或行数”是指明要返回的记录条数。
【例4.32】显示fruits表查询结果的前4条记录,SQL语句如下:
SELECT * From fruits LIMIT 4;
查询结果如下:

从结果可以看到,该语句没有指定返回记录的“位置偏移量”参数,因此显示结果从第一条记录开始,“记录数或行数”参数为4,于是返回的结果为数据表中的前4条记录。
如果指定了返回记录的开始位置,那么返回结果为从“位置偏移量”参数指定的行开始,“记录数或行数”参数指定返回的记录条数。
【例4.33】在fruits表中,使用LIMIT子句,返回从第5个记录开始之后的3条记录,SQL语句如下:
SELECT * From fruits LIMIT 4, 3;
查询结果如下:

从结果可以看到,该语句指示MySQL返回从第5条记录开始之后的3条记录。第一个数字'4'表示从第5条记录开始(因为位置偏移量从0开始,第5条记录的位置偏移量为4),第二个数字3表示要返回的记录条数。
因此,带一个参数的LIMIT是指定从查询结果的首行开始,唯一的参数表示返回的记录条数,即“LIMIT n”与“LIMIT 0,n”等价。带两个参数的LIMIT可以从指定位置开始之后返回指定的记录条数。
返回第一行的记录时,位置偏移量是0。因此,“LIMIT 1, 1”将返回第二行的记录,而不是第一行的记录。
提示
MySQL 8.0中可以使用“LIMIT 4 OFFSET 3”,意思是返回从第5条记录开始之后的3条记录,和“LIMIT 4,3;”返回的结果相同。