MySQL 8.0——查询数据

查询数据

1、基本查询语句

MySQL从数据表中查询数据的基本语句为SELECT语句。SELECT语句的基本格式是:

sql 复制代码
select
	{* | <字段列表> }
	[
		from <表1>, <表2>...
		[where <表达式>
		[group by <group by definition>]
		[having <expression> [{<operator> <expression>}...]]
		[order by <order by definition>]
		[limit [<offset>,] <row count>]
		
select [字段1, 字段2,..., 字段n]
from [表或视图]
where [查询条件];

其中,各条子句的含义如下:

  • {* | <字段列表>}包含星号通配符和字段列表,表示查询的字段。其中,字段列表至少包含一个字段名称,如果要查询多个字段,多个字段之间用逗号隔开,最后一个字段后不加逗号。
  • FROM <表1>,<表2>...,表1和表2表示查询数据的来源,可以是单个或者多个。
  • WHERE子句是可选项,如果选择该项,将限定查询行必须满足的查询条件。
  • GROUP BY <字段>,该子句告诉MySQL如何显示查询出来的数据,并按照指定的字段分组。
  • [ORDER BY <字段>]​,该子句告诉MySQL按什么样的顺序显示查询出来的数据,可以进行的排序有升序(ASC)、降序(DESC)。
  • [LIMIT [<offset>,] <rowcount>]​,该子句告诉MySQL每次显示查询出来的数据条数。

下面以一个例子说明如何使用SELECT从单个表中获取数据。

首先定义数据表,输入语句如下:

sql 复制代码
create table fruits
(
	f_id char(10) not null,
	s_id int not null,
	f_name char(255) not null,
	f_price decimal(8, 2) not null,
	primary key(f_id)
);

为了演示如何使用SELECT语句,需要插入如下数据:

sql 复制代码
insert into fruits (f_id, s_id, f_name, f_price) values
('a1', 101, 'apple', 5.2),
('b1', 101, 'blackberry', 20.1),
('bs1', 102, 'orange', 11.2),
('bs2', 105, 'mellon', 8.2),
('t1', 102, 'banana', 10.3),
('t2', 102, 'grape', 5.3),
('o2', 103, 'coconut', 9.2),
('c0', 101, 'cherry', 3.2),
('a2', 103, 'apricot', 2.2),
('l2', 104, 'lemon', 6.4),
('b2', 104, 'berry', 7.6),
('m1', 106, 'mango', 15.7),
('m2', 105, 'xbabay', 2.6),
('t4', 107, 'xbababa', 3.6),
('m3', 105, 'xxtt', 11.6),
('b5', 107, 'xxxx', 3.6);

使用SELECT语句查询f_id字段的数据:

sql 复制代码
select f_id, f_name
from fruits;

+------+------------+
| f_id | f_name     |
+------+------------+
| a1   | apple      |
| a2   | apricot    |
| b1   | blackberry |
| b2   | berry      |
| b5   | xxxx       |
| bs1  | orange     |
| bs2  | mellon     |
| c0   | cherry     |
| l2   | lemon      |
| m1   | mango      |
| m2   | xbabay     |
| m3   | xxtt       |
| o2   | coconut    |
| t1   | banana     |
| t2   | grape      |
| t4   | xbababa    |
+------+------------+

该语句的执行过程是,SELECT语句决定了要查询的列值,在这里查询f_id和f_name两个字段的值,FROM子句指定了数据的来源,这里指定数据表fruits,因此返回结果为fruits表中f_id和f_name两个字段下所有的数据。其显示顺序为添加到表中的顺序。

2、单表查询

单表查询是指从一张表数据中查询所需的数据。本节将介绍单表查询中的各种基本的查询方式,主要有查询所有字段、查询指定字段、查询指定记录、查询空值、多条件的查询、对查询结果进行排序等。

2.1、查询所有字段

2.1.1、在SELECT语句中使用星号(*)通配符查询所有字段

SELECT查询记录最简单的形式是从一个表中检索所有记录,实现的方法是使用星号(*)通配符指定查找所有列的名称。语法格式如下:

sql 复制代码
SELECT * FROM 表名;

从fruits表中检索所有字段的数据,SQL语句如下:

sql 复制代码
select * from fruits;

+------+------+------------+---------+
| f_id | s_id | f_name     | f_price |
+------+------+------------+---------+
| a1   |  101 | apple      |    5.20 |
| a2   |  103 | apricot    |    2.20 |
| b1   |  101 | blackberry |   20.10 |
| b2   |  104 | berry      |    7.60 |
| b5   |  107 | xxxx       |    3.60 |
| bs1  |  102 | orange     |   11.20 |
| bs2  |  105 | mellon     |    8.20 |
| c0   |  101 | cherry     |    3.20 |
| l2   |  104 | lemon      |    6.40 |
| m1   |  106 | mango      |   15.70 |
| m2   |  105 | xbabay     |    2.60 |
| m3   |  105 | xxtt       |   11.60 |
| o2   |  103 | coconut    |    9.20 |
| t1   |  102 | banana     |   10.30 |
| t2   |  102 | grape      |    5.30 |
| t4   |  107 | xbababa    |    3.60 |
+------+------+------------+---------+

可以看到,使用星号(*)通配符时,将返回所有列,列按照定义表时候的顺序显示。

2.1.2、在SELECT语句中指定所有字段

下面介绍另外一种查询所有字段值的方法。根据前面SELECT语句的格式,SELECT关键字后面的字段名为将要查找的数据,因此可以将表中所有字段的名称跟在SELECT子句后面,如果忘记了字段名称,可以使用DESC命令查看表的结构。有时候,表中的字段可能比较多,不一定能记得所有字段的名称,因此该方法会很不方便,不建议使用。例如,查询fruits表中的所有数据,SQL语句也可以书写如下:

sql 复制代码
SELECT f_id, s_id ,f_name, f_price 
FROM fruits;

一般情况下,除非需要使用表中所有的字段数据,最好不要使用通配符'*'。使用通配符虽然可以节省输入查询语句的时间,但是获取不需要的列数据通常会降低查询和所使用的应用程序的效率。通配符的优势是,当不知道所需要的列的名称时,可以通过它获取它们。

2.2、查询指定字段

2.2.1、查询单个字段

查询表中的某一个字段,语法格式为:

sql 复制代码
SELECT 列名FROM 表名;

查询fruits表中f_name列所有的水果名称,SQL语句如下:

sql 复制代码
select f_name from fruits;

该语句使用SELECT声明从fruits表中获取名称为f_name字段下的所有水果名称,指定字段的名称紧跟在SELECT关键字之后,查询结果如下:

sql 复制代码
+------------+
| f_name     |
+------------+
| apple      |
| apricot    |
| blackberry |
| berry      |
| xxxx       |
| orange     |
| mellon     |
| cherry     |
| lemon      |
| mango      |
| xbabay     |
| xxtt       |
| coconut    |
| banana     |
| grape      |
| xbababa    |
+------------+

输出结果显示了fruits表中f_name字段下的所有数据。

2.2.2、查询多个字段

使用SELECT声明,可以获取多个字段下的数据,只需要在关键字SELECT后面指定要查找的字段的名称,不同字段名称之间用逗号(​,​)分隔开,最后一个字段后面不需要加逗号,语法格式如下:

sql 复制代码
SELECT 字段名1,字段名2,...,字段名n 
FROM 表名;

从fruits表中获取f_name和f_price两列,SQL语句如下:

sql 复制代码
select f_name, f_price
from fruits;

该语句使用SELECT声明从fruits表中获取名称为f_name和f_price两个字段下的所有水果名称和价格,两个字段之间用逗号分隔开,查询结果如下:

sql 复制代码
+------------+---------+
| f_name     | f_price |
+------------+---------+
| apple      |    5.20 |
| apricot    |    2.20 |
| blackberry |   20.10 |
| berry      |    7.60 |
| xxxx       |    3.60 |
| orange     |   11.20 |
| mellon     |    8.20 |
| cherry     |    3.20 |
| lemon      |    6.40 |
| mango      |   15.70 |
| xbabay     |    2.60 |
| xxtt       |   11.60 |
| coconut    |    9.20 |
| banana     |   10.30 |
| grape      |    5.30 |
| xbababa    |    3.60 |
+------------+---------+

输出结果显示了fruits表中f_name和f_price两个字段下的所有数据。

2.3、查询指定记录

数据库中包含大量的数据,根据特殊要求,可能只需要查询表中的指定数据,即对数据进行过滤。在SELECT语句中,通过WHERE子句可以对数据进行过滤,语法格式为:

sql 复制代码
SELECT 字段名1,字段名2,...,字段名n
FROM 表名
WHERE 查询条件

在WHERE子句中,MySQL提供了一系列的条件判断符,查询结果如表所示。

查询价格为10.2元的水果的名称,SQL语句如下:

sql 复制代码
select f_name, f_price
from fruits
where f_price = 10.3;

该语句使用SELECT声明从fruits表中获取价格等于10.3的水果的数据,从查询结果可以看到,价格是10.3的水果的名称是banana,其他的均不满足查询条件,查询结果如下:

sql 复制代码
+--------+---------+
| f_name | f_price |
+--------+---------+
| banana |   10.30 |
+--------+---------+

本例采用了简单的相等过滤,查询一个指定列f_price具有值10.20。

相等还可以用来比较字符串,下面给出一个例子:

查找名称为"apple"的水果的价格,SQL语句如下:

sql 复制代码
select f_name, f_price
from fruits
where f_name = 'apple';

+--------+---------+
| f_name | f_price |
+--------+---------+
| apple  |    5.20 |
+--------+---------+

该语句使用SELECT声明从fruits表中获取名称为"apple"的水果的价格,从查询结果可以看到只有名称为"apple"行被返回,其他的均不满足查询条件。

查询价格小于10的水果的名称,SQL语句如下:

sql 复制代码
SELECT f_name, f_price
FROM fruits
WHERE f_price < 10;

该语句使用SELECT声明从fruits表中获取价格低于10的水果名称,即f_price小于10的水果信息被返回,查询结果如下:

sql 复制代码
+---------+---------+
| f_name  | f_price |
+---------+---------+
| apple   |    5.20 |
| apricot |    2.20 |
| berry   |    7.60 |
| xxxx    |    3.60 |
| mellon  |    8.20 |
| cherry  |    3.20 |
| lemon   |    6.40 |
| xbabay  |    2.60 |
| coconut |    9.20 |
| grape   |    5.30 |
| xbababa |    3.60 |
+---------+---------+

可以看到查询结果中所有记录的f_price字段的值均小于10.00元,而大于等于10.00元的记录没有被返回。

2.4、带IN关键字的查询

IN操作符用来查询满足指定范围内的条件的记录,使用IN操作符,将所有检索条件用括号括起来,检索条件之间用逗号分隔开,只要满足条件范围内的一个值即为匹配项。

查询s_id为101和102的记录,SQL语句如下:

sql 复制代码
SELECT s_id,f_name, f_price
FROM fruits
WHERE s_id IN (101,102)
ORDER BY f_name;

+------+------------+---------+
| s_id | f_name     | f_price |
+------+------------+---------+
|  101 | apple      |    5.20 |
|  102 | banana     |   10.30 |
|  101 | blackberry |   20.10 |
|  101 | cherry     |    3.20 |
|  102 | grape      |    5.30 |
|  102 | orange     |   11.20 |
+------+------------+---------+

相反,可以使用关键字NOT来检索不在条件范围内的记录。

查询所有s_id不等于101也不等于102的记录,SQL语句如下:

sql 复制代码
SELECT s_id,f_name, f_price
FROM fruits
WHERE s_id NOT IN (101,102)
ORDER BY f_name;

+------+---------+---------+
| s_id | f_name  | f_price |
+------+---------+---------+
|  103 | apricot |    2.20 |
|  104 | berry   |    7.60 |
|  103 | coconut |    9.20 |
|  104 | lemon   |    6.40 |
|  106 | mango   |   15.70 |
|  105 | mellon  |    8.20 |
|  107 | xbababa |    3.60 |
|  105 | xbabay  |    2.60 |
|  105 | xxtt    |   11.60 |
|  107 | xxxx    |    3.60 |
+------+---------+---------+

可以看到,该语句在IN关键字前面加上了NOT关键字,这使得查询的结果与前面一个的结果正好相反,前面检索了s_id等于101和102的记录,而这里所要求查询的记录中s_id字段值不等于这两个值中的任何一个。

2.5、带BETWEEN AND的范围查询

BETWEEN AND用来查询某个范围内的值,该操作符需要两个参数,即范围的开始值和结束值,如果字段值满足指定的范围查询条件,则这些记录被返回。

查询价格在2.00元到10.20元之间的水果名称和价格,SQL语句如下:

sql 复制代码
SELECT f_name, f_price 
FROM fruits 
WHERE f_price BETWEEN 2.00 AND 10.20;

+---------+---------+
| f_name  | f_price |
+---------+---------+
| apple   |    5.20 |
| apricot |    2.20 |
| berry   |    7.60 |
| xxxx    |    3.60 |
| mellon  |    8.20 |
| cherry  |    3.20 |
| lemon   |    6.40 |
| xbabay  |    2.60 |
| coconut |    9.20 |
| grape   |    5.30 |
| xbababa |    3.60 |
+---------+---------+

可以看到,返回结果包含了价格从2.00元到10.20元之间的字段值,并且端点值10.20也包括在返回结果中,即BETWEEN匹配范围中的所有值,包括开始值和结束值。

BETWEEN AND操作符前可以加关键字NOT,表示指定范围之外的值,如果字段值不满足指定的范围内的值,则这些记录被返回。

查询价格在2.00元到10.20元之外的水果名称和价格,SQL语句如下:

sql 复制代码
SELECT f_name, f_price
FROM fruits
WHERE f_price NOT BETWEEN 2.00 AND 10.20;

+------------+---------+
| f_name     | f_price |
+------------+---------+
| blackberry |   20.10 |
| orange     |   11.20 |
| mango      |   15.70 |
| xxtt       |   11.60 |
| banana     |   10.30 |
+------------+---------+

由结果可以看到,返回的记录只有f_price字段大于10.20的,其实,f_price字段小于2.00的记录也满足查询条件。因此,如果表中有f_price字段小于2.00的记录,也应当作为查询结果。

2.6、带LIKE的字符匹配查询

在前面的检索操作中讲述了如何查询多个字段的记录,如何进行比较查询或者是查询一个条件范围内的记录,如果要查找所有包含字符"ge"的水果名称,该如何查找呢?简单的比较操作在这里已经行不通了,需要使用通配符进行匹配查找,通过创建查找模式对表中的数据进行比较。执行这个任务的关键字是LIKE。

通配符是一种在SQL的WHERE条件子句中拥有特殊意思的字符。SQL语句中支持多种通配符,可以和LIKE一起使用的通配符有'%'和'_'。

1.百分号通配符'%',匹配任意长度的字符,甚至包括零字符

查找所有以'b'字母开头的水果,SQL语句如下:

sql 复制代码
SELECT f_id, f_name
FROM fruits
WHERE f_name LIKE 'b%';

+------+------------+
| f_id | f_name     |
+------+------------+
| b1   | blackberry |
| b2   | berry      |
| t1   | banana     |
+------+------------+

该语句查询的结果返回所有以'b'开头的水果的id和name,'%'告诉MySQL,返回所有以字母'b'开头的记录,不管'b'后面有多少个字符。

在fruits表中,查询f_name中包含字母'g'的记录,SQL语句如下:

sql 复制代码
SELECT f_id, f_name
FROM fruits
WHERE f_name LIKE '%g%';

+------+--------+
| f_id | f_name |
+------+--------+
| bs1  | orange |
| m1   | mango  |
| t2   | grape  |
+------+--------+

该语句查询字符串中包含字母'g'的水果名称,只要名字中有字符'g',不管前面或后面有多少个字符,都满足查询的条件。

查询以'b'开头并以'y'结尾的水果的名称,SQL语句如下:

sql 复制代码
SELECT f_name
FROM fruits
WHERE f_name LIKE 'b%y';

+------------+
| f_name     |
+------------+
| blackberry |
| berry      |
+------------+

通过以上查询结果可以看到,'%'用于匹配在指定位置的任意数目的字符。

2. 下划线通配符'_',一次只能匹配任意一个字符

另一个非常有用的通配符是下划线通配符''。该通配符的用法和'%'相同,区别是'%'可以匹配多个字符,而''只能匹配任意单个字符。如果要匹配多个字符,则需要使用相同个数的'_'。

在fruits表中,查询以字母'y'结尾,且'y'前面只有4个字母的记录,SQL语句如下:

sql 复制代码
SELECT f_id, f_name 
FROM fruits 
WHERE f_name LIKE '____y';

+------+--------+
| f_id | f_name |
+------+--------+
| b2   | berry  |
+------+--------+

从结果可以看到,以'y'结尾且前面只有4个字母的记录只有一条。其他记录的f_name字段也有以'y'结尾的,但其总的字符串长度不为5,因此不在返回结果中。

2.7、查询空值

数据表创建的时候,设计者可以指定某列中是否包含空值(NULL)​。空值不同于0,也不同于空字符串。空值一般表示数据未知、不适用或将在以后添加数据。在SELECT语句中使用IS NULL子句,可以查询某字段内容为空记录。

下面在数据库中创建数据表customers,该表中包含了本章中需要用到的数据。

sql 复制代码
create table customers
(
	c_id int not null auto_increment,
	c_name char(50) not null,
	c_address char(50) null,
	c_city char(50) null,
	c_zip char(10) null,
	c_contact char(50) null,
	c_email char(255) null,
	primary key (c_id)
);

insert into customers(c_id, c_name, c_address, c_city, c_zip, c_contact, c_email) values
(10001, 'RedHook', '200 Street ', 'Tianjin', '300000', 'LiMing', 'LMing@163.com'),
(10002, 'Starts', '333 Fromage Lane', 'Dalian', '116000', 'Zhangbo', 'Jerry@hotmail.com'),
(10003, 'Netbhood', '1 Sunny Place', 'Qingdao', '266000', 'LuoCong', null),
(10004, 'JOTO', '829 Riverside Drive', 'Haikou', '570000', 'YangShan', 'sam@hotmail.com');

查询customers表中c_email为空的记录的c_id、c_name和c_email字段值,SQL语句如下:

sql 复制代码
SELECT c_id, c_name,c_email 
FROM customers 
WHERE c_email IS NULL;

+-------+----------+---------+
|  c_id | c_name   | c_email |
+-------+----------+---------+
| 10003 | Netbhood | <null>  |
+-------+----------+---------+

可以看到,显示customers表中字段c_email的值为NULL的记录,满足查询条件。

与IS NULL相反的是NOT NULL,该关键字查找字段不为空的记录。

查询customers表中c_email不为空的记录的c_id、c_name和c_email字段值,SQL语句如下:

sql 复制代码
SELECT c_id, c_name,c_email 
FROM customers 
WHERE c_email IS NOT NULL;

+-------+---------+-------------------+
|  c_id | c_name  | c_email           |
+-------+---------+-------------------+
| 10001 | RedHook | LMing@163.com     |
| 10002 | Starts  | Jerry@hotmail.com |
| 10004 | JOTO    | sam@hotmail.com   |
+-------+---------+-------------------+

可以看到,查询出来的记录的c_email字段都不为空值。

2.8、带AND的多条件查询

使用SELECT查询时,可以增加查询的限制条件,这样可以使查询的结果更加精确。MySQL在WHERE子句中使用AND操作符限定只有满足所有查询条件的记录才会被返回。可以使用AND连接两个甚至多个查询条件,多个条件表达式之间用AND分开。

在fruits表中查询s_id = 101并且f_price大于等于5的水果id、价格和名称,SQL语句如下:

sql 复制代码
SELECT f_id, f_price, f_name 
FROM fruits 
WHERE s_id = '101' AND f_price >=5;

+------+---------+------------+
| f_id | f_price | f_name     |
+------+---------+------------+
| a1   |    5.20 | apple      |
| b1   |   20.10 | blackberry |
+------+---------+------------+

前面的语句检索了s_id=101的水果供应商所有价格大于等于5元的水果名称和价格。WHERE子句中的条件分为两部分,AND关键字指示MySQL返回所有同时满足两个条件的行。id=101的水果供应商提供的水果如果价格小于5,或者是id不等于'101'的水果供应商里的水果(不管其价格为多少)​,均不是要查询的结果。

上述例子的WHERE子句中只包含了一个AND语句,把两个过滤条件组合在一起。实际上可以添加多个AND过滤条件,增加条件的同时增加一个AND关键字。

在fruits表中查询s_id = 101或者102,并且f_price大于等于5、f_name='apple'的水果价格和名称,SQL语句如下:

sql 复制代码
SELECT f_id, f_price, f_name 
FROM fruits
WHERE s_id IN('101', '102') 
AND f_price >= 5 
AND f_name = 'apple';

+------+---------+--------+
| f_id | f_price | f_name |
+------+---------+--------+
| a1   |    5.20 | apple  |
+------+---------+--------+

可以看到,符合查询条件的返回记录只有一条。

2.9、带OR的多条件查询

与AND相反,在WHERE声明中使用OR操作符,表示只需要满足其中一个条件的记录即可返回。OR也可以连接两个甚至多个查询条件,多个条件表达式之间用OR分开。

查询s_id=101或者s_id=102的水果供应商的f_price和f_name,SQL语句如下:

sql 复制代码
SELECT s_id,f_name, f_price 
FROM fruits 
WHERE s_id = 101 OR s_id = 102;

+------+------------+---------+
| s_id | f_name     | f_price |
+------+------------+---------+
|  101 | apple      |    5.20 |
|  101 | blackberry |   20.10 |
|  102 | orange     |   11.20 |
|  101 | cherry     |    3.20 |
|  102 | banana     |   10.30 |
|  102 | grape      |    5.30 |
+------+------------+---------+

结果显示了s_id=101和s_id=102的水果供应商的水果名称和价格,OR操作符告诉MySQL检索的时候只需要满足其中的一个条件,不需要全部都满足。如果这里使用AND的话,将检索不到符合条件的数据。

在这里,也可以使用IN操作符实现与OR相同的功能,下面的例子可进行说明。

查询s_id=101或者s_id=102的水果供应商的f_price和f_name,SQL语句如下:

sql 复制代码
SELECT s_id,f_name, f_price 
FROM fruits 
WHERE s_id IN(101,102);

+------+------------+---------+
| s_id | f_name     | f_price |
+------+------------+---------+
|  101 | apple      |    5.20 |
|  101 | blackberry |   20.10 |
|  102 | orange     |   11.20 |
|  101 | cherry     |    3.20 |
|  102 | banana     |   10.30 |
|  102 | grape      |    5.30 |
+------+------------+---------+

在这里可以看到,OR操作符和IN操作符使用后的结果是一样的,它们可以实现相同的功能,但是使用IN操作符使得检索语句更加简洁明了,并且IN执行的速度要快于OR。更重要的是,使用IN操作符可以执行更加复杂的嵌套查询(后面章节将会讲述)​。

OR可以和AND一起使用,但是在使用时要注意两者的优先级,由于AND的优先级高于OR,因此先对AND两边的操作数进行操作,再与OR中的操作数结合。

2.10、查询结果不重复

在SELECT语句中,可以使用DISTINCT关键字指示MySQL消除重复的记录值。语法格式为:

sql 复制代码
SELECT DISTINCT 字段名 FROM 表名;

查询fruits表中s_id字段的值,返回s_id字段值且不得重复,SQL语句如下:

sql 复制代码
SELECT DISTINCT s_id 
FROM fruits;

+------+
| s_id |
+------+
|  101 |
|  103 |
|  104 |
|  107 |
|  102 |
|  105 |
|  106 |
+------+

可以看到,这次查询结果只返回了7条记录的s_id值,且不再有重复的值,SELECTDISTINCT s_id告诉MySQL只返回不同的s_id行。

2.11、对查询结果排序

从前面的查询结果,读者会发现有些字段的值是没有任何顺序的,MySQL可以通过在SELECT语句中使用ORDER BY子句对查询的结果进行排序。

** 1.单列排序**

查询fruits表的f_name字段值,并对其进行排序,SQL语句如下:

sql 复制代码
select f_name
from fruits
order by f_name;

+------------+
| f_name     |
+------------+
| apple      |
| apricot    |
| banana     |
| berry      |
| blackberry |
| cherry     |
| coconut    |
| grape      |
| lemon      |
| mango      |
| mellon     |
| orange     |
| xbababa    |
| xbabay     |
| xxtt       |
| xxxx       |
+------------+

该语句查询的结果和前面的语句相同,不同的是,通过指定ORDER BY子句,MySQL对查询的name列的数据按字母表的顺序进行了升序排列。

2.多列排序

有时,需要根据多列值进行排序。比如,如果要显示一个学生列表,可能会有多个学生的姓氏是相同的,因此还需要根据学生的名进行排序。对多列数据进行排序,要将需要排序的列之间用逗号隔开。

`查询fruits表中的f_name和f_price字段,先按f_name排序,再按f_price排序,SQL语句如下:

sql 复制代码
SELECT f_name, f_price 
FROM fruits 
ORDER BY f_name, f_price;

+------------+---------+
| f_name     | f_price |
+------------+---------+
| apple      |    5.20 |
| apricot    |    2.20 |
| banana     |   10.30 |
| berry      |    7.60 |
| blackberry |   20.10 |
| cherry     |    3.20 |
| coconut    |    9.20 |
| grape      |    5.30 |
| lemon      |    6.40 |
| mango      |   15.70 |
| mellon     |    8.20 |
| orange     |   11.20 |
| xbababa    |    3.60 |
| xbabay     |    2.60 |
| xxtt       |   11.60 |
| xxxx       |    3.60 |
+------------+---------+

在对多列进行排序的时候,首先排序的第一列必须有相同的列值,才会对第二列进行排序。如果第一列数据中所有值都是唯一的,将不再对第二列进行排序。

3.指定排序方向

默认情况下,查询数据按字母升序进行排序(A~Z)​,但数据的排序并不仅限于此,还可以使用ORDER BY对查询结果进行降序排序(Z~A)​。这可以通过关键字DESC实现,下面的例子表明了如何进行降序排列。

查询fruits表中的f_name和f_price字段,对结果按f_price降序方式排序,SQL语句如下:

sql 复制代码
SELECT f_name, f_price 
FROM fruits 
ORDER BY f_price DESC;

+------------+---------+
| f_name     | f_price |
+------------+---------+
| blackberry |   20.10 |
| mango      |   15.70 |
| xxtt       |   11.60 |
| orange     |   11.20 |
| banana     |   10.30 |
| coconut    |    9.20 |
| mellon     |    8.20 |
| berry      |    7.60 |
| lemon      |    6.40 |
| grape      |    5.30 |
| apple      |    5.20 |
| xxxx       |    3.60 |
| xbababa    |    3.60 |
| cherry     |    3.20 |
| xbabay     |    2.60 |
| apricot    |    2.20 |
+------------+---------+

与DESC相反的是ASC(升序)​,将字段列中的数据按字母表顺序升序排列。实际上,在排序的时候ASC是默认的排序方式,所以加不加都可以。

查询fruits表,先按f_price降序排列,再按f_name字段升序排列,SQL语句如下:

sql 复制代码
SELECT f_price, f_name 
FROM fruits 
ORDER BY f_price DESC, f_name;

+---------+------------+
| f_price | f_name     |
+---------+------------+
|   20.10 | blackberry |
|   15.70 | mango      |
|   11.60 | xxtt       |
|   11.20 | orange     |
|   10.30 | banana     |
|    9.20 | coconut    |
|    8.20 | mellon     |
|    7.60 | berry      |
|    6.40 | lemon      |
|    5.30 | grape      |
|    5.20 | apple      |
|    3.60 | xbababa    |
|    3.60 | xxxx       |
|    3.20 | cherry     |
|    2.60 | xbabay     |
|    2.20 | apricot    |
+---------+------------+

DESC排序方式只应用到直接位于其前面的字段上,由结果可以看出。

DESC关键字只对其前面的列进行降序排列,在这里只对f_price排序,而并没有对f_name进行排序,因此,f_price按降序排列,而f_name列仍按升序排列。如果要对多列都进行降序排列,必须要在每一列的列名后面加DESC关键字。

2.12、分组查询

分组查询是对数据按照某个或多个字段进行分组。MySQL中使用GROUP BY关键字对数据进行分组,基本语法形式为:

sql 复制代码
[GROUP BY 字段] [HAVING <条件表达式>]

字段值为进行分组时所依据的列名称;​"HAVING <条件表达式>"指定满足表达式限定条件的结果将被显示。

2.12.1、创建分组

GROUP BY关键字通常和集合函数一起使用,比如MAX()、MIN()、COUNT()、SUM()、AVG()。例如,要返回每个水果供应商提供的水果种类,这时就要在分组过程中用到COUNT()函数,把数据分为多个逻辑组,并对每个组进行集合计算。

根据s_id对fruits表中的数据进行分组,SQL语句如下:

sql 复制代码
SELECT s_id, COUNT(*) AS Total 
FROM fruits 
GROUP BY s_id;

+------+-------+
| s_id | Total |
+------+-------+
|  101 |     3 |
|  103 |     2 |
|  104 |     2 |
|  107 |     2 |
|  102 |     3 |
|  105 |     3 |
|  106 |     1 |
+------+-------+

查询结果显示,s_id表示供应商的ID,Total字段使用COUNT()函数计算得出,GROUPBY子句按照s_id排序并对数据分组,可以看到ID为101、102、105的供应商分别提供3种水果,ID为103、104、107的供应商分别提供2种水果,ID为106的供应商只提供1种水果。

如果要查看每个供应商提供的水果的种类名称,该怎么办呢?在MySQL中,可以在GROUP BY子句中使用GROUP_CONCAT()函数,将每个分组中各个字段的值显示出来。

根据s_id对fruits表中的数据进行 分组,将每个供应商的水果名称显示出来,SQL语句如下:

sql 复制代码
SELECT s_id, GROUP_CONCAT(f_name) AS Names 
FROM fruits 
GROUP BY s_id;

+------+-------------------------+
| s_id | Names                   |
+------+-------------------------+
|  101 | apple,blackberry,cherry |
|  102 | orange,banana,grape     |
|  103 | apricot,coconut         |
|  104 | berry,lemon             |
|  105 | mellon,xbabay,xxtt      |
|  106 | mango                   |
|  107 | xxxx,xbababa            |
+------+-------------------------+

由结果可以看到,GROUP_CONCAT()函数将每个分组中的名称显示出来了,其名称的个数与COUNT()函数计算出来的相同。

2.12.2、使用HAVING过滤分组

GROUP BY可以和HAVING一起限定显示记录所需满足的条件,只有满足条件的分组才会被显示。

根据s_id对fruits表中的数据进行分组,并显示水果种类大于1的分组信息,SQL语句如下:

sql 复制代码
SELECT s_id, GROUP_CONCAT(f_name) AS Names
FROM fruits
GROUP BY s_id HAVING COUNT(f_name) > 1;

+------+-------------------------+
| s_id | Names                   |
+------+-------------------------+
|  101 | apple,blackberry,cherry |
|  102 | orange,banana,grape     |
|  103 | apricot,coconut         |
|  104 | berry,lemon             |
|  105 | mellon,xbabay,xxtt      |
|  107 | xxxx,xbababa            |
+------+-------------------------+

由结果可以看到,ID为101、102、103、104、105、107的供应商提供的水果种类大于1,满足HAVING子句条件,因此出现在返回结果中;而ID为106的供应商的水果种类等于1,不满足限定条件,因此不在返回结果中。

2.12.3、在GROUP BY子句中使用WITH ROLLUP

使用WITH ROLLUP关键字之后,在所有查询出的分组记录之后增加一条记录,该记录计算查询出的所有记录的总和,即统计记录数量。

根据s_id对fruits表中的数据进行分组,并显示记录数量,SQL语句如下:

sql 复制代码
select s_id, count(*) as Total
from fruits
group by s_id with rollup;

+--------+-------+
|   s_id | Total |
+--------+-------+
|    101 |     3 |
|    102 |     3 |
|    103 |     2 |
|    104 |     2 |
|    105 |     3 |
|    106 |     1 |
|    107 |     2 |
| <null> |    16 |
+--------+-------+

由结果可以看到,通过GROUP BY分组之后,在显示结果的最后面新添加了一行,该行Total列的值正好是上面所有数值之和。

2.12.4、多字段分组

使用GROUP BY可以对多个字段进行分组,GROUP BY关键字后面跟需要分组的字段,MySQL根据多字段的值来进行层次分组,分组层次从左到右,即先按第1个字段分组,然后在第1个字段值相同的记录中再根据第2个字段的值进行分组,以此类推。

根据s_id和f_name字段对fruits表中的数据进行分组,SQL语句如下:

sql 复制代码
SELECT s_id, f_name
FROM fruits 
group by s_id,f_name;
2.12.5、GROUP BY和ORDER BY一起使用

某些情况下需要对分组进行排序,在前面的介绍中,ORDER BY用来对查询的记录排序,如果和GROUP BY一起使用可以完成对分组的排序。

为了演示效果,首先创建数据表,SQL语句如下:

sql 复制代码
create table orderitems
(
	o_num int not null,
	o_item int not null,
	f_id char(10) not null,
	quantity int not null,
	item_price decimal(8, 2) not null,
	primary key (o_num, o_item)
);

insert into orderitems(o_num, o_item, f_id, quantity, item_price) values
(30001, 1, 'a1', 10, 5.2),
(30001, 3, 'bs1', 5, 11.2),
(30001, 4, 'bs2', 15, 9.2),
(30002, 1, 'b3', 2, 20.0),
(30003, 1, 'c0', 100, 10),
(30004, 1, 'o2', 50, 2.50),
(30005, 1, 'co', 5, 10),
(30005, 2, 'b1', 10, 8.99),
(30005, 3, 'a2', 10, 2.2),
(30005, 4, 'm1', 5, 14.99);

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

sql 复制代码
select o_num, sum(quantity * item_price) as orderTotal
from orderitems
group by o_num
having sum(quantity * item_price) >= 100;

+-------+------------+
| o_num | orderTotal |
+-------+------------+
| 30001 |     246.00 |
| 30003 |    1000.00 |
| 30004 |     125.00 |
| 30005 |     236.85 |
+-------+------------+

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

sql 复制代码
select o_num, sum(quantity * item_price) as orderTotal
from orderitems
group by o_num
having sum(quantity * item_price) >= 100
order by orderTotal;

+-------+------------+
| o_num | orderTotal |
+-------+------------+
| 30004 |     125.00 |
| 30005 |     236.85 |
| 30001 |     246.00 |
| 30003 |    1000.00 |
+-------+------------+

由结果可以看到,GROUP BY子句按订单号对数据进行分组,SUM()函数便可以返回总的订单价格,HAVING子句对分组数据进行过滤,使得只返回总价格大于100的订单,最后使用ORDER BY子句排序输出。

当使用ROLLUP时,不能同时使用ORDER BY子句进行结果排序,即ROLLUP和ORDER BY是互相排斥的。

2.13、使用LIMIT限制查询结果的数量

SELECT返回所有匹配的行,有可能是表中所有的行,若仅仅需要返回第一行或者前几行,可使用LIMIT关键字,基本语法格式如下:

sql 复制代码
LIMIT [位置偏移量,] 行数
  • 第一个"位置偏移量"参数指示MySQL从哪一行开始显示,是一个可选参数,如果不指定"位置偏移量",将会从表中的第一条记录开始(第一条记录的位置偏移量是0,第二条记录的位置偏移量是1,以此类推);
  • 第二个参数"行数"指示返回的记录条数。

显示fruits表查询结果的前4行,SQL语句如下:

sql 复制代码
SELECT * From fruits LIMIT 4;

+------+------+------------+---------+
| f_id | s_id | f_name     | f_price |
+------+------+------------+---------+
| a1   |  101 | apple      |    5.20 |
| a2   |  103 | apricot    |    2.20 |
| b1   |  101 | blackberry |   20.10 |
| b2   |  104 | berry      |    7.60 |
+------+------+------------+---------+

由结果可以看到,该语句没有指定返回记录的"位置偏移量"参数,显示结果从第一行开始,​"行数"参数为4,因此返回的结果为表中的前4行记录。

如果指定返回记录的开始位置,那么返回结果为从"位置偏移量"参数开始的指定行数,​"行数"参数指定返回的记录条数。

在fruits表中,使用LIMIT子句,返回从第5个记录开始的行数长度为3的记录,SQL语句如下:

sql 复制代码
SELECT * From fruits LIMIT 4, 3;

+------+------+--------+---------+
| f_id | s_id | f_name | f_price |
+------+------+--------+---------+
| b5   |  107 | xxxx   |    3.60 |
| bs1  |  102 | orange |   11.20 |
| bs2  |  105 | mellon |    8.20 |
+------+------+--------+---------+

由结果可以看到,该语句指示MySQL返回从第5条记录行开始之后的3条记录。第一个数字'4'表示从第5行开始(位置偏移量从0开始,第5行的位置偏移量为4)​,第二个数字3表示返回的行数。

所以,带一个参数的LIMIT指定从查询结果的首行开始,唯一的参数表示返回的行数,即"LIMIT n"与"LIMIT 0,n"等价。带两个参数的LIMIT可以返回从任何一个位置开始的指定的行数。

返回第一行时,位置偏移量是0。因此,​"LIMIT 1, 1"将返回第二行,而不是第一行。

3、使用集合函数查询

3.1、COUNT()函数

COUNT()函数统计数据表中包含的记录行的总数,或者根据查询结果返回列中包含的数据行数。其使用方法有两种:

  • COUNT(*)计算表中总的行数,不管某列是否有数值或者为空值。
  • COUNT(字段名)计算指定列下总的行数,计算时将忽略空值的行。

查询customers表中总的行数,SQL语句如下:

sql 复制代码
select count(*) as cust_num 
from customers;

+----------+
| cust_num |
+----------+
|        4 |
+----------+

由查询结果可以看到,COUNT(*)返回customers表中记录的总行数,不管其值是什么,返回的总数的名称为cust_num。

查询customers表中有电子邮箱的顾客的总数,SQL语句如下:

sql 复制代码
select count(c_email) as email_num
from customers;

+-----------+
| email_num |
+-----------+
|         3 |
+-----------+

由查询结果可以看到,表中5个customer只有3个有email,customer的email为空值NULL的记录没有被COUNT()函数计算。

两个例子中不同的数值说明了两种方式在计算总数的时候对待NULL值的方式不同:指定列的值为空的行被COUNT()函数忽略;如果不指定列,而在COUNT()函数中使用星号"*"​,则所有记录都不忽略。

前面介绍分组查询的时候,介绍了如何用COUNT()函数与GROUP BY关键字一起来计算不同分组中的记录总数。

在orderitems表中,使用COUNT()函数统计不同订单号中订购的水果种类,SQL语句如下:

sql 复制代码
select o_num, count(f_id)
from orderitems
group by o_num;

+-------+-------------+
| o_num | count(f_id) |
+-------+-------------+
| 30001 |           3 |
| 30002 |           1 |
| 30003 |           1 |
| 30004 |           1 |
| 30005 |           4 |
+-------+-------------+

从查询结果可以看到,GROUP BY关键字先按照订单号进行分组,然后计算每个分组中的总记录数。

3.2、SUM()函数

SUM()是一个求总和的函数,返回指定列值的总和。

在orderitems表中查询30005号订单一共购买的水果总量,SQL语句如下:

sql 复制代码
select sum(quantity) as items_total
from orderitems
where o_num = 30005;

+-------------+
| items_total |
+-------------+
|          30 |
+-------------+

由查询结果可以看到,SUM(quantity)函数返回订单中所有水果数量之和,WHERE子句指定查询的订单号为30005。

SUM()可以与GROUP BY一起使用,来计算每个分组的总和。

在orderitems表中,使用SUM()函数统计不同订单号中订购的水果总量,SQL语句如下:

sql 复制代码
select o_num, sum(quantity) as items_total
from orderitems
group by o_num;

+-------+-------------+
| o_num | items_total |
+-------+-------------+
| 30001 |          30 |
| 30002 |           2 |
| 30003 |         100 |
| 30004 |          50 |
| 30005 |          30 |
+-------+-------------+

由查询结果可以看到,GROUP BY按照订单号o_num进行分组,SUM()函数计算每个分组中订购的水果的总量。

SUM()函数在计算时,忽略列值为NULL的行。

3.3、AVG()函数

AVG()函数通过计算返回的行数和每一行数据的和,求得指定列数据的平均值。

在fruits表中,查询s_id=103的供应商的水果价格的平均值,SQL语句如下:

sql 复制代码
select avg(f_price) as avg_price
from fruits
where s_id = 103;

+-----------+
| avg_price |
+-----------+
|  5.700000 |
+-----------+

该例中,查询语句增加了一个WHERE子句,并且添加了查询过滤条件,只查询s_id = 103的记录中的f_price。因此,通过AVG()函数计算的结果只是指定的供应商水果的价格平均值,而不是市场上所有水果价格的平均值。

AVG()可以与GROUP BY一起使用,来计算每个分组的平均值。

在fruits表中,查询每一个供应商的水果价格的平均值,SQL语句如下:

sql 复制代码
select s_id, avg(f_price) as avg_price
from fruits 
group by s_id;

+------+-----------+
| s_id | avg_price |
+------+-----------+
|  101 |  9.500000 |
|  103 |  5.700000 |
|  104 |  7.000000 |
|  107 |  3.600000 |
|  102 |  8.933333 |
|  105 |  7.466667 |
|  106 | 15.700000 |
+------+-----------+

GROUP BY关键字根据s_id字段对记录进行分组,然后计算出每个分组的平均值,这种分组求平均值的方法非常有用,例如求不同班级学生成绩的平均值、求不同部门工人的平均工资、求各地的年平均气温等。

AVG()函数使用时,其参数为要计算的列名称,如果要得到多个列的多个平均值,则需要在每一列上使用AVG()函数。

3.4、MAX()函数

MAX()返回指定列中的最大值。

在fruits表中查找市场上价格最高的水果值,SQL语句如下:

sql 复制代码
select max(f_price) as max_price
from fruits;

+-----------+
| max_price |
+-----------+
|     20.10 |
+-----------+

由结果可以看到,MAX()函数查询出了f_price字段的最大值15.70。

MAX()也可以和GROUP BY关键字一起使用,求每个分组中的最大值。

在fruits表中查找不同供应商提供的价格最高的水果值,SQL语句如下:

sql 复制代码
select s_id, max(f_price) as max_price
from fruits 
group by s_id;

+------+-----------+
| s_id | max_price |
+------+-----------+
|  101 |     20.10 |
|  103 |      9.20 |
|  104 |      7.60 |
|  107 |      3.60 |
|  102 |     11.20 |
|  105 |     11.60 |
|  106 |     15.70 |
+------+-----------+

由结果可以看到,GROUP BY关键字根据s_id字段对记录进行分组,然后计算出每个分组中的最大值。

MAX()函数不仅适用于查找数值类型,也可应用于字符类型。

在fruits表中查找f_name的最大值,SQL语句如下:

sql 复制代码
select max(f_name) 
from fruits;

+-------------+
| max(f_name) |
+-------------+
| xxxx        |
+-------------+

由结果可以看到,MAX()函数可以对字母进行大小判断,并返回最大的字符或者字符串值。

MAX()函数除了用来找出最大的列值或日期值之外,还可以返回任意列中的最大值,包括返回字符类型的最大值。在对字符类型数据进行比较时,按照字符的ASCII码值大小进行比较,从a~z,a的ASCII码最小,z的最大。在比较时,先比较第一个字母,如果相等,继续比较下一个字符,一直到两个字符不相等或者字符结束为止。例如,'b'与't'比较时,'t'为最大值;​"bcd"与"bca"比较时,​"bcd"为最大值。

3.5、MIN()函数

MIN()返回查询列中的最小值。

在fruits表中查找市场上价格最低的水果值,SQL语句如下:

sql 复制代码
select min(f_price) as min_price
from fruits;

+-----------+
| min_price |
+-----------+
|      2.20 |
+-----------+

由结果可以看到,MIN ()函数查询出了f_price字段的最小值2.20。

MIN()也可以和GROUP BY关键字一起使用,求出每个分组中的最小值。

在fruits表中查找不同供应商提供的价格最低的水果值,SQL语句如下:

sql 复制代码
select s_id, min(f_price) as min_price
from fruits 
group by s_id;

+------+-----------+
| s_id | min_price |
+------+-----------+
|  101 |      3.20 |
|  103 |      2.20 |
|  104 |      6.40 |
|  107 |      3.60 |
|  102 |      5.30 |
|  105 |      2.60 |
|  106 |     15.70 |
+------+-----------+

由结果可以看到,GROUP BY关键字根据s_id字段对记录进行分组,然后计算出每个分组中的最小值。

MIN()函数与MAX()函数类似,不仅适用于查找数值类型,也可应用于字符类型。

4、连接查询

4.1、内连接查询

内连接(INNER JOIN)使用比较运算符进行表间某(些)列数据的比较操作,并列出这些表中与连接条件相匹配的数据行,组合成新的记录,也就是说,在内连接查询中,只有满足条件的记录才能出现在结果关系中。

为了演示的需要,首先创建数据表suppliers,SQL语句如下:

sql 复制代码
create table suppliers
(
	s_id int not null auto_increment,
	s_name char(50) not null,
	s_city char(50) null,
	s_zip char(10) null,
	s_call char(50) not null,
	primary key (s_id)
);

insert into suppliers(s_id, s_name, s_city, s_zip, s_call) values
(102, 'LT Supplies', 'Chongqing', '400000', '44333'),
(103, 'ACME', 'Shanghai', '200000', '90046'),
(104, 'FNK Inc.', 'Zhongshan', '528437', '11111'),
(1.5, 'Good Set', 'Taiyuang', '030000', '22222'),
(106, 'Just Eat Ours', 'Beijing', '010', '45678'),
(107, 'DK Inc.', 'Zhengzhou', '450000', '33332');

在fruits表和suppliers表之间使用内连接查询。

查询之前,查看两个表的结构:

sql 复制代码
desc fruits;
+---------+--------------+------+-----+---------+-------+
| Field   | Type         | Null | Key | Default | Extra |
+---------+--------------+------+-----+---------+-------+
| f_id    | char(10)     | NO   | PRI | <null>  |       |
| s_id    | int          | NO   |     | <null>  |       |
| f_name  | char(255)    | NO   |     | <null>  |       |
| f_price | decimal(8,2) | NO   |     | <null>  |       |
+---------+--------------+------+-----+---------+-------+

desc suppliers;

+--------+----------+------+-----+---------+----------------+
| Field  | Type     | Null | Key | Default | Extra          |
+--------+----------+------+-----+---------+----------------+
| s_id   | int      | NO   | PRI | <null>  | auto_increment |
| s_name | char(50) | NO   |     | <null>  |                |
| s_city | char(50) | YES  |     | <null>  |                |
| s_zip  | char(10) | YES  |     | <null>  |                |
| s_call | char(50) | NO   |     | <null>  |                |
+--------+----------+------+-----+---------+----------------+

由结果可以看到,fruits表和suppliers表中都有相同数据类型的字段s_id,两个表通过s_id字段建立联系。接下来从fruits表中查询f_name、f_price字段,从suppliers表中查询s_id、s_name,SQL语句如下:

sql 复制代码
select suppliers.s_id, s_name, f_name, f_price
from fruits, suppliers
where fruits.s_id = suppliers.s_id;

+------+---------------+---------+---------+
| s_id | s_name        | f_name  | f_price |
+------+---------------+---------+---------+
|  103 | ACME          | apricot |    2.20 |
|  104 | FNK Inc.      | berry   |    7.60 |
|  107 | DK Inc.       | xxxx    |    3.60 |
|  102 | LT Supplies   | orange  |   11.20 |
|  104 | FNK Inc.      | lemon   |    6.40 |
|  106 | Just Eat Ours | mango   |   15.70 |
|  103 | ACME          | coconut |    9.20 |
|  102 | LT Supplies   | banana  |   10.30 |
|  102 | LT Supplies   | grape   |    5.30 |
|  107 | DK Inc.       | xbababa |    3.60 |
+------+---------------+---------+---------+

在这里,SELECT语句与前面所介绍的一个最大的差别是:SELECT后面指定的列分别属于两个不同的表,​(f_name,f_price)在表fruits中,而另外两个字段在表suppliers中;同时FROM子句列出了两个表fruits和suppliers。WHERE子句在这里作为过滤条件,指明只有两个表中的s_id字段值相等的时候才符合连接查询的条件。从返回的结果可以看到,显示的记录是由两个表中的不同列值组成的新记录。

因为fruits表和suppliers表中有相同的字段s_id,因此在比较的时候需要完全限定表名(格式为"表名.列名"​)​,如果只给出s_id,MySQL将不知道指的是哪一个,并返回错误信息。

下面的内连接查询语句返回与前面完全相同的结果。

在fruits表和suppliers表之间,使用INNER JOIN语法进行内连接查询,SQL语句如下:

sql 复制代码
select suppliers.s_id, s_name, f_name, f_price
from fruits inner join suppliers
on fruits.s_id = suppliers.s_id;

+------+---------------+---------+---------+
| s_id | s_name        | f_name  | f_price |
+------+---------------+---------+---------+
|  103 | ACME          | apricot |    2.20 |
|  104 | FNK Inc.      | berry   |    7.60 |
|  107 | DK Inc.       | xxxx    |    3.60 |
|  102 | LT Supplies   | orange  |   11.20 |
|  104 | FNK Inc.      | lemon   |    6.40 |
|  106 | Just Eat Ours | mango   |   15.70 |
|  103 | ACME          | coconut |    9.20 |
|  102 | LT Supplies   | banana  |   10.30 |
|  102 | LT Supplies   | grape   |    5.30 |
|  107 | DK Inc.       | xbababa |    3.60 |
+------+---------------+---------+---------+

在这里的查询语句中,两个表之间的关系通过INNER JOIN指定。使用这种语法的时候,连接的条件使用ON子句而不是WHERE,ON和WHERE后面指定的条件相同。

使用WHERE子句定义连接条件比较简单明了,而INNER JOIN语法是ANSI SQL的标准规范,使用INNER JOIN连接语法能够确保不会忘记连接条件,而且WHERE子句在某些时候会影响查询的性能。

如果在一个连接查询中,涉及的两个表都是同一个表,这种查询称为自连接查询。自连接是一种特殊的内连接,它是指相互连接的表在物理上为同一张表,但可以在逻辑上分为两张表。

查询f_id= 'a1'的水果供应商提供的水果种类,SQL语句如下:

sql 复制代码
select f1.f_id, f1.f_name
from fruits as  f1, fruits as f2
where f1.s_id = f2.s_id and f2.f_id = 'a1';

+------+------------+
| f_id | f_name     |
+------+------------+
| a1   | apple      |
| b1   | blackberry |
| c0   | cherry     |
+------+------------+

此处查询的两个表是相同的表,为了防止产生二义性,对表使用了别名,fruits表第1次出现的别名为f1,第2次出现的别名为f2,使用SELECT语句返回列时明确指出返回以f1为前缀的列的全名,WHERE连接两个表,并按照第2个表的f_id对数据进行过滤,返回所需数据。

4.2、外连接查询

外连接查询将查询多个表中相关联的行,内连接时,返回查询结果集合中仅是符合查询条件和连接条件的行。有时候需要包含没有关联的行中数据,即返回查询结果集合中不仅包含符合连接条件的行,还包括左表(左外连接或左连接)​、右表(右外连接或右连接)或两个边接表(全外连接)中的所有数据行。外连接分为左外连接或左连接和右外连接或右连接:

  • LEFT JOIN(左连接):返回包括左表中的所有记录和右表中连接字段相等的记录。
  • RIGHT JOIN(右连接):返回包括右表中的所有记录和左表中连接字段相等的记录。
4.2.1、LEFT JOIN左连接

左连接的结果包括LEFT OUTER子句中指定的左表的所有行,而不仅仅是连接列所匹配的行。如果左表的某行在右表中没有匹配行,则在相关联的结果行中,右表的所有选择列表列均为空值。

首先创建表orders,SQL语句如下:

sql 复制代码
create table orders
(
	o_num int not null auto_increment,
	o_date datetime not null,
	c_id int not null,
	primary key(o_num)
);

插入需要演示的数据,SQL语句如下:

sql 复制代码
insert into orders(o_num, o_date, c_id) values
(30001, '2008-09-01', 10001),
(30002, '2008-09-12', 10003),
(30003, '2008-09-30', 10004),
(30004, '2008-10-03', 10005),
(30005, '2008-10-08', 10001);

在customers表和orders表中,查询所有客户,包括没有订单的客户,SQL语句如下:

sql 复制代码
select customers.c_id, orders.o_num
from customers left outer join orders
on customers.c_id = orders.c_id;

+-------+--------+
|  c_id |  o_num |
+-------+--------+
| 10001 |  30005 |
| 10001 |  30001 |
| 10002 | <null> |
| 10003 |  30002 |
| 10004 |  30003 |
+-------+--------+

结果显示了5条记录,ID等于10002的客户目前并没有下订单,所以对应的orders表中并没有该客户的订单信息,所以该条记录只取出了customers表中相应的值,而从orders表中取出的值为空值NULL。

4.2.2、RIGHT JOIN右连接

右连接是左连接的反向连接,将返回右表的所有行。如果右表的某行在左表中没有匹配行,左表将返回空值。

在customers表和orders表中,查询所有订单,包括没有客户的订单,SQL语句如下:

sql 复制代码
select customers.c_id, orders.o_num
from customers right outer join orders
on customers.c_id = orders.c_id;

+--------+-------+
|   c_id | o_num |
+--------+-------+
|  10001 | 30001 |
|  10003 | 30002 |
|  10004 | 30003 |
| <null> | 30004 |
|  10001 | 30005 |
+--------+-------+

结果显示了5条记录,订单号等于30004的订单的客户可能由于某种原因取消了该订单,对应的customers表中并没有该客户的信息,所以该条记录只取出了orders表中相应的值,而从customers表中取出的值为空值NULL。

4.3、复合条件连接查询

复合条件连接查询是在连接查询的过程中,通过添加过滤条件限制查询的结果,使查询的结果更加准确。

在customers表和orders表中,使用INNER JOIN语法查询customers表中ID为10001的客户的订单信息,SQL语句如下:

sql 复制代码
select customers.c_id, orders.o_num
from customers inner join orders
on customers.c_id = orders.c_id and customers.c_id = 10001;

+-------+-------+
|  c_id | o_num |
+-------+-------+
| 10001 | 30001 |
| 10001 | 30005 |
+-------+-------+

结果显示,在连接查询时指定查询客户ID为10001的订单信息,添加了过滤条件之后返回的结果将会变少,因此返回结果只有两条记录。

使用连接查询,并对查询的结果进行排序。

在fruits表和suppliers表之间,使用INNER JOIN语法进行内连接查询,并对查询结果排序,SQL语句如下:

sql 复制代码
select suppliers.s_id, s_name, f_name, f_price
from fruits inner join suppliers
on fruits.s_id = suppliers.s_id
order by fruits.s_id;

+------+---------------+---------+---------+
| s_id | s_name        | f_name  | f_price |
+------+---------------+---------+---------+
|  102 | LT Supplies   | orange  |   11.20 |
|  102 | LT Supplies   | banana  |   10.30 |
|  102 | LT Supplies   | grape   |    5.30 |
|  103 | ACME          | apricot |    2.20 |
|  103 | ACME          | coconut |    9.20 |
|  104 | FNK Inc.      | berry   |    7.60 |
|  104 | FNK Inc.      | lemon   |    6.40 |
|  106 | Just Eat Ours | mango   |   15.70 |
|  107 | DK Inc.       | xxxx    |    3.60 |
|  107 | DK Inc.       | xbababa |    3.60 |
+------+---------------+---------+---------+

由结果可以看到,内连接查询的结果按照suppliers.s_id字段进行了升序排序。

5、子查询

子查询指一个查询语句嵌套在另一个查询语句内部的查询,这个特性从MySQL 4.1开始引入。在SELECT子句中先计算子查询,子查询结果作为外层另一个查询的过滤条件,查询可以基于一个表或者多个表。子查询中常用的操作符有ANY(SOME)​、ALL、IN、EXISTS。子查询可以添加到SELECT、UPDATE和DELETE语句中,而且可以进行多层嵌套。子查询中也可以使用比较运算符,如"<"​"<="​">"​">="和"!="等。

5.1、带ANY、SOME关键字的子查询

ANY和SOME关键字是同义词,表示满足其中任一条件,它们允许创建一个表达式对子查询的返回值列表进行比较,只要满足内层子查询中的任何一个比较条件,就返回一个结果作为外层查询的条件。

下面定义两个表tbl1和tbl2:

sql 复制代码
CREATE table tbl1 ( num1 INT NOT NULL);
CREATE table tbl2 ( num2 INT NOT NULL);

分别向两个表中插入数据:

sql 复制代码
INSERT INTO tbl1 values(1), (5), (13), (27);
INSERT INTO tbl2 values(6), (14), (11), (20);

ANY关键字接在一个比较操作符的后面,表示若与子查询返回的任何值比较为TRUE,则返回TRUE。

返回tbl2表的所有num2列,然后将tbl1中的num1的值与之进行比较,只要大于num2的任何1个值,即为符合查询条件的结果。

sql 复制代码
select num1 from tbl1
where num1 > any (
	select num2 from tbl2
);

+------+
| num1 |
+------+
|   13 |
|   27 |
+------+

在子查询中,返回的是tbl2表的所有num2列结果(6,14,11,20)​,然后将tbl1中的num1列的值与之进行比较,只要大于num2列的任意一个数即为符合条件的结果。

5.2、带ALL关键字的子查询

ALL关键字与ANY和SOME不同,使用ALL时需要同时满足所有内层查询的条件。例如,修改前面的例子,用ALL关键字替换ANY。

ALL关键字接在一个比较操作符的后面,表示与子查询返回的所有值比较为TRUE,则返回TRUE。

返回tbl1表中比tbl2表num2列所有值都大的值,SQL语句如下:

sql 复制代码
select num1
from tbl1
where num1 > all (
	select num2 from tbl2
);

+------+
| num1 |
+------+
|   27 |
+------+

在子查询中,返回的是tbl2的所有num2列结果(6,14,11,20)​,然后将tbl1中的num1列的值与之进行比较,大于所有num2列值的num1值只有27,因此返回结果为27。

5.3、带EXISTS关键字的子查询

EXISTS关键字后面的参数是一个任意的子查询,系统对子查询进行运算以判断它是否返回行,如果至少返回一行,那么EXISTS的结果为true,此时外层查询语句将进行查询;如果子查询没有返回任何行,那么EXISTS返回的结果是false,此时外层语句将不进行查询。

查询suppliers表中是否存在s_id=107的供应商,如果存在,则查询fruits表中的记录,SQL语句如下:

sql 复制代码
select * from fruits
where exists (
	select s_name 
	from suppliers
	where s_id = 107
);

+------+------+------------+---------+
| f_id | s_id | f_name     | f_price |
+------+------+------------+---------+
| a1   |  101 | apple      |    5.20 |
| a2   |  103 | apricot    |    2.20 |
| b1   |  101 | blackberry |   20.10 |
| b2   |  104 | berry      |    7.60 |
| b5   |  107 | xxxx       |    3.60 |
| bs1  |  102 | orange     |   11.20 |
| bs2  |  105 | mellon     |    8.20 |
| c0   |  101 | cherry     |    3.20 |
| l2   |  104 | lemon      |    6.40 |
| m1   |  106 | mango      |   15.70 |
| m2   |  105 | xbabay     |    2.60 |
| m3   |  105 | xxtt       |   11.60 |
| o2   |  103 | coconut    |    9.20 |
| t1   |  102 | banana     |   10.30 |
| t2   |  102 | grape      |    5.30 |
| t4   |  107 | xbababa    |    3.60 |
+------+------+------------+---------+

由结果可以看到,内层查询结果表明suppliers表中存在s_id=107的记录,因此EXISTS表达式返回true;外层查询语句接收true之后对表fruits进行查询,返回所有的记录。

EXISTS关键字可以和条件表达式一起使用。

查询suppliers表中是否存在s_id=107的供应商,如果存在,则查询fruits表中的f_price大于10.20的记录,SQL语句如下:

sql 复制代码
select * from fruits
where f_price > 10.20 and exists (
	select s_name 
	from suppliers
	where s_id = 107
);

+------+------+------------+---------+
| f_id | s_id | f_name     | f_price |
+------+------+------------+---------+
| b1   |  101 | blackberry |   20.10 |
| bs1  |  102 | orange     |   11.20 |
| m1   |  106 | mango      |   15.70 |
| m3   |  105 | xxtt       |   11.60 |
| t1   |  102 | banana     |   10.30 |
+------+------+------------+---------+

由结果可以看到,内层查询结果表明suppliers表中存在s_id=107的记录,因此EXISTS表达式返回true;外层查询语句接收true之后根据查询条件f_price > 10.20对fruits表进行查询,返回结果为4条f_price大于10.20的记录。

NOT EXISTS与EXISTS使用方法相同,返回的结果相反。子查询如果至少返回一行,那么NOT EXISTS的结果为false,此时外层查询语句将不进行查询;如果子查询没有返回任何行,那么NOT EXISTS返回的结果是true,此时外层语句将进行查询。

查询suppliers表中是否存在s_id=107的供应商,如果不存在则查询fruits表中的记录,SQL语句如下:

sql 复制代码
select * from fruits 
where not exists (
	select s_name 
	from suppliers
	where s_id = 107
);

+------+------+--------+---------+
| f_id | s_id | f_name | f_price |
+------+------+--------+---------+
+------+------+--------+---------+

查询语句SELECT s_name FROM suppliersWHERE s_id = 107,对suppliers表进行查询返回了一条记录,NOT EXISTS表达式返回false,外层表达式接收false,将不再查询fruits表中的记录。

EXISTS和NOT EXISTS的结果只取决于是否会返回行,而不取决于这些行的内容, 所以这个子查询输入列表通常是无关紧要的。

5.4、带IN关键字的子查询

IN关键字进行子查询时,内层查询语句仅仅返回一个数据列,这个数据列里的值将提供给外层查询语句进行比较操作。

在orderitems表中查询f_id为c0的订单号,并根据订单号查询具有订单号的客户c_id,SQL语句如下:

sql 复制代码
select c_id from orders
where o_num in (
	select o_num 
	from orderitems
	where f_id = 'c0'
);

+-------+
|  c_id |
+-------+
| 10004 |
+-------+

查询结果的c_id有两个值,分别为10001和10004。上述查询过程可以分步执行,首先内层子查询查出orderitems表中符合条件的订单号,单独执行内查询,查询结果如下:

sql 复制代码
select o_num
from orderitems 
where f_id = 'c0';

+-------+
| o_num |
+-------+
| 30003 |
+-------+

可以看到,符合条件的o_num列的值有两个:30003和30005,然后执行外层查询,在orders表中查询订单号等于30003或30005的客户c_id。嵌套子查询语句还可以写为如下形式,实现相同的效果:

sql 复制代码
select c_id from orders
where o_num in (30003, 30005);

+-------+
|  c_id |
+-------+
| 10004 |
| 10001 |
+-------+

这个例子说明在处理SELECT语句的时候,MySQL实际上执行了两个操作过程,即先执行内层子查询,再执行外层查询,内层子查询的结果作为外部查询的比较条件。

SELECT语句中可以使用NOT IN关键字,其作用与IN正好相反。

与前一个例子类似,但是在SELECT语句中使用NOT IN关键字,SQL语句如下:

sql 复制代码
select c_id from orders
where o_num not in (
	select o_num 
	from orderitems
	where f_id = 'c0'
);

+-------+
|  c_id |
+-------+
| 10001 |
| 10003 |
| 10005 |
| 10001 |
+-------+

这里返回的结果有3条记录,由前面可以看到,子查询返回的订单值有两个,即30003和30005,但为什么这里还有值为10001的c_id呢?这是因为c_id等于10001的客户的订单不只一个,可以查看订单表orders中的记录。

sql 复制代码
select * from orders;

+-------+---------------------+-------+
| o_num | o_date              |  c_id |
+-------+---------------------+-------+
| 30001 | 2008-09-01 00:00:00 | 10001 |
| 30002 | 2008-09-12 00:00:00 | 10003 |
| 30003 | 2008-09-30 00:00:00 | 10004 |
| 30004 | 2008-10-03 00:00:00 | 10005 |
| 30005 | 2008-10-08 00:00:00 | 10001 |
+-------+---------------------+-------+

5.5、带比较运算符的子查询

在前面介绍的带ANY、ALL关键字的子查询时使用了">"比较运算符,子查询时还可以使用其他的比较运算符,如"<"​"<="​"="​">="和"!="等。

在suppliers表中查询s_city等于"Tianjin"的供应商s_id,然后在fruits表中查询所有该供应商提供的水果的种类,SQL语句如下:

sql 复制代码
select s_id, f_name 
from fruits
where s_id = (
	select s1.s_id
	from suppliers as s1
	where s1.s_city = 'Tianjin'
);

+------+--------+
| s_id | f_name |
+------+--------+
+------+--------+

在suppliers表中查询s_city等于"Tianjin"的供应商s_id,然后在fruits表中查询所有非该供应商提供的水果的种类,SQL语句如下:

sql 复制代码
select s_id, f_name
from fruits
where s_id <> (
	select s1.s_id
	from suppliers as s1 
	where s1.s_city = 'Tianjin'
);

6、合并查询结果

利用UNION关键字,可以给出多条SELECT语句,并将它们的结果组合成单个结果集。合并时,两个表对应的列数和数据类型必须相同。各个SELECT语句之间使用UNION或UNION ALL关键字分隔。UNION不使用关键字ALL,执行的时候删除重复的记录,所有返回的行都是唯一的;使用关键字ALL的作用是不删除重复行也不对结果进行自动排序。基本语法格式如下:

sql 复制代码
  SELECT column,... FROM table1
  UNION [ALL]
  SELECT column,... FROM table2

查询所有价格小于9的水果的信息,查询s_id等于101和103所有的水果的信息,使用UNION连接查询结果,SQL语句如下:

sql 复制代码
select s_id, f_name, f_price
from fruits
where f_price < 9.0
union 
select s_id, f_name, f_price
from fruits
where s_id in(101, 103);

+------+------------+---------+
| s_id | f_name     | f_price |
+------+------------+---------+
|  101 | apple      |    5.20 |
|  103 | apricot    |    2.20 |
|  104 | berry      |    7.60 |
|  107 | xxxx       |    3.60 |
|  105 | mellon     |    8.20 |
|  101 | cherry     |    3.20 |
|  104 | lemon      |    6.40 |
|  105 | xbabay     |    2.60 |
|  102 | grape      |    5.30 |
|  107 | xbababa    |    3.60 |
|  101 | blackberry |   20.10 |
|  103 | coconut    |    9.20 |
+------+------------+---------+

使用UNION将两条SELECT语句分隔开,执行完毕之后把输出结果组合成单个的结果集,并删除重复的记录。

使用UNION ALL包含重复的行,在前面的例子中,分开查询时,两个返回结果中有相同的记录。UNION从查询结果集中自动去除了重复的行,如果要返回所有匹配行,而不进行删除,可以使用UNION ALL。

查询所有价格小于9的水果的信息,查询s_id等于101和103的所有水果的信息,使用UNION ALL连接查询结果,SQL语句如下:

sql 复制代码
select s_id, f_name, f_price
from fruits
where f_price < 9.0
union all
select s_id, f_name, f_price
from fruits
where s_id in(101, 103);

+------+------------+---------+
| s_id | f_name     | f_price |
+------+------------+---------+
|  101 | apple      |    5.20 |
|  103 | apricot    |    2.20 |
|  104 | berry      |    7.60 |
|  107 | xxxx       |    3.60 |
|  105 | mellon     |    8.20 |
|  101 | cherry     |    3.20 |
|  104 | lemon      |    6.40 |
|  105 | xbabay     |    2.60 |
|  102 | grape      |    5.30 |
|  107 | xbababa    |    3.60 |
|  101 | apple      |    5.20 |
|  103 | apricot    |    2.20 |
|  101 | blackberry |   20.10 |
|  101 | cherry     |    3.20 |
|  103 | coconut    |    9.20 |
+------+------------+---------+

由结果可以看到,这里总的记录数等于两条SELECT语句返回的记录数之和,连接查询结果并没有去除重复的行。

UNION和UNION ALL的区别:使用UNION ALL的功能是不删除重复行,加上ALL关键字语句执行时所需要的资源少,所以尽可能地使用它,因此知道有重复行但是想保留这些行,确定查询结果中不会有重复数据或者不需要去掉重复数据的时候,应当使用UNION ALL以提高查询效率。

7、为表和字段取别名

7.1、为表取别名

当表名字很长或者执行一些特殊查询时,为了方便操作或者需要多次使用相同的表时,可以为表指定别名,用这个别名替代表原来的名称。为表取别名的基本语法格式为:

sql 复制代码
表名 [AS] 表别名

"表名"为数据库中存储的数据表的名称,​"表别名"为查询时指定的表的新名称,AS关键字为可选参数。

为orders表取别名o,查询30001订单的下单日期,SQL语句如下:

sql 复制代码
select * from orders as o
where o.o_num = 30001;

在这里orders AS o代码表示为orders表取别名为o,指定过滤条件时直接使用o代替orders,查询结果如下:

sql 复制代码
+-------+---------------------+-------+
| o_num | o_date              |  c_id |
+-------+---------------------+-------+
| 30001 | 2008-09-01 00:00:00 | 10001 |
+-------+---------------------+-------+

为customers和orders表分别取别名,并进行连接查询,SQL语句如下:

sql 复制代码
select c.c_id, o.o_num
from customers as c left outer join orders as o
on c.c_id = o.c_id;

+-------+--------+
|  c_id |  o_num |
+-------+--------+
| 10001 |  30005 |
| 10001 |  30001 |
| 10002 | <null> |
| 10003 |  30002 |
| 10004 |  30003 |
+-------+--------+

由结果看到,MySQL可以同时为多个表取别名,而且表别名可以放在不同的位置,如WHERE子句、SELECT列表、ON子句以及ORDER BY子句等。

在前面介绍内连接查询时指出自连接是一种特殊的内连接,在连接查询中的两个表都是同一个表,其查询语句如下:

sql 复制代码
select f1.f_id, f1.f_name
from fruits as f1, fruits as f2
where f1.s_id = f2.s_id and f2.f_id = 'a1';

+------+------------+
| f_id | f_name     |
+------+------------+
| a1   | apple      |
| b1   | blackberry |
| c0   | cherry     |
+------+------------+

在这里,如果不使用表别名,MySQL将不知道引用的是哪个fruits表实例,这是表别名一个非常有用的地方。

在为表取别名时,要保证不能与数据库中其他表的名称冲突。

7.2、为字段取别名

MySQL可以指定列别名,替换字段或表达式。为字段取别名的基本语法格式为:

sql 复制代码
  列名 [AS] 列别名

"列名"为表中字段定义的名称,​"列别名"为字段新的名称,AS关键字为可选参数。

查询fruits表,为f_name取别名fruit_name,f_price取别名fruit_price,为fruits表取别名f1,查询表中f_price < 8的水果的名称,SQL语句如下:

sql 复制代码
select f1.f_name as fruit_name, f1.f_price as fruit_price
from fruits as f1
where f1.f_price < 8;

+------------+-------------+
| fruit_name | fruit_price |
+------------+-------------+
| apple      |        5.20 |
| apricot    |        2.20 |
| berry      |        7.60 |
| xxxx       |        3.60 |
| cherry     |        3.20 |
| lemon      |        6.40 |
| xbabay     |        2.60 |
| grape      |        5.30 |
| xbababa    |        3.60 |
+------------+-------------+

也可以为SELECT子句中的计算字段取别名。例如,对使用COUNT聚合函数或者CONCAT等系统函数执行的结果字段取别名。

查询suppliers表中字段s_name和s_city,使用CONCAT函数连接这两个字段值,并取列别名为suppliers_title。

如果没有对连接后的值取别名,其显示列名称将会不够直观,SQL语句如下:

sql 复制代码
select concat(trim(s_name), '(', trim(s_city), ')')
from suppliers
order by s_name;

+----------------------------------------------+
| concat(trim(s_name), '(', trim(s_city), ')') |
+----------------------------------------------+
| ACME(Shanghai)                               |
| DK Inc.(Zhengzhou)                           |
| FNK Inc.(Zhongshan)                          |
| Good Set(Taiyuang)                           |
| Just Eat Ours(Beijing)                       |
| LT Supplies(Chongqing)                       |
+----------------------------------------------+

由结果可以看到,显示结果的列名称为SELECT子句后面的计算字段,实际上计算之后的列是没有名字的,这样的结果让人很不容易理解,如果为字段取一个别名,将会使结果清晰,SQL语句如下:

sql 复制代码
select concat(trim(s_name), '(', trim(s_city), ')') as suppliers_title
from suppliers
order by s_name;

+------------------------+
| suppliers_title        |
+------------------------+
| ACME(Shanghai)         |
| DK Inc.(Zhengzhou)     |
| FNK Inc.(Zhongshan)    |
| Good Set(Taiyuang)     |
| Just Eat Ours(Beijing) |
| LT Supplies(Chongqing) |
+------------------------+

由结果可以看到,SELECT子句计算字段值之后增加了AS suppliers_title,它指示MySQL为计算字段创建一个别名suppliers_title,显示结果为指定的列别名,这样就增强了查询结果的可读性。

表别名只在执行查询的时候使用,并不在返回结果中显示,而列别名定义之后,将返回给客户端显示,显示的结果字段为字段列的别名。

8、使用正则表达式查询

MySQL中使用REGEXP关键字指定正则表达式的字符匹配模式。下表列出了REGEXP操作符中常用字符匹配列表。

8.1、查询以特定字符或字符串开头的记录

字符'^'匹配以特定字符或者字符串开头的文本。

在fruits表中,查询f_name字段以字母'b'开头的记录,SQL语句如下:

sql 复制代码
select * from fruits
where f_name regexp '^b';

+------+------+------------+---------+
| f_id | s_id | f_name     | f_price |
+------+------+------------+---------+
| b1   |  101 | blackberry |   20.10 |
| b2   |  104 | berry      |    7.60 |
| t1   |  102 | banana     |   10.30 |
+------+------+------------+---------+

fruits表中有3条记录的f_name字段值是以字母b开头的,返回结果有3条记录。

在fruits表中,查询f_name字段以"be"开头的记录,SQL语句如下:

sql 复制代码
select * from fruits
where f_name regexp '^be';

+------+------+--------+---------+
| f_id | s_id | f_name | f_price |
+------+------+--------+---------+
| b2   |  104 | berry  |    7.60 |
+------+------+--------+---------+

只有berry是以"be"开头的,所以查询结果中只有1条记录。

8.2、查询以特定字符或字符串结尾的记录

字符'$'匹配以特定字符或者字符串结尾的文本。

在fruits表中,查询f_name字段以字母'y'结尾的记录,SQL语句如下:

sql 复制代码
select * from fruits
where f_name regexp 'y$';

+------+------+------------+---------+
| f_id | s_id | f_name     | f_price |
+------+------+------------+---------+
| b1   |  101 | blackberry |   20.10 |
| b2   |  104 | berry      |    7.60 |
| c0   |  101 | cherry     |    3.20 |
| m2   |  105 | xbabay     |    2.60 |
+------+------+------------+---------+

fruits表中有4条记录的f_name字段值是以字母'y'结尾的,返回结果有4条记录。

在fruits表中,查询f_name字段以字符串"rry"结尾的记录,SQL语句如下:

sql 复制代码
select * from fruits
where f_name regexp 'rry$';

+------+------+------------+---------+
| f_id | s_id | f_name     | f_price |
+------+------+------------+---------+
| b1   |  101 | blackberry |   20.10 |
| b2   |  104 | berry      |    7.60 |
| c0   |  101 | cherry     |    3.20 |
+------+------+------------+---------+

fruits表中有3条记录的f_name字段值是以字符串"rry"结尾的,返回结果有3条记录。

8.3、用符号"."来替代字符串中的任意一个字符

字符'.'匹配任意一个字符。

在fruits表中,查询f_name字段值包含字母'a'与'g'且两个字母之间只有一个字母的记录,SQL语句如下:

sql 复制代码
select * from fruits
where f_name regexp 'a.g';

+------+------+--------+---------+
| f_id | s_id | f_name | f_price |
+------+------+--------+---------+
| bs1  |  102 | orange |   11.20 |
| m1   |  106 | mango  |   15.70 |
+------+------+--------+---------+

查询语句中'a.g'指定匹配字符中要有字母a和g,且两个字母之间包含单个字符,并不限定匹配的字符的位置和所在查询字符串的总长度,因此orange和mango都符合匹配条件。

8.4、使用"*"和"+"来匹配多个字符

星号'*'匹配前面的字符任意多次,包括0次。加号'+'匹配前面的字符至少一次。

在fruits表中,查询f_name字段值以字母'b'开头且'b'后面出现字母'a'的记录,SQL语句如下:

sql 复制代码
select * from fruits
where f_name regexp '^ba*';

+------+------+------------+---------+
| f_id | s_id | f_name     | f_price |
+------+------+------------+---------+
| b1   |  101 | blackberry |   20.10 |
| b2   |  104 | berry      |    7.60 |
| t1   |  102 | banana     |   10.30 |
+------+------+------------+---------+

星号'*'可以匹配任意多个字符,blackberry和berry中字母b后面并没有出现字母a,但是也满足匹配条件。

在fruits表中,查询f_name字段值以字母'b'开头且'b'后面出现字母'a'至少一次的记录,SQL语句如下:

sql 复制代码
select * from fruits
where f_name regexp '^ba+';

Time: 0.071s
+------+------+--------+---------+
| f_id | s_id | f_name | f_price |
+------+------+--------+---------+
| t1   |  102 | banana |   10.30 |
+------+------+--------+---------+

'a+'匹配字母'a'至少一次,只有banana满足匹配条件。

8.5、匹配指定字符串

正则表达式可以匹配指定字符串,只要这个字符串在查询文本中即可,如要匹配多个字符串,多个字符串之间使用分隔符'|'隔开。

在fruits表中,查询f_name字段值包含字符串"on"的记录,SQL语句如下:

sql 复制代码
select * from fruits
where f_name regexp 'on';

+------+------+---------+---------+
| f_id | s_id | f_name  | f_price |
+------+------+---------+---------+
| bs2  |  105 | mellon  |    8.20 |
| l2   |  104 | lemon   |    6.40 |
| o2   |  103 | coconut |    9.20 |
+------+------+---------+---------+

可以看到,f_name字段的melon、lemon和coconut 3个值中都包含有字符串"on"​,满足匹配条件。

在fruits表中,查询f_name字段值包含字符串"on"或者"ap"的记录,SQL语句如下:

sql 复制代码
select * from fruits
where f_name regexp 'on|ap';

+------+------+---------+---------+
| f_id | s_id | f_name  | f_price |
+------+------+---------+---------+
| a1   |  101 | apple   |    5.20 |
| a2   |  103 | apricot |    2.20 |
| bs2  |  105 | mellon  |    8.20 |
| l2   |  104 | lemon   |    6.40 |
| o2   |  103 | coconut |    9.20 |
| t2   |  102 | grape   |    5.30 |
+------+------+---------+---------+

可以看到,f_name字段的melon、lemon和coconut 3个值中都包含有字符串"on"​,apple和apricot值中包含字符串"ap"​,满足匹配条件。

在fruits表中,使用LIKE运算符查询f_name字段值为"on"的记录,SQL语句如下:

sql 复制代码
select * from fruits
where f_name like 'on';

+------+------+--------+---------+
| f_id | s_id | f_name | f_price |
+------+------+--------+---------+
+------+------+--------+---------+

f_name字段没有值为"on"的记录,返回结果为空。

8.6、匹配指定字符中的任意一个

方括号"​​"指定一个字符集合,只匹配其中任何一个字符,即为所查找的文本。

在fruits表中,查找f_name字段中包含字母'o'或者't'的记录,SQL语句如下:

sql 复制代码
select * from fruits
where f_name regexp '[ot]';

+------+------+---------+---------+
| f_id | s_id | f_name  | f_price |
+------+------+---------+---------+
| a2   |  103 | apricot |    2.20 |
| bs1  |  102 | orange  |   11.20 |
| bs2  |  105 | mellon  |    8.20 |
| l2   |  104 | lemon   |    6.40 |
| m1   |  106 | mango   |   15.70 |
| m3   |  105 | xxtt    |   11.60 |
| o2   |  103 | coconut |    9.20 |
+------+------+---------+---------+

由查询结果可以看到,所有返回的记录的f_name字段的值中都包含有字母o或者t,或者两个都有。

方括号"​​"还可以指定数值集合。

在fruits表中,查询s_id字段中包含4、5或者6的记录,SQL语句如下:

sql 复制代码
select * from fruits
where s_id regexp '[456]';

+------+------+--------+---------+
| f_id | s_id | f_name | f_price |
+------+------+--------+---------+
| b2   |  104 | berry  |    7.60 |
| bs2  |  105 | mellon |    8.20 |
| l2   |  104 | lemon  |    6.40 |
| m1   |  106 | mango  |   15.70 |
| m2   |  105 | xbabay |    2.60 |
| m3   |  105 | xxtt   |   11.60 |
+------+------+--------+---------+

在查询结果中,s_id字段值中只要有3个数字中的1个即为匹配记录字段。

匹配集合"​456​"也可以写成"​4-6​"​,即指定集合区间。例如,​"​a-z​"表示集合区间为从a~z的字母,​"​0-9​"表示集合区间为所有数字。

8.7、匹配指定字符以外的字符

"​\^字符集合​"匹配不在指定集合中的任何字符。

在fruits表中,查询f_id字段中包含字母ae和数字12以外字符的记录,SQL语句如下:

sql 复制代码
select * from fruits
where f_id regexp '[^a-e1-2]';

+------+------+---------+---------+
| f_id | s_id | f_name  | f_price |
+------+------+---------+---------+
| b5   |  107 | xxxx    |    3.60 |
| bs1  |  102 | orange  |   11.20 |
| bs2  |  105 | mellon  |    8.20 |
| c0   |  101 | cherry  |    3.20 |
| l2   |  104 | lemon   |    6.40 |
| m1   |  106 | mango   |   15.70 |
| m2   |  105 | xbabay  |    2.60 |
| m3   |  105 | xxtt    |   11.60 |
| o2   |  103 | coconut |    9.20 |
| t1   |  102 | banana  |   10.30 |
| t2   |  102 | grape   |    5.30 |
| t4   |  107 | xbababa |    3.60 |
+------+------+---------+---------+

返回记录中的f_id字段值中包含指定字母和数字以外的值,如s、m、o、t等,这些字母均不在ae与12之间,满足匹配条件。

8.8、使用{n,}或者{n,m}来指定字符串连续出现的次数

"字符串{n,}"表示至少匹配n次前面的字符;​"字符串{n,m}"表示匹配前面的字符串不少于n次,不多于m次。例如,a{2,}表示字母a连续出现至少2次,也可以大于2次;a{2,4}表示字母a连续出现最少2次,最多不能超过4次。

在fruits表中,查询f_name字段值出现字母'x'至少2次的记录,SQL语句如下:

sql 复制代码
select * from fruits
where f_name regexp 'x{2,}';

+------+------+--------+---------+
| f_id | s_id | f_name | f_price |
+------+------+--------+---------+
| b5   |  107 | xxxx   |    3.60 |
| m3   |  105 | xxtt   |   11.60 |
+------+------+--------+---------+

可以看到,f_name字段的"xxxx"包含了4个字母'x',​"xxtt"包含两个字母'x',均为满足匹配条件的记录。

在fruits表中,查询f_name字段值出现字符串"ba"最少1次、最多3次的记录,SQL语句如下:

sql 复制代码
select * from fruits
where f_name regexp 'ba{1,3}';

+------+------+---------+---------+
| f_id | s_id | f_name  | f_price |
+------+------+---------+---------+
| m2   |  105 | xbabay  |    2.60 |
| t1   |  102 | banana  |   10.30 |
| t4   |  107 | xbababa |    3.60 |
+------+------+---------+---------+

可以看到,f_name字段的xbabay值中"ba"出现了2次,banana中出现了1次,xbababa中出现了3次,都满足匹配条件的记录。

9、MySQL 8.0的新特性------GROUP BY不再隐式排序

从MySQL 8.0版本开始,MySQL对GROUPBY字段不再隐式排序。如果确实需要排序,必须加上ORDER BY子句。

下面通过案例来对比不同的版本中GROUPBy字段的排序情况,分别在MySQL 5.7版本和MySQL 8.0版本中创建数据表、插入数据和查询数据,结果如下:

sql 复制代码
create table bs1
(
	bsid int(11) not null,
	bscount int(11) default '0'
);

insert into bs1 (bsid, bscount) values
(101, 5),
(102, 5),
(103, 5),
(105, 0),
(106, 0),
(107, 8),
(109, 8);

在MySQL 5.7中查看数据表bs1的结构,结果如下:

sql 复制代码
mysql> show create table bs1\G;
*************************** 1. row ***************************
       Table: bs1
Create Table: CREATE TABLE `bs1` (
  `bsid` int(11) NOT NULL,
  `bscount` int(11) DEFAULT '0'
) ENGINE=InnoDB DEFAULT CHARSET=utf8

在MySQL 8.0中查看数据表bs1的结构,结果

sql 复制代码
***************************[ 1. row ]***************************
Table        | bs1
Create Table | CREATE TABLE `bs1` (
  `bsid` int NOT NULL,
  `bscount` int DEFAULT '0'
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3

在MySQL 5.7中分组查询,结果如下:

sql 复制代码
select count(bsid), bscount 
from bs1 
group by bscount;


+-------------+---------+
| count(bsid) | bscount |
+-------------+---------+
|           2 |       0 |
|           3 |       5 |
|           2 |       8 |
+-------------+---------+
3 rows in set (0.00 sec)

从结果可以看出,字段bscount按升序自动排列。

在MySQL 8.0中分组查询,结果如下:

sql 复制代码
select count(bsid), bscount 
from bs1 
group by bscount

+-------------+---------+
| count(bsid) | bscount |
+-------------+---------+
|           3 |       5 |
|           2 |       0 |
|           2 |       8 |
+-------------+---------+

从结果可以看出,字段bscount没有排列。

在MySQL 8.0中添加ORDER BY子句实现排序效果,结果如下:

sql 复制代码
select count(bsid), bscount 
from bs1 
group by bscount
order by bscount;

+-------------+---------+
| count(bsid) | bscount |
+-------------+---------+
|           2 |       0 |
|           3 |       5 |
|           2 |       8 |
+-------------+---------+

10、MySQL 8.0的新特性------通用表表达式

通用表表达式简称为CTE(Common TableExpressions)​。CTE是命名的临时结果集,作用范围是当前语句。CTE可以理解成一个可以复用的子查询,当然跟子查询还是有点区别的,CTE可以引用其他CTE,但子查询不能引用其他子查询。

CTE的语法格式如下:

sql 复制代码
with_clause:
	with [recursive]
		cte_name [{col_name [, col_name]...)] as (subquery)
		[, cte_name [(col_name [, col_name]...)] as (subquery)]...

使用WITH语句创建CTE的情况如下:

  1. SELECT、UPDATE、DELETE语句的开头:
sql 复制代码
with ... select ...
with ... update ...
with ... delete ...
  1. 在子查询的开头:
sql 复制代码
select ... where id in (with ... select ...) ...
select * from (with ... select ...) as dt ...
  1. 紧接SELECT,在包含SELECT声明的语句之前:
sql 复制代码
insert ... with ... select ...
replace ... with ... select ...
create table ... with ... select ...
create view ... with ... select ...
declare cursor ... with ... select ...
explain ... with ... select ...

下面通过案例来讲述通用表表达式的使用方法。

创建商品表goods,该数据表包含上下级关系的数据,具体字段包含商品编号(id)、商品名称(name)​、上级商品的编号(gid)。创建语句如下:

sql 复制代码
create table goods(
	id int(11),
	name varchar(30),
	gid int(11),
	primary key (`id`)
);

插入演示数据:

sql 复制代码
insert into goods (id, name, gid) values
(1, '商品', 0),
(2, '水果', 1),
(3, '蔬菜', 2),
(4, '苹果', 2),
(5, '香蕉', 2),
(6, '菠菜', 3),
(7, '萝卜', 3);

下面开始查询每个商品对应的上级商品名称。

这里使用子查询的方式:

sql 复制代码
select g.*, ( select name from goods where id = g.gid) as pname
from goods as g;

+----+------+-----+--------+
| id | name | gid | pname  |
+----+------+-----+--------+
|  1 | 商品 |   0 | <null> |
|  2 | 水果 |   1 | 商品   |
|  3 | 蔬菜 |   2 | 水果   |
|  4 | 苹果 |   2 | 水果   |
|  5 | 香蕉 |   2 | 水果   |
|  6 | 菠菜 |   3 | 蔬菜   |
|  7 | 萝卜 |   3 | 蔬菜   |
+----+------+-----+--------+

接着使用CTE的方式,完成上述功能:

sql 复制代码
with cte as (
	select * from goods
)
select g.*, (select cte.name from cte where cte.id = g.gid) as gname 
from goods as g;

+----+------+-----+--------+
| id | name | gid | gname  |
+----+------+-----+--------+
|  1 | 商品 |   0 | <null> |
|  2 | 水果 |   1 | 商品   |
|  3 | 蔬菜 |   2 | 水果   |
|  4 | 苹果 |   2 | 水果   |
|  5 | 香蕉 |   2 | 水果   |
|  6 | 菠菜 |   3 | 蔬菜   |
|  7 | 萝卜 |   3 | 蔬菜   |
+----+------+-----+--------+

从结果可以看出,CTE是一个可以重复使用的结果集。相比于子查询,CTE的效率会更高,因为非递归的CTE只会查询一次并可以重复使用。

CTE可以引用其他CTE的结果。例如,下面的语句中,cte2就引用了cte1中的结果。

sql 复制代码
with cte1 as (
	select * from goods
), cte2 as (
	select g.*, cte1.name as gname 
	from goods as g
	left join cte1 on g.gid = cte1.id
)
select * from cte2;

+----+------+-----+--------+
| id | name | gid | gname  |
+----+------+-----+--------+
|  1 | 商品 |   0 | <null> |
|  2 | 水果 |   1 | 商品   |
|  3 | 蔬菜 |   2 | 水果   |
|  4 | 苹果 |   2 | 水果   |
|  5 | 香蕉 |   2 | 水果   |
|  6 | 菠菜 |   3 | 蔬菜   |
|  7 | 萝卜 |   3 | 蔬菜   |
+----+------+-----+--------+

还有一种特殊的CTE,就是递归CTE,其子查询会引用自身。WITH子句必须以WITHRECURSIVE开头。

CTE递归子查询包括两部分:seed查询和recursive查询,中间由union all或uniondistinct分隔。seed查询会被执行一次,以创建初始数据子集。recursive查询会被重复执行以返回数据子集,直到获得完整结果集。当迭代不会生成任何新行时,递归会停止。可以参看下面的案例:

sql 复制代码
with recursive cte(n) as (
	select 1
	union all
	select n + 1
	from cte 
	where n < 8
)
select * from cte;

+---+
| n |
+---+
| 1 |
| 2 |
| 3 |
| 4 |
| 5 |
| 6 |
| 7 |
| 8 |
+---+

上面的语句会递归显示8行,每行分别显示 1~8数字。

递归的过程如下:

  1. 首先执行SELECT 1得到结果1,即当前n的值为1。
  2. 接着执行SELECT N+1 FROM cteWHERE n < 8,因为当前n为1,所以WHERE条件成立,生成新行,SELECT n+1得到结果2,即当前n的值为2。
  3. 继续执行SELECT n+1 FROM cteWHERE n < 8,因为当前n为2,所以WHERE条件成立,生成新行,SELECT n+1 得到结果3,即当前n的值为3。
  4. 一直递归下去。
  5. 直到当n为8时,where条件不成立,无法生成新行,递归停止。

下面使用递归CTE来查询每个商品到顶级商品的层次。

sql 复制代码
with recursive cte as (
	select id, name, cast('0' as char(255)) as path
	from goods
	where gid = 0
	union all
	select goods.id, goods.name, concat(cte.path, ',', cte.id) as path
	from goods inner join cte 
	on goods.gid = cte.id
)
select * from cte;

+----+------+---------+
| id | name | path    |
+----+------+---------+
|  1 | 商品 | 0       |
|  2 | 水果 | 0,1     |
|  3 | 蔬菜 | 0,1,2   |
|  4 | 苹果 | 0,1,2   |
|  5 | 香蕉 | 0,1,2   |
|  6 | 菠菜 | 0,1,2,3 |
|  7 | 萝卜 | 0,1,2,3 |
+----+------+---------+

查询一个指定商品的所有父级商品。

sql 复制代码
with recursive cte as (
	select id, name, gid 
	from goods 
	where id = 7
	union all
	select goods.id, goods.name, goods.gid 
	from goods inner join cte 
	on cte.gid = goods.id
)
select * from cte;

+----+------+-----+
| id | name | gid |
+----+------+-----+
|  7 | 萝卜 |   3 |
|  3 | 蔬菜 |   2 |
|  2 | 水果 |   1 |
|  1 | 商品 |   0 |
+----+------+-----+

11、综合案例

employee、dept表结构以及表中的记录如表所示。

创建数据表employee和dept。

sql 复制代码
create table dept(
	d_no int(11) not null primary key auto_increment,
	d_name varchar(50),
	d_location varchar(100)
);

由于employee表dept_no依赖于父表dept的主键d_no,因此需要先创建dept表,然后创建employee表。

sql 复制代码
create table employee(
	e_no int(11) not null primary key,
	e_name varchar(50) not null,
	e_gender char(2) not null,
	dept_no int(11) not null,
	e_job varchar(50) not null,
	e_salary int(11) not null,
	hireDate date,
	constraint dno_fk foreign key(dept_no) references dept(d_no)
);

将指定记录分别插入两个表中。

sql 复制代码
insert into dept values
(10, 'ACCOUNTING', 'ShangHai'),
(20, 'RESEARCH', 'BeiJing'),
(30, 'SALES', 'ShenZhen'),
(40, 'OPERATIONS', 'FuJian');


insert into employee values
(1001, 'SMITH', 'm', 20, 'CLERK', 800, '2005-11-12'),
(1002, 'ALLEN', 'f', 30, 'SALESMAN', 1600, '2003-05-12'),
(1003, 'WARD', 'f', 30, 'SALESMAN', 1250, '2003-05-12'),
(1004, 'JONES', 'm', 20, 'MANAGER', 2975, '1998-05-18'),
(1005, 'MARTIN', 'm', 30, 'SALESMAN', 1250, '2001-06-12'),
(1006, 'BLACK', 'f', 30, 'MANAGER', 2850, '1997-02-15'),
(1007, 'CLARK', 'm', 10, 'MANAGER', 2450, '2002-09-12'),
(1008, 'SCOTT', 'm', 20, 'ANALYST', 3000, '2003-05-12'),
(1009, 'KING', 'f', 10, 'PRESIDENT', 5000, '1995-01-01'),
(1010, 'TURNER', 'f', 30, 'SALESMAN', 1500, '1997-10-12'),
(1011, 'ADAMS', 'm', 20, 'CLERK', 1100, '1999-10-05'),
(1012, 'JAMES', 'm', 30, 'CLERK', 950, '2008-06-15');

在employee表中,查询所有记录的e_no、e_name和e_salary字段值。

sql 复制代码
SELECT e_no, e_name, e_salary 
FROM employee;

+------+--------+----------+
| e_no | e_name | e_salary |
+------+--------+----------+
| 1001 | SMITH  |      800 |
| 1002 | ALLEN  |     1600 |
| 1003 | WARD   |     1250 |
| 1004 | JONES  |     2975 |
| 1005 | MARTIN |     1250 |
| 1006 | BLACK  |     2850 |
| 1007 | CLARK  |     2450 |
| 1008 | SCOTT  |     3000 |
| 1009 | KING   |     5000 |
| 1010 | TURNER |     1500 |
| 1011 | ADAMS  |     1100 |
| 1012 | JAMES  |      950 |
+------+--------+----------+

在employee表中,查询dept_no等于10和20的所有记录。

sql 复制代码
SELECT * FROM employee WHERE dept_no IN (10, 20);

+------+--------+----------+---------+-----------+----------+------------+
| e_no | e_name | e_gender | dept_no | e_job     | e_salary | hireDate   |
+------+--------+----------+---------+-----------+----------+------------+
| 1007 | CLARK  | m        |      10 | MANAGER   |     2450 | 2002-09-12 |
| 1009 | KING   | f        |      10 | PRESIDENT |     5000 | 1995-01-01 |
| 1001 | SMITH  | m        |      20 | CLERK     |      800 | 2005-11-12 |
| 1004 | JONES  | m        |      20 | MANAGER   |     2975 | 1998-05-18 |
| 1008 | SCOTT  | m        |      20 | ANALYST   |     3000 | 2003-05-12 |
| 1011 | ADAMS  | m        |      20 | CLERK     |     1100 | 1999-10-05 |
+------+--------+----------+---------+-----------+----------+------------+

在employee表中,查询工资范围在800~2500之间的员工信息。

sql 复制代码
SELECT * 
FROM employee 
WHERE e_salary BETWEEN 800 AND 2500;

+------+--------+----------+---------+----------+----------+------------+
| e_no | e_name | e_gender | dept_no | e_job    | e_salary | hireDate   |
+------+--------+----------+---------+----------+----------+------------+
| 1001 | SMITH  | m        |      20 | CLERK    |      800 | 2005-11-12 |
| 1002 | ALLEN  | f        |      30 | SALESMAN |     1600 | 2003-05-12 |
| 1003 | WARD   | f        |      30 | SALESMAN |     1250 | 2003-05-12 |
| 1005 | MARTIN | m        |      30 | SALESMAN |     1250 | 2001-06-12 |
| 1007 | CLARK  | m        |      10 | MANAGER  |     2450 | 2002-09-12 |
| 1010 | TURNER | f        |      30 | SALESMAN |     1500 | 1997-10-12 |
| 1011 | ADAMS  | m        |      20 | CLERK    |     1100 | 1999-10-05 |
| 1012 | JAMES  | m        |      30 | CLERK    |      950 | 2008-06-15 |
+------+--------+----------+---------+----------+----------+------------+

在employee表中,查询部门编号为20的部门中的员工信息。

sql 复制代码
SELECT * 
FROM employee 
WHERE dept_no = 20;

+------+--------+----------+---------+---------+----------+------------+
| e_no | e_name | e_gender | dept_no | e_job   | e_salary | hireDate   |
+------+--------+----------+---------+---------+----------+------------+
| 1001 | SMITH  | m        |      20 | CLERK   |      800 | 2005-11-12 |
| 1004 | JONES  | m        |      20 | MANAGER |     2975 | 1998-05-18 |
| 1008 | SCOTT  | m        |      20 | ANALYST |     3000 | 2003-05-12 |
| 1011 | ADAMS  | m        |      20 | CLERK   |     1100 | 1999-10-05 |
+------+--------+----------+---------+---------+----------+------------+

在employee表中,查询每个部门最高工资的员工信息。

sql 复制代码
SELECT dept_no, MAX(e_salary) 
FROM employee 
GROUP BY dept_no;

+---------+---------------+
| dept_no | MAX(e_salary) |
+---------+---------------+
|      10 |          5000 |
|      20 |          3000 |
|      30 |          2850 |
+---------+---------------+

查询员工BLAKE所在部门和部门所在地。

sql 复制代码
select d_no, d_location
from dept 
where d_no = (
	select dept_no 
	from employee 
	where e_name = 'BLAKE'
);

使用连接查询,查询所有员工的部门和部门信息。

sql 复制代码
select e_no, e_name, dept_no, d_name, d_location
from employee, dept
where dept.d_no = employee.dept_no;

+------+--------+---------+------------+------------+
| e_no | e_name | dept_no | d_name     | d_location |
+------+--------+---------+------------+------------+
| 1007 | CLARK  |      10 | ACCOUNTING | ShangHai   |
| 1009 | KING   |      10 | ACCOUNTING | ShangHai   |
| 1001 | SMITH  |      20 | RESEARCH   | BeiJing    |
| 1004 | JONES  |      20 | RESEARCH   | BeiJing    |
| 1008 | SCOTT  |      20 | RESEARCH   | BeiJing    |
| 1011 | ADAMS  |      20 | RESEARCH   | BeiJing    |
| 1002 | ALLEN  |      30 | SALES      | ShenZhen   |
| 1003 | WARD   |      30 | SALES      | ShenZhen   |
| 1005 | MARTIN |      30 | SALES      | ShenZhen   |
| 1006 | BLACK  |      30 | SALES      | ShenZhen   |
| 1010 | TURNER |      30 | SALES      | ShenZhen   |
| 1012 | JAMES  |      30 | SALES      | ShenZhen   |
+------+--------+---------+------------+------------+

在employee表中,计算每个部门各有多少名员工。

sql 复制代码
select dept_no, count(*)
from employee
group by dept_no;

+---------+----------+
| dept_no | count(*) |
+---------+----------+
|      10 |        2 |
|      20 |        4 |
|      30 |        6 |
+---------+----------+

在employee表中,计算不同类型职工的总工资数。

sql 复制代码
select e_job, sum(e_salary)
from employee
group by e_job;

+-----------+---------------+
| e_job     | sum(e_salary) |
+-----------+---------------+
| CLERK     |          2850 |
| SALESMAN  |          5600 |
| MANAGER   |          8275 |
| ANALYST   |          3000 |
| PRESIDENT |          5000 |
+-----------+---------------+

在employee表中,计算不同部门的平均工资。

sql 复制代码
select dept_no, avg(e_salary)
from employee
group by dept_no;

+---------+---------------+
| dept_no | avg(e_salary) |
+---------+---------------+
|      10 |     3725.0000 |
|      20 |     1968.7500 |
|      30 |     1566.6667 |
+---------+---------------+

在employee表中,查询工资低于1500的员工信息。

sql 复制代码
SELECT * 
FROM employee 
WHERE e_salary < 1500;

+------+--------+----------+---------+----------+----------+------------+
| e_no | e_name | e_gender | dept_no | e_job    | e_salary | hireDate   |
+------+--------+----------+---------+----------+----------+------------+
| 1001 | SMITH  | m        |      20 | CLERK    |      800 | 2005-11-12 |
| 1003 | WARD   | f        |      30 | SALESMAN |     1250 | 2003-05-12 |
| 1005 | MARTIN | m        |      30 | SALESMAN |     1250 | 2001-06-12 |
| 1011 | ADAMS  | m        |      20 | CLERK    |     1100 | 1999-10-05 |
| 1012 | JAMES  | m        |      30 | CLERK    |      950 | 2008-06-15 |
+------+--------+----------+---------+----------+----------+------------+

在employee表中,将查询记录先按部门编号由高到低排列,再按员工工资由高到低排列。

sql 复制代码
select e_name, dept_no, e_salary
from employee 
order by dept_no desc, e_salary desc;

+--------+---------+----------+
| e_name | dept_no | e_salary |
+--------+---------+----------+
| BLACK  |      30 |     2850 |
| ALLEN  |      30 |     1600 |
| TURNER |      30 |     1500 |
| WARD   |      30 |     1250 |
| MARTIN |      30 |     1250 |
| JAMES  |      30 |      950 |
| SCOTT  |      20 |     3000 |
| JONES  |      20 |     2975 |
| ADAMS  |      20 |     1100 |
| SMITH  |      20 |      800 |
| KING   |      10 |     5000 |
| CLARK  |      10 |     2450 |
+--------+---------+----------+

在employee表中,查询员工姓名以字母'A'或'S'开头的员工的信息。

sql 复制代码
select * from employee
where e_name regexp '^[as]';

+------+--------+----------+---------+----------+----------+------------+
| e_no | e_name | e_gender | dept_no | e_job    | e_salary | hireDate   |
+------+--------+----------+---------+----------+----------+------------+
| 1001 | SMITH  | m        |      20 | CLERK    |      800 | 2005-11-12 |
| 1002 | ALLEN  | f        |      30 | SALESMAN |     1600 | 2003-05-12 |
| 1008 | SCOTT  | m        |      20 | ANALYST  |     3000 | 2003-05-12 |
| 1011 | ADAMS  | m        |      20 | CLERK    |     1100 | 1999-10-05 |
+------+--------+----------+---------+----------+----------+------------+

在employee表中,查询到目前为止工龄大于等于18年的员工信息。

sql 复制代码
select * from employee
where year(curdate()) - year(hireDate) >= 18;

+------+--------+----------+---------+-----------+----------+------------+
| e_no | e_name | e_gender | dept_no | e_job     | e_salary | hireDate   |
+------+--------+----------+---------+-----------+----------+------------+
| 1001 | SMITH  | m        |      20 | CLERK     |      800 | 2005-11-12 |
| 1002 | ALLEN  | f        |      30 | SALESMAN  |     1600 | 2003-05-12 |
| 1003 | WARD   | f        |      30 | SALESMAN  |     1250 | 2003-05-12 |
| 1004 | JONES  | m        |      20 | MANAGER   |     2975 | 1998-05-18 |
| 1005 | MARTIN | m        |      30 | SALESMAN  |     1250 | 2001-06-12 |
| 1006 | BLACK  | f        |      30 | MANAGER   |     2850 | 1997-02-15 |
| 1007 | CLARK  | m        |      10 | MANAGER   |     2450 | 2002-09-12 |
| 1008 | SCOTT  | m        |      20 | ANALYST   |     3000 | 2003-05-12 |
| 1009 | KING   | f        |      10 | PRESIDENT |     5000 | 1995-01-01 |
| 1010 | TURNER | f        |      30 | SALESMAN  |     1500 | 1997-10-12 |
| 1011 | ADAMS  | m        |      20 | CLERK     |     1100 | 1999-10-05 |
| 1012 | JAMES  | m        |      30 | CLERK     |      950 | 2008-06-15 |
+------+--------+----------+---------+-----------+----------+------------+