错位的梦寐

MySQL 操作表中数据

2020-03-04


关系数据库

关系数据库(Relational database)是指基于关系模型的数据库。关系模型由关系数据结构(二维表)、关系操作集合、关系完整性约束三部分组成。

常见的数据操作包括增加(Create)、查询(Retrieve)、更新(Update)以及删除(Delete),或者统称为增删改查(CRUD)。

为了维护数据的完整性和精确性,或者为了实现业务需求,SQL 标准定义了 6 种完整性约束。

  • 非空约束(NOT NULL),用于确保字段不会出现空值。例如学生信息表中,学生的姓名、出生日期、性别等一定要有数据。
  • 唯一约束(UNIQUE),用于确保字段中的值不会重复。例如每个学生的身份证、手机号等需要唯一。
  • 主键约束(Primary Key),用 于唯一标识表中的每一行数据。例如学生信息表中,学号通常作为主键。主键字段不能为空并且唯一,每个表可以有且只能有一个主键。
  • 外键约束(Foreign Key),用于建立两个表之间的参照完整性。例如学生属于班级,学生信息表中的班级字段是一个外键,引用了班级表的主键。对于外键引用,被引用的数据必须存在,学生不可能属于一个不存在的班级。
  • 检查约束(CHECK)可以定义更多的业务规则。例如,性别的取值只能为“男”或“女”,用户名必须大写等;
  • 默认值(DEFAULT)用于为字段提供默认的数据。例如,玩家注册时的级别默认为 1 级。

SQL 语句分类

常见的 SQL 语句包含以下类别:

  • DQL(data query language),数据查询语言;主要是SELECT语句,用于查询数据库中的数据和信息。
  • DML(data manipulation language),数据操作语言;主要用于对数据库进行数据的增加、修改和删除,包括INSERTUPDATEDELETEMERGE 等语句。
  • DDL(data definition language),数据定义语言;主要用于定义数据库中的模式对象,例如表或索引,包括CREATEALTERDROP等等
  • TCL(transaction control language),事务控制语言;用于管理数据库事务,主要包含BEGIN TRANSACTIONCOMMITROLLBACKSAVEPOINT等语句。
  • DCL(data control language),数据控制语言;用于控制数据的访问权限,主要有GRANTREVOKE等语句。

操作表中数据

SELECT:数据表查询语句

SELECT EXPR,...FROM tbl_name

SELECT select_expr [,select_expr...]
[
FROM tbl_references
[WHERE where_condition]
[GROUP BY {col_name | position} [ASC | DESC],...]
[HAVING where_condition]
[ORDER BY {col_name | expo | position}  [ASC | DESC],...]
[LIMIT {[offset,] row_count | row_count OFFSET offset}]
]

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

  • {*|<字段列名>}包含星号通配符的字段列表,表示所要查询字段的名称。
  • <表 1>,<表 2>…,表 1 和表 2 表示查询数据的来源,可以是单个或多个。
  • WHERE <表达式>是可选项,如果选择该项,将限定查询数据必须满足该查询条件。
  • GROUP BY< 字段 >,该子句告诉 MySQL 如何显示查询出来的数据,并按照指定的字段分组。
  • [ORDER BY< 字段 >],该子句告诉 MySQL 按什么样的顺序显示查询出来的数据,可以进行的排序有升序(ASC)和降序(DESC),默认情况下是升序。
  • [LIMIT[,]],该子句告诉 MySQL 每次显示查询出来的数据条数。

查询表达式的每个表达式表示想要查找的一列,必须有至少一个。多个列之间以英文逗号分开, 查询 id, username 两列。

select id, username from users;

在使用多表连接时,可能会出现不同的表中存在名称相同的字段,如果直接写字段,分不清到底是哪张数据表的字段。在字段名前加上数据表可以分辨出隶属于哪张数据表。

select users,id, users, username from users;

星号*号表示所有的列。tbl_name.* 可以表示命名表的所有列 .

select * from users;

查询表达式可以使用 [AS] alias_name 为其赋予别名,别名可用于 GROUP BY, ORDER BY, HAVING 字句 .

select id AS userId, username AS uname from users;

DISTINCT 去重

使用DISTINCT过滤重复数据

DISTINCT 关键字的主要作用就是对数据表中一个或多个字段重复的数据进行过滤,只返回其中的一条数据给用户。

SELECT DISTINCT <字段名> FROM <表名>;

其中,“字段名”为需要消除重复记录的字段名称,多个字段时用逗号隔开。

使用 DISTINCT 关键字时需要注意以下几点:

  • DISTINCT 关键字只能在 SELECT 语句中使用。
  • 在对一个或多个字段去重时,DISTINCT 关键字必须在所有字段的最前面。
  • 如果 DISTINCT 关键字后有多个字段,则会对多个字段进行组合去重,也就是说,只有多个字段组合起来完全是一样的情况下才会被去重。

在 SQL 中,提供了DISTINCT关键字,用于删除查询结果中的重复值。例如:

SELECT DISTINCT
		first_name
	FROM employees;

DISTINCT位于SELECT之后,可以基于多个列值进行查重操作,通用语法如下:

SELECT DISTINCT
		column1,
		column2,
		...
	FROM table;

为了消除重复值,数据库系统需要对结果进行排序,然后扫描重复值;因此,大量数据的重复值处理会降低查询的速度。

除了DISTINCT之外,另一个关键字是ALL,它不会排除重复的结果,而是显示所有数据:

SELECT [ALL | DISTINCT]
		column1,
		column2,
		...
	FROM table;

如果不指定,默认值为ALL

LIMIT: 限制查询结果的条数

限制查询结果的条数

SQL 标准中对查询结果进行限制的OFFSET子句和FETCH子句

[LIMIT {[offset,] row_count | row_count OFFSET offset}]

指定初始位置

LIMIT 初始位置,记录数

其中,“初始位置”表示从哪条记录开始显示;“记录数”表示显示记录的条数。第一条记录的位置是 0,第二条记录的位置是 1。后面的记录依次类推。

不指定初始位置

LIMIT 记录数

其中,“记录数”表示显示记录的条数。如果“记录数”的值小于查询结果的总数,则会从第一条记录开始,显示指定条数的记录。如果“记录数”的值大于查询结果的总数,则会直接显示查询出来的所有记录。

限制查询结果 (LIMIT) 默认情况下,返回所有查找到的结果

如果 LIMIT 后面只有一个数字,表示从第一条开始返回,并返回相应数字个数的记录

select * from usres limit 2;

从第一条开始,返回两条记录。

** LIMITOFFSET组合使用**

LIMIT 记录数 OFFSET 初始位置

参数和 LIMIT 语法中参数含义相同,“初始位置”指定从哪条记录开始显示;“记录数”表示显示记录的条数。

SELECT 语句默认从0开始编号,如果想从第三条开始返回,则需要 offset 参数和 row_count 参数一起使用

select * from usres limit 2,2;

SELECT first_name, last_name, salary
  FROM employees
 ORDER BY salary DESC
 LIMIT 5, 10; -- return from 5th to 14th
 -- LIMIT 10 OFFSET 5;

Top-N 查询

由于不同数据库的实现存在较大差异, 先以 Oracle 12c 语法为例

SELECT first_name, last_name, salary
	FROM employees
	ORDER BY salary DESC
	FETCH FIRST 10 ROWS ONLY;

以上查询返回薪水最高的前 10 位员工。首先,ORDER BY子句定义了按照薪水从高到低排序;然后FETCH子句指定了只返回前 10 条记录。

分页查询

考虑另一个场景,假如应用提供了分页显示的功能,每页显示 10 条记录,点击下一页时,需要显示第 11 到第 20 条记录。

SELECT first_name, last_name, salary
  FROM employees
 ORDER BY salary DESC
OFFSET 10 ROWS
 FETCH FIRST 10 ROWS ONLY;

SQL 标准中的完整定义:

SELECT column1, column2, ...
  FROM table
[WHERE conditions]
[ORDER BY column1 ASC, column2 DESC, ...]
[OFFSET m {ROW | ROWS}]
[FETCH { FIRST | NEXT } [ num_rows | n PERCENT ] { ROW | ROWS } { ONLY | WITH TIES }];

其中,OFFSET 表示偏移量,即从第 m+1 行开始返回;如果不指定,从第 1 行开始返回。

FETCH 用于指定返回多少行,FIRSTNEXT 等价;num_rows 表示行数,n PERCENT 表示即按照百分比指定行数,ROWROWS 等价;ONLYWITH TIES 的差别在于,如果最后存在更多排名相同的数据行,WITH TIES会返回更多的数据。

以下查询按照百分比返回前10%的数据:

SELECT first_name, last_name, salary
	FROM employees
	ORDER BY salary DESC
	FETCH FIRST 10 PERCENT ROWS ONLY;

ORDER BY:对查询结果排序

ORDER BY 关键字主要用来将查询结果中的数据按照一定的顺序进行排序。其语法格式如下:

ORDER BY <字段名> [ASC|DESC]

语法说明如下。

  • 字段名:表示需要排序的字段名称,多个字段时用逗号隔开。
  • ASC|DESC:ASC表示字段按升序排序;DESC表示字段按降序排序。其中ASC为默认值。

使用 ORDER BY 关键字应该注意以下几个方面:

  • ORDER BY 关键字后可以跟子查询(关于子查询后面教程会详细讲解,这里了解即可)。
  • 当排序的字段中存在空值时,ORDER BY 会将该空值作为最小值来对待。
  • ORDER BY 指定多个字段进行排序时,MySQL 会按照字段的顺序从左到右依次进行排序。

单字段排序

 SELECT * FROM tb_students_info ORDER BY height

多字段排序

SELECT name,height FROM tb_students_info ORDER BY height,name;

注意:在对多个字段进行排序时,排序的第一个字段必须有相同的值,才会对第二个字段进行排序。如果第一个字段数据中所有的值都是唯一的,MySQL 将不再对第二个字段进行排序。

默认情况下,查询数据按字母升序进行排序(A~Z),但数据的排序并不仅限于此,还可以使用 ORDER BY 中的 DESC 对查询结果进行降序排序(Z~A)。

SELECT name,height FROM tb_student_info ORDER BY height DESC,name ASC;

按列的位置排序

SELECT name,height FROM tb_student_info ORDER BY 3 DESC,name ASC;

数字 3 表示按表的第三列的字段排序

注意:

  • 不支持大小写的排序
  • 在指定一条 order by 子句时,应该保证它是 select 语句中最后一条子句。

WHERE:条件查询数据

在 MySQL 中,如果需要有条件的从数据表中查询数据,可以使用 WHERE 关键字来指定查询条件。

使用 WHERE 关键字的语法格式如下:

WHERE 查询条件

查询条件可以是:

  • 带比较运算符和逻辑运算符的查询条件
  • BETWEEN AND 关键字的查询条件
  • IS NULL 关键字的查询条件
  • IN 关键字的查询条件
  • LIKE 关键字的查询条件

单一条件的查询语句

单一条件指的是在 WHERE 关键字后只有一个查询条件。

在 tb_students_info 数据表中查询身高为 170cm 的学生姓名,SQL 语句和运行结果如下。

mysql> SELECT name,height FROM tb_students_info
    -> WHERE height=170;

多条件的查询语句

在 WHERE 关键词后可以有多个查询条件,这样能够使查询结果更加精确。多个查询条件时用逻辑运算符 AND(&&)、OR(||)或 XOR 隔开。

  • AND:记录满足所有查询条件时,才会被查询出来。
  • OR:记录满足任意一个查询条件时,才会被查询出来。
  • XOR:记录满足其中一个条件,并且不满足另一个条件时,才会被查询出来。

在 tb_students_info 表中查询 age 大于 21,并且 height 大于等于 175 的学生信息,SQL 语句和运行结果如下。

mysql> SELECT name,age,height FROM tb_students_info 
    -> WHERE age>21 AND height>=175;

注意事项

  • 同时使用 order by where 子句时,应该让 order by 位于where 之后。

LIKE:模糊查询

在 MySQL 中,LIKE 关键字主要用于搜索匹配字段中的指定内容。其语法格式如下:

[NOT] LIKE  '字符串'

其中:

  • NOT :可选参数,字段中的内容与指定的字符串不匹配时满足条件。
  • 字符串:指定用来匹配的字符串。“字符串”可以是一个很完整的字符串,也可以包含通配符。

LIKE 关键字支持百分号“%”和下划线“_”通配符。

带有“%”通配符的查询

%”是 MySQL 中最常用的通配符,它能代表任何长度的字符串,字符串的长度可以为 0。例如,a%b表示以字母 a 开头,以字母 b 结尾的任意长度的字符串。该字符串可以代表 ab、acb、accb、accrb 等字符串。

在 tb_students_info 表中,查找所有以字母“T”开头的学生姓名,SQL 语句和运行结果如下。

mysql> SELECT name FROM tb_students_info
    -> WHERE name LIKE 'T%';

带有“_”通配符的查询

“_”只能代表单个字符,字符的长度不能为 0。例如,a_b可以代表 acb、adb、aub 等字符串。

在 tb_students_info 表中,查找所有以字母“y”结尾,且“y”前面只有 4 个字母的学生姓名,SQL 语句和运行结果如下。

mysql> SELECT name FROM tb_students_info
    -> WHERE name LIKE '____y';

LIKE 区分大小写

默认情况下,LIKE 关键字匹配字符的时候是不区分大小写的。如果需要区分大小写,可以加入 BINARY 关键字。

在 tb_students_info 表中,查找所有以字母“t”开头的学生姓名,区分大小写和不区分大小写的 SQL 语句和运行结果如下。

mysql> SELECT name FROM tb_students_info WHERE name LIKE 't%';
+--------+
| name   |
+--------+
| Thomas |
| Tom    |
+--------+
2 rows in set (0.00 sec)

mysql> SELECT name FROM tb_students_info WHERE name LIKE BINARY 't%';
Empty set (0.01 sec)

使用通配符的注意事项和技巧

下面是使用通配符的一些注意事项:

  • 注意大小写。MySQL 默认是不区分大小写的。如果区分大小写,像“Tom”这样的数据就不能被“t%”所匹配到。
  • 注意尾部空格,尾部空格会干扰通配符的匹配。例如,“T% ”就不能匹配到“Tom”。
  • 注意 NULL。“%”通配符可以到匹配任意字符,但是不能匹配 NULL。也就是说 “%”匹配不到 tb_students_info 数据表中值为 NULL 的记录。

下面是一些使用通配符要记住的技巧。

  • 不要过度使用通配符,如果其它操作符能达到相同的目的,应该使用其它操作符。因为 MySQL 对通配符的处理一般会比其他操作符花费更长的时间。
  • 在确定使用通配符后,除非绝对有必要,否则不要把它们用在字符串的开始处。把通配符置于搜索模式的开始处,搜索起来是最慢的。
  • 仔细注意通配符的位置。如果放错地方,可能不会返回想要的数据。

方括号[] 通配符

方括号 [] 通配符用来指定一个字符集,他必须匹配指定位置(通配符的位置) 的一个字符

例如,找出所有名字以 ·JM 起头的联系人,

select *
from customers
where cust_contact like '[JM]%'

[JM] 匹配方括号中任意一个字符

此通配符可以用前缀字符 ^(脱字号) 来否定

例如,查询匹配以 JM 之外的任意字符起头的联系人。

select *
from customers
where cust_contact like '[^JM]%'

BETWEEN AND:范围查询

MySQL 提供了 BETWEEN AND 关键字,用来判断字段的数值是否在指定范围内。

BETWEEN AND 需要两个参数,即范围的起始值和终止值。如果字段值在指定的范围内,则这些记录被返回。如果不在指定范围内,则不会被返回。

使用 BETWEEN AND 的基本语法格式如下:

[NOT] BETWEEN 取值1 AND 取值2

其中:

  • NOT:可选参数,表示指定范围之外的值。如果字段值不满足指定范围内的值,则这些记录被返回。
  • 取值1:表示范围的起始值。
  • 取值2:表示范围的终止值。

BETWEEN AND 和 NOT BETWEEN AND 关键字在查询指定范围内的记录时很有用。例如,查询学生的年龄段、出生日期,员工的工资水平等。

在表 tb_students_info 中查询年龄在 20 到 23 之间的学生姓名和年龄,SQL 语句和运行结果如下。

mysql> SELECT name,age FROM tb_students_info 
    -> WHERE age BETWEEN 20 AND 23;

查询结果中包含学生年龄为 20 和 23 的记录,这就说明,在 MySQL 中,BETWEEN AND 能匹配指定范围内的所有值,包括起始值和终止值

NULL:空值查询

MySQL 提供了 IS NULL 关键字,用来判断字段的值是否为空值(NULL)。空值不同于 0,也不同于空字符串

如果字段的值是空值,则满足查询条件,该记录将被查询出来。如果字段的值不是空值,则不满足查询条件。

使用 IS NULL 的基本语法格式如下:

IS [NOT] NULL

其中,“NOT”是可选参数,表示字段值不是空值时满足条件。

下面使用 IS NULL 关键字来查询 tb_students_info 表中 login_date 字段是 NULL 的记录。

mysql> SELECT `name`,`login_date` FROM tb_students_info 
    -> WHERE login_date IS NULL;
+--------+------------+
| NAME   | login_date |
+--------+------------+
| Dany   | NULL       |
| Green  | NULL       |
| Henry  | NULL       |
| Jane   | NULL       |
| Thomas | NULL       |
| Tom    | NULL       |
+--------+------------+
6 rows in set (0.01 sec)

注意:IS NULL 是一个整体,不能将 IS 换成“=”。如果将 IS 换成“=”将不能查询出任何结果,数据库系统会出现“Empty set(0.00 sec)”这样的提示。同理,IS NOT NULL 中的 IS NOT 不能换成“!=”或“<>”。

IS NOT NULL 表示查询字段值不为空的记录。

下面使用 IS NOT NULL 关键字来查询 tb_students_info 表中 login_date 字段不为空的记录。

mysql> SELECT `name`,login_date FROM tb_students_info 
    -> WHERE login_date IS NOT NULL;

GROUP BY分组查询

在 MySQL 中,GROUP BY 关键字可以根据一个或多个字段对查询结果进行分组。

GROUP BY <字段名>

其中,“字段名”表示需要分组的字段名称,多个字段时用逗号隔开。

GROUP BY单独使用

单独使用 GROUP BY 关键字时,查询结果会只显示每个分组的第一条记录。

mysql> SELECT `name`,`sex` FROM tb_students_info 
    -> GROUP BY sex;
+-------+------+
| name  | sex  |
+-------+------+
| Henry |    |
| Dany  |    |
+-------+------+
2 rows in set (0.01 sec)

GROUP BY 与 GROUP_CONCAT()

GROUP BY 关键字可以和 GROUP_CONCAT() 函数一起使用。GROUP_CONCAT() 函数会把每个分组的字段值都显示出来。

mysql> SELECT `sex`, GROUP_CONCAT(name) FROM tb_students_info 
    -> GROUP BY sex;
+------+----------------------------+
| sex  | GROUP_CONCAT(name)         |
+------+----------------------------+
|    | Henry,Jim,John,Thomas,Tom  |
|    | Dany,Green,Jane,Lily,Susan |
+------+----------------------------+
2 rows in set (0.00 sec

下面根据 tb_students_info 表中的 age 和 sex 字段进行分组查询。SQL 语句和运行结果如下:

mysql> SELECT age,sex,GROUP_CONCAT(name) FROM tb_students_info 
    -> GROUP BY age,sex;
+------+------+--------------------+
| age  | sex  | GROUP_CONCAT(name) |
+------+------+--------------------+
|   21 |    | John               |
|   22 |    | Thomas             |
|   22 |    | Jane,Lily          |
|   23 |    | Henry,Tom          |
|   23 |    | Green,Susan        |
|   24 |    | Jim                |
|   25 |    | Dany               |
+------+------+--------------------+
7 rows in set (0.00 sec)

上面实例在分组过程中,先按照 age 字段进行分组,当 age 字段值相等时,再把 age 字段值相等的记录按照 sex 字段进行分组。

多个字段分组查询时,会先按照第一个字段进行分组。如果第一个字段中有相同的值,MySQL 才会按照第二个字段进行分组。如果第一个字段中的数据都是唯一的,那么 MySQL 将不再对第二个字段进行分组。

GROUP BY 与聚合函数

在数据统计时,GROUP BY 关键字经常和聚合函数一起使用。

聚合函数包括 COUNT(),SUM(),AVG(),MAX() 和 MIN()。其中,COUNT() 用来统计记录的条数;SUM() 用来计算字段值的总和;AVG() 用来计算字段值的平均值;MAX() 用来查询字段的最大值;MIN() 用来查询字段的最小值。

下面根据 tb_students_info 表的 sex 字段进行分组查询,使用 COUNT() 函数计算每一组的记录数。SQL 语句和运行结果如下:

mysql> SELECT sex,COUNT(sex) FROM tb_students_info 
    -> GROUP BY sex;
+------+------------+
| sex  | COUNT(sex) |
+------+------------+
|    |          5 |
|    |          5 |
+------+------------+
2 rows in set (0.00 sec)

如果 group by 中嵌套了分组,数据将在最后的分组上进行汇总

GROUP BY 与 WITH ROLLUP

WITH POLLUP 关键字用来在所有记录的最后加上一条记录,这条记录是上面所有记录的总和,即统计记录数量。

下面根据 tb_students_info 表中的 sex 字段进行分组查询,并使用 WITH ROLLUP 显示记录的总和。

mysql> SELECT sex,GROUP_CONCAT(name) FROM tb_students_info 
    ->GROUP BY sex WITH ROLLUP;
+------+------------------------------------------------------+
| sex  | GROUP_CONCAT(name)                                   |
+------+------------------------------------------------------+
|    | Henry,Jim,John,Thomas,Tom                            |
|    | Dany,Green,Jane,Lily,Susan                           |
| NULL | Henry,Jim,John,Thomas,Tom,Dany,Green,Jane,Lily,Susan |
+------+------------------------------------------------------+
3 rows in set (0.00 sec)

查询结果显示,GROUP_CONCAT(name) 显示了每个分组的 name 字段值。同时,最后一条记录的 GROUP_CONCAT(name) 字段的值刚好是上面分组 name 字段值的总和。

注意事项

  • 如果 group by 中嵌套了分组,数据将在最后的分组上进行汇总
  • group by 子句必须在 where 子句之后,ordre by 子句之前。
  • 如果分组中含有 null 值的行,则将 null 作为一个分组返回。如果列中有多个 null 值,它们将分为一组。


HAVING:过滤分组

在 MySQL 中,可以使用 HAVING 关键字对分组后的数据进行过滤。

使用 HAVING 关键字的语法格式如下:

HAVING <查询条件>

HAVING 关键字和 WHERE 关键字都可以用来过滤数据,且 HAVING 支持 WHERE 关键字中所有的操作符和语法。

但是 WHERE 和 HAVING 关键字也存在以下几点差异:

  • 一般情况下,WHERE 用于过滤数据行,而 HAVING 用于过滤分组。
  • WHERE 查询条件中不可以使用聚合函数,而 HAVING 查询条件中可以使用聚合函数。
  • WHERE 在数据分组前进行过滤,而 HAVING 在数据分组后进行过滤 。
  • WHERE 针对数据库文件进行过滤,而 HAVING 针对查询结果进行过滤。也就是说,WHERE 根据数据表中的字段直接进行过滤,而 HAVING 是根据前面已经查询出的字段进行过滤。
  • WHERE 查询条件中不可以使用字段别名,而 HAVING 查询条件中可以使用字段别名。

例 1

分别使用 HAVING 和 WHERE 关键字查询出 tb_students_info 表中身高大于 150 的学生姓名,性别和身高。SQL 语句和运行结果如下。

mysql> SELECT name,sex,height FROM tb_students_info 
    -> HAVING height>150;
+--------+------+--------+
| name   | sex  | height |
+--------+------+--------+
| Dany   |    |    160 |
| Green  |    |    158 |
| Henry  |    |    185 |
| Jane   |    |    162 |
| Jim    |    |    175 |
| John   |    |    172 |
| Lily   |    |    165 |
| Susan  |    |    170 |
| Thomas |    |    178 |
| Tom    |    |    165 |
+--------+------+--------+
10 rows in set (0.00 sec)

mysql> SELECT name,sex,height FROM tb_students_info 
    -> WHERE height>150;
+--------+------+--------+
| name   | sex  | height |
+--------+------+--------+
| Dany   |    |    160 |
| Green  |    |    158 |
| Henry  |    |    185 |
| Jane   |    |    162 |
| Jim    |    |    175 |
| John   |    |    172 |
| Lily   |    |    165 |
| Susan  |    |    170 |
| Thomas |    |    178 |
| Tom    |    |    165 |
+--------+------+--------+
10 rows in set (0.00 sec)

上述实例中,因为在 SELECT 关键字后已经查询出了 height 字段,所以 HAVING 和 WHERE 都可以使用。但是如果 SELECT 关键字后没有查询出 height 字段,MySQL 就会报错。

例 2

使用 HAVING 和 WHERE 关键字分别查询出 tb_students_info 表中身高大于 150 的学生姓名和性别(与例 1 相比,这次没有查询 height 字段)。SQL 语句和运行结果如下。

mysql> SELECT name,sex FROM tb_students_info 
    -> HAVING height>150;
+--------+------+
| name   | sex  |
+--------+------+
| Dany   |    |
| Green  |    |
| Henry  |    |
| Jane   |    |
| Jim    |    |
| John   |    |
| Lily   |    |
| Susan  |    |
| Thomas |    |
| Tom    |    |
+--------+------+
10 rows in set (0.00 sec)

mysql> SELECT name,sex FROM tb_students_info HAVING height>150;
ERROR 1054 (42S22): Unknown column 'height' in 'having clause'

由结果可以看出,如果 SELECT 关键字后没有查询出 HAVING 查询条件中使用的 height 字段,MySQL 会提示错误信息:“having子句”中的列“height”未知”。

例 3

根据 height 字段对 tb_students_info 表中的数据进行分组,并使用 HAVING 和 WHERE 关键字分别查询出分组后平均身高大于 170 的学生姓名、性别和身高。SQL 语句和运行结果如下。

mysql> SELECT GROUP_CONCAT(name),sex,height FROM tb_students_info 
    -> GROUP BY height 
    -> HAVING AVG(height)>170;
+--------------------+------+--------+
| GROUP_CONCAT(name) | sex  | height |
+--------------------+------+--------+
| John               |    |    172 |
| Jim                |    |    175 |
| Thomas             |    |    178 |
| Henry              |    |    185 |
+--------------------+------+--------+
4 rows in set (0.00 sec)

mysql> SELECT GROUP_CONCAT(name),sex,height FROM tb_students_info WHERE AVG(height)>170 GROUP BY height;
ERROR 1111 (HY000): Invalid use of group function

由结果可以看出,如果在 WHERE 查询条件中使用聚合函数,MySQL 会提示错误信息:无效使用组函数。

CROSS JOIN:交叉连接

前面所讲的查询语句都是针对一个表的,但是在关系型数据库中,表与表之间是有联系的,所以在实际应用中,经常使用多表查询。多表查询就是同时查询两个或两个以上的表。

交叉连接(CROSS JOIN)一般用来返回连接表的笛卡尔积。

交叉连接的语法格式如下:

SELECT <字段名> FROM <1> CROSS JOIN <2> [WHERE子句]

-- 或
SELECT <字段名> FROM <1>, <2> [WHERE子句] 

语法说明如下:

  • 字段名:需要查询的字段名称。
  • <表1><表2>:需要交叉连接的表名。
  • WHERE 子句:用来设置交叉连接的查询条件。

当连接的表之间没有关系时,我们会省略掉 WHERE 子句,这时返回结果就是两个表的笛卡尔积,返回结果数量就是两个表的数据行相乘。需要注意的是,如果每个表有 1000 行,那么返回结果的数量就有 1000×1000 = 1000000 行,数据量是非常巨大的。

交叉连接可以查询两个或两个以上的表,为了让读者更好的理解,下面先讲解两个表的交叉连接查询。

例 1

查询学生信息表和科目信息表,并得到一个笛卡尔积。

为了方便观察学生信息表和科目表交叉连接后的运行结果,我们先分别查询出这两个表的数据,再进行交叉连接查询。

1)查询 tb_students_info 表中的数据,SQL 语句和运行结果如下:

mysql> SELECT * FROM tb_students_info;
+----+--------+------+------+--------+-----------+
| id | name   | age  | sex  | height | course_id |
+----+--------+------+------+--------+-----------+
|  1 | Dany   |   25 |    |    160 |         1 |
|  2 | Green  |   23 |    |    158 |         2 |
|  3 | Henry  |   23 |    |    185 |         1 |
|  4 | Jane   |   22 |    |    162 |         3 |
|  5 | Jim    |   24 |    |    175 |         2 |
|  6 | John   |   21 |    |    172 |         4 |
|  7 | Lily   |   22 |    |    165 |         4 |
|  8 | Susan  |   23 |    |    170 |         5 |
|  9 | Thomas |   22 |    |    178 |         5 |
| 10 | Tom    |   23 |    |    165 |         5 |
+----+--------+------+------+--------+-----------+
10 rows in set (0.00 sec)

2)查询 tb_course 表中的数据,SQL 语句和运行结果如下:

mysql> SELECT * FROM tb_course;
+----+-------------+
| id | course_name |
+----+-------------+
|  1 | Java        |
|  2 | MySQL       |
|  3 | Python      |
|  4 | Go          |
|  5 | C++         |
+----+-------------+
5 rows in set (0.00 sec)

3)使用 CROSS JOIN 查询出两张表中的笛卡尔积,SQL 语句和运行结果如下:

mysql> SELECT * FROM tb_course CROSS JOIN tb_students_info;
+----+-------------+----+--------+------+------+--------+-----------+
| id | course_name | id | name   | age  | sex  | height | course_id |
+----+-------------+----+--------+------+------+--------+-----------+
|  1 | Java        |  1 | Dany   |   25 |    |    160 |         1 |
|  2 | MySQL       |  1 | Dany   |   25 |    |    160 |         1 |
|  3 | Python      |  1 | Dany   |   25 |    |    160 |         1 |
|  4 | Go          |  1 | Dany   |   25 |    |    160 |         1 |
|  5 | C++         |  1 | Dany   |   25 |    |    160 |         1 |
|  1 | Java        |  2 | Green  |   23 |    |    158 |         2 |
|  2 | MySQL       |  2 | Green  |   23 |    |    158 |         2 |
|  3 | Python      |  2 | Green  |   23 |    |    158 |         2 |
|  4 | Go          |  2 | Green  |   23 |    |    158 |         2 |
|  5 | C++         |  2 | Green  |   23 |    |    158 |         2 |
|  1 | Java        |  3 | Henry  |   23 |    |    185 |         1 |
|  2 | MySQL       |  3 | Henry  |   23 |    |    185 |         1 |
|  3 | Python      |  3 | Henry  |   23 |    |    185 |         1 |
|  4 | Go          |  3 | Henry  |   23 |    |    185 |         1 |
|  5 | C++         |  3 | Henry  |   23 |    |    185 |         1 |
|  1 | Java        |  4 | Jane   |   22 |    |    162 |         3 |
|  2 | MySQL       |  4 | Jane   |   22 |    |    162 |         3 |
|  3 | Python      |  4 | Jane   |   22 |    |    162 |         3 |
|  4 | Go          |  4 | Jane   |   22 |    |    162 |         3 |
|  5 | C++         |  4 | Jane   |   22 |    |    162 |         3 |
|  1 | Java        |  5 | Jim    |   24 |    |    175 |         2 |
|  2 | MySQL       |  5 | Jim    |   24 |    |    175 |         2 |
|  3 | Python      |  5 | Jim    |   24 |    |    175 |         2 |
|  4 | Go          |  5 | Jim    |   24 |    |    175 |         2 |
|  5 | C++         |  5 | Jim    |   24 |    |    175 |         2 |
|  1 | Java        |  6 | John   |   21 |    |    172 |         4 |
|  2 | MySQL       |  6 | John   |   21 |    |    172 |         4 |
|  3 | Python      |  6 | John   |   21 |    |    172 |         4 |
|  4 | Go          |  6 | John   |   21 |    |    172 |         4 |
|  5 | C++         |  6 | John   |   21 |    |    172 |         4 |
|  1 | Java        |  7 | Lily   |   22 |    |    165 |         4 |
|  2 | MySQL       |  7 | Lily   |   22 |    |    165 |         4 |
|  3 | Python      |  7 | Lily   |   22 |    |    165 |         4 |
|  4 | Go          |  7 | Lily   |   22 |    |    165 |         4 |
|  5 | C++         |  7 | Lily   |   22 |    |    165 |         4 |
|  1 | Java        |  8 | Susan  |   23 |    |    170 |         5 |
|  2 | MySQL       |  8 | Susan  |   23 |    |    170 |         5 |
|  3 | Python      |  8 | Susan  |   23 |    |    170 |         5 |
|  4 | Go          |  8 | Susan  |   23 |    |    170 |         5 |
|  5 | C++         |  8 | Susan  |   23 |    |    170 |         5 |
|  1 | Java        |  9 | Thomas |   22 |    |    178 |         5 |
|  2 | MySQL       |  9 | Thomas |   22 |    |    178 |         5 |
|  3 | Python      |  9 | Thomas |   22 |    |    178 |         5 |
|  4 | Go          |  9 | Thomas |   22 |    |    178 |         5 |
|  5 | C++         |  9 | Thomas |   22 |    |    178 |         5 |
|  1 | Java        | 10 | Tom    |   23 |    |    165 |         5 |
|  2 | MySQL       | 10 | Tom    |   23 |    |    165 |         5 |
|  3 | Python      | 10 | Tom    |   23 |    |    165 |         5 |
|  4 | Go          | 10 | Tom    |   23 |    |    165 |         5 |
|  5 | C++         | 10 | Tom    |   23 |    |    165 |         5 |
+----+-------------+----+--------+------+------+--------+-----------+
50 rows in set (0.00 sec)

由运行结果可以看出,tb_course 和 tb_students_info 表交叉连接查询后,返回了 50 条记录。可以想象,当表中的数据较多时,得到的运行结果会非常长,而且得到的运行结果也没太大的意义。所以,通过交叉连接的方式进行多表查询的这种方法并不常用,我们应该尽量避免这种查询。

例 2

查询 tb_course 表中的 id 字段和 tb_students_info 表中的 course_id 字段相等的内容, SQL 语句和运行结果如下:

mysql> SELECT * FROM tb_course CROSS JOIN tb_students_info 
    -> WHERE tb_students_info.course_id = tb_course.id;
+----+-------------+----+--------+------+------+--------+-----------+
| id | course_name | id | name   | age  | sex  | height | course_id |
+----+-------------+----+--------+------+------+--------+-----------+
|  1 | Java        |  1 | Dany   |   25 |    |    160 |         1 |
|  2 | MySQL       |  2 | Green  |   23 |    |    158 |         2 |
|  1 | Java        |  3 | Henry  |   23 |    |    185 |         1 |
|  3 | Python      |  4 | Jane   |   22 |    |    162 |         3 |
|  2 | MySQL       |  5 | Jim    |   24 |    |    175 |         2 |
|  4 | Go          |  6 | John   |   21 |    |    172 |         4 |
|  4 | Go          |  7 | Lily   |   22 |    |    165 |         4 |
|  5 | C++         |  8 | Susan  |   23 |    |    170 |         5 |
|  5 | C++         |  9 | Thomas |   22 |    |    178 |         5 |
|  5 | C++         | 10 | Tom    |   23 |    |    165 |         5 |
+----+-------------+----+--------+------+------+--------+-----------+
10 rows in set (0.01 sec)

如果在交叉连接时使用 WHERE 子句,MySQL 会先生成两个表的笛卡尔积,然后再选择满足 WHERE 条件的记录。因此,表的数量较多时,交叉连接会非常非常慢。一般情况下不建议使用交叉连接。

在 MySQL 中,多表查询一般使用内连接和外连接,它们的效率要高于交叉连接。

笛卡尔积

笛卡尔积(Cartesian product)是指两个集合 X 和 Y 的乘积。

例如,有 A 和 B 两个集合,它们的值如下:

A = {1,2} B = {3,4,5}

集合 A×B 和 B×A 的结果集分别表示为:

A×B={(1,3), (1,4), (1,5), (2,3), (2,4), (2,5) }; B×A={(3,1), (3,2), (4,1), (4,2), (5,1), (5,2) };

以上 A×B 和 B×A 的结果就叫做两个集合的笛卡尔积。

并且,从以上结果我们可以看出:

  • 两个集合相乘,不满足交换率,即 A×B≠B×A。
  • A 集合和 B 集合的笛卡尔积是 A 集合的元素个数 × B 集合的元素个数。

多表查询遵循的算法就是以上提到的笛卡尔积,表与表之间的连接可以看成是在做乘法运算。在实际应用中,应避免使用笛卡尔积,因为笛卡尔积中容易存在大量的不合理数据,简单来说就是容易导致查询结果重复、混乱。

v2-9b8c7c295543556d21b8df426f4a8693_r

INNER JOIN:内连接

内连接(INNER JOIN)主要通过设置连接条件的方式,来移除查询结果中某些数据行的交叉连接。简单来说,就是利用条件表达式来消除交叉连接的某些数据行。

内连接使用 INNER JOIN 关键字连接两张表,并使用 ON 子句来设置连接条件。如果没有连接条件,INNER JOIN 和 CROSS JOIN 在语法上是等同的,两者可以互换。

内连接的语法格式如下:

SELECT <字段名> FROM <1> INNER JOIN <2> [ON子句]

语法说明如下。

  • 字段名:需要查询的字段名称。
  • <表1><表2>:需要内连接的表名。
  • INNER JOIN :内连接中可以省略 INNER 关键字,只用关键字 JOIN。
  • ON 子句:用来设置内连接的连接条件。

INNER JOIN 也可以使用 WHERE 子句指定连接条件,但是 INNER JOIN … ON 语法是官方的标准写法,而且 WHERE 子句在某些时候会影响查询的性能。

多个表内连接时,在 FROM 后连续使用 INNER JOIN 或 JOIN 即可。

首先有2张表

image-20200816174152450

“内连接”join,其实就是“inner join”,为了简写才写成join,两个是表示一个的,内连接,表示以两个表的交集为主,查出来是两个表有交集的部分,其余没有关联就不额外显示出来

image-20200816174321128

内连接可以查询两个或两个以上的表。为了让大家更好的理解,暂时只讲解两个表的连接查询。

例 1

在 tb_students_info 表和 tb_course 表之间,使用内连接查询学生姓名和相对应的课程名称,SQL 语句和运行结果如下。

mysql> SELECT s.name,c.course_name 
	->FROM tb_students_info s 
	->INNER JOIN tb_course c 
    -> ON s.course_id = c.id;
+--------+-------------+
| name   | course_name |
+--------+-------------+
| Dany   | Java        |
| Green  | MySQL       |
| Henry  | Java        |
| Jane   | Python      |
| Jim    | MySQL       |
| John   | Go          |
| Lily   | Go          |
| Susan  | C++         |
| Thomas | C++         |
| Tom    | C++         |
+--------+-------------+
10 rows in set (0.00 sec)

在这里的查询语句中,两个表之间的关系通过 INNER JOIN 指定,连接的条件使用 ON 子句给出。

假设使用INNER JOIN子句连接两个表:t1t2

SELECT column_list
FROM t1
INNER JOIN t2 ON join_condition;

对于t1表中的每一行,INNER JOIN子句将它与t2表的每一行进行比较,以检查它们是否都满足连接条件。当满足连接条件时,INNER JOIN将返回由t1t2表中的列组成的新行。

请注意,t1t2表中的行必须根据连接条件进行匹配。如果找不到匹配项,查询将返回一个空结果集。当连接超过2个表时,也应用此逻辑。

以下维恩图说明了INNER JOIN子句的工作原理。结果集中的行必须出现在两个表中:t1t2,如两个圆的交叉部分所示

注意:当对多个表进行查询时,要在 SELECT 语句后面指定字段是来源于哪一张表。因此,在多表查询时,SELECT 语句后面的写法是表名.列名。另外,如果表名非常长的话,也可以给表设置别名,这样就可以直接在 SELECT 语句后面写上表的别名.列名

LEFT/RIGHT JOIN:外连接

左连接

左外连接又称为左连接,使用 LEFT OUTER JOIN 关键字连接两个表,并使用 ON 子句来设置连接条件。

左连接的语法格式如下:

SELECT <字段名> FROM <1> LEFT OUTER JOIN <2> <ON子句>

语法说明如下。

  • 字段名:需要查询的字段名称。
  • <表1><表2>:需要左连接的表名。
  • LEFT OUTER JOIN:左连接中可以省略 OUTER 关键字,只使用关键字 LEFT JOIN。
  • ON 子句:用来设置左连接的连接条件,不能省略。

上述语法中,“表1”为基表,“表2”为参考表。左连接查询时,可以查询出“表1”中的所有记录和“表2”中匹配连接条件的记录。如果“表1”的某行在“表2”中没有匹配行,那么在返回结果中,“表2”的字段值均为空值(NULL)

image-20200816213534191

image-20200816213624600

从subjects表中找出,没有在score表出现过的记录

image-20200816213739001

例 1

在进行左连接查询之前,我们先查看 tb_course 和 tb_students_info 两张表中的数据。SQL 语句和运行结果如下。

mysql> SELECT * FROM tb_course;
+----+-------------+
| id | course_name |
+----+-------------+
|  1 | Java        |
|  2 | MySQL       |
|  3 | Python      |
|  4 | Go          |
|  5 | C++         |
|  6 | HTML        |
+----+-------------+
6 rows in set (0.00 sec)

mysql> SELECT * FROM tb_students_info;
+----+--------+------+------+--------+-----------+
| id | name   | age  | sex  | height | course_id |
+----+--------+------+------+--------+-----------+
|  1 | Dany   |   25 |    |    160 |         1 |
|  2 | Green  |   23 |    |    158 |         2 |
|  3 | Henry  |   23 |    |    185 |         1 |
|  4 | Jane   |   22 |    |    162 |         3 |
|  5 | Jim    |   24 |    |    175 |         2 |
|  6 | John   |   21 |    |    172 |         4 |
|  7 | Lily   |   22 |    |    165 |         4 |
|  8 | Susan  |   23 |    |    170 |         5 |
|  9 | Thomas |   22 |    |    178 |         5 |
| 10 | Tom    |   23 |    |    165 |         5 |
| 11 | LiMing |   22 |    |    180 |         7 |
+----+--------+------+------+--------+-----------+
11 rows in set (0.00 sec)

在 tb_students_info 表和 tb_course 表中查询所有学生姓名和相对应的课程名称,包括没有课程的学生,SQL 语句和运行结果如下。

mysql> SELECT s.name,c.course_name 
	->FROM tb_students_info s 
	->LEFT OUTER JOIN tb_course c 
    -> ON s.`course_id`=c.`id`;
+--------+-------------+
| name   | course_name |
+--------+-------------+
| Dany   | Java        |
| Henry  | Java        |
| NULL   | Java        |
| Green  | MySQL       |
| Jim    | MySQL       |
| Jane   | Python      |
| John   | Go          |
| Lily   | Go          |
| Susan  | C++         |
| Thomas | C++         |
| Tom    | C++         |
| LiMing | NULL        |
+--------+-------------+
12 rows in set (0.00 sec)

可以看到,运行结果显示了 12 条记录,name 为 LiMing 的学生目前没有课程,因为对应的 tb_course 表中没有该学生的课程信息,所以该条记录只取出了 tb_students_info 表中相应的值,而从 tb_course 表中取出的值为 NULL。

右连接

右外连接又称为右连接,右连接是左连接的反向连接。使用 RIGHT OUTER JOIN 关键字连接两个表,并使用 ON 子句来设置连接条件。

右连接的语法格式如下:

SELECT <字段名> FROM <1> RIGHT OUTER JOIN <2> <ON子句>

语法说明如下。

  • 字段名:需要查询的字段名称。
  • <表1><表2>:需要右连接的表名。
  • RIGHT OUTER JOIN:右连接中可以省略 OUTER 关键字,只使用关键字 RIGHT JOIN。
  • ON 子句:用来设置右连接的连接条件,不能省略。

与左连接相反,右连接以“表2”为基表,“表1”为参考表。右连接查询时,可以查询出“表2”中的所有记录和“表1”中匹配连接条件的记录。如果“表2”的某行在“表1”中没有匹配行,那么在返回结果中,“表1”的字段值均为空值(NULL)

“右连接”,表1右连接表2,以右为主,表示以表2为主,关联查询表1的数据,查出表2所有数据以及表1和表2有交集的数据

image-20200816213915138

从score表中找出,没有在subjects表中出现过的记录

image-20200816213958798

例 2

在 tb_students_info 表和 tb_course 表中查询所有课程,包括没有学生的课程,SQL 语句和运行结果如下。

mysql> SELECT s.name,c.course_name FROM tb_students_info s RIGHT OUTER JOIN tb_course c 
    -> ON s.`course_id`=c.`id`;
+--------+-------------+
| name   | course_name |
+--------+-------------+
| Dany   | Java        |
| Green  | MySQL       |
| Henry  | Java        |
| Jane   | Python      |
| Jim    | MySQL       |
| John   | Go          |
| Lily   | Go          |
| Susan  | C++         |
| Thomas | C++         |
| Tom    | C++         |
| NULL   | HTML        |
+--------+-------------+
11 rows in set (0.00 sec)

可以看到,结果显示了 11 条记录,名称为 HTML 的课程目前没有学生,因为对应的 tb_students_info 表中并没有该学生的信息,所以该条记录只取出了 tb_course 表中相应的值,而从 tb_students_info 表中取出的值为 NULL。

多个表左/右连接时,在 ON 子句后连续使用 LEFT/RIGHT OUTER JOIN 或 LEFT/RIGHT JOIN 即可。

使用外连接查询时,一定要分清需要查询的结果,是需要显示左表的全部记录还是右表的全部记录,然后选择相应的左连接和右连接。

SELF JOIN 自联接

INNER JOINLEFT JOINCROSS JOIN子句加入一个表来使用其他表。但是,有一种特殊情况需要将表连接到自身,这称为自联接。

如果要将行与同一表中的其他行组合,可以使用自联接。要执行自联接操作,必须使用表别名来帮助MySQL在单个查询中区分左表和同一表的右表。

自联接实例

employees表中,我们不仅存储员工数据,还存储组织结构数据。reportsto 列用于确定员工的经理ID。

+----------------+
| employees      |
+----------------+
| employeeNumber |
| lastName       |
| firstName      |
| extension      |
| email          |
| officeCode     |
| reportsTo      |
| jobTitle       |
+----------------+
8 rows in set (0.01 sec)

要获取整个组织结构,可以使用employees表中的employeeNumberreportsTo列将表连接到自身。employees表有两个角色:一个是Manager,另一个是Direct Reports。

SELECT 
    CONCAT(m.lastname, ', ', m.firstname) AS 'Manager',
    CONCAT(e.lastname, ', ', e.firstname) AS 'Direct report'
FROM
    employees e
        INNER JOIN
    employees m ON m.employeeNumber = e.reportsto
ORDER BY manager; 

运行结果:

+--------------------+--------------------+
| Manager            | Direct report      |
+--------------------+--------------------+
| Bondur, Gerard     | Bott, Larry        |
| Bondur, Gerard     | Gerard, Martin     |
| Bondur, Gerard     | Hernandez, Gerard  |
| Bondur, Gerard     | Jones, Barry       |
| Bondur, Gerard     | Castillo, Pamela   |
| Bondur, Gerard     | Bondur, Loui       |
| Bow, Anthony       | Firrelli, Julie    |
...

在上面的输出中,您只看到拥有经理的员工。但是,您没有看到顶级经理,因为他的名字由于INNER JOIN子句而被过滤掉了。最高经理是没有任何经理或其经理的员工值为NULL

让我们将INNER JOIN子句更改为LEFT JOIN上面查询中的子句,以包含顶级管理者。如果管理者的姓名是NULL,您还需要使用IFNULL功能来显示最高管理者。

SELECT 
    IFNULL(CONCAT(m.lastname, ', ', m.firstname),
            'Top Manager') AS 'Manager',
    CONCAT(e.lastname, ', ', e.firstname) AS 'Direct report'
FROM
    employees e
        LEFT JOIN
    employees m ON m.employeeNumber = e.reportsto
ORDER BY manager DESC; 

运行结果:

+--------------------+--------------------+
| Manager            | Direct report      |
+--------------------+--------------------+
| Top Manager        | Murphy, Diane      |
| Patterson, William | King, Tom          |
| Patterson, William | Fixter, Andy       |
| Patterson, William | Marsh, Peter       |
| Patterson, Mary    | Bow, Anthony       |
| Patterson, Mary    | Patterson, William |
| Patterson, Mary    | Bondur, Gerard     |
| Patterson, Mary    | Nishi, Mami        |

通过使用MySQL自联接,可以通过将customers表连接到自身来显示位于同一城市的客户列表。

SELECT 
    c1.city, c1.customerName, c2.customerName
FROM
    customers c1
        INNER JOIN
    customers c2 ON c1.city = c2.city
        AND c1.customername > c2.customerName
ORDER BY c1.city; 

运行结果:

+---------------+------------------------------+--------------------------------+
| city          | customerName                 | customerName                   |
+---------------+------------------------------+--------------------------------+
| Auckland      | Kelly's Gift Shop            | Down Under Souveniers, Inc     |
| Auckland      | Kelly's Gift Shop            | GiftsForHim.com                |
| Auckland      | GiftsForHim.com              | Down Under Souveniers, Inc     |
| Boston        | Gifts4AllAges.com            | Diecast Collectables           |
| Brickhaven    | Online Mini Collectables     | Collectables For Less Inc.     |
| Brickhaven    | Collectables For Less Inc.   | Auto-Moto Classics Inc.        |
| Brickhaven    | Online Mini Collectables     | Auto-Moto Classics Inc.        |
| Cambridge     | Marta's Replicas Co.         | Cambridge Collectables Co.     |
...

customers通过以下连接条件加入了表:

  • c1.city = c2.city 确保两个客户都拥有相同的城市。
  • c.customerName > c2.customerName 确保我们不会得到同一个客户。

MySQL子查询

前面我们介绍了如何使用 SELECTINSERTUPDATEDELETE 语句对 MySQL 进行简单访问和操作。下面在此基础上开始学习子查询。

子查询是 MySQL 中比较常用的查询方法,通过子查询可以实现多表查询。子查询指将一个查询语句嵌套在另一个查询语句中。子查询可以在 SELECT、UPDATE 和 DELETE 语句中使用,而且可以进行多层嵌套。在实际开发时,子查询经常出现在 WHERE 子句中。

子查询在 WHERE 中的语法格式如下:

WHERE <表达式> <操作符> (子查询)

其中,操作符可以是比较运算符和 INNOT INEXISTSNOT EXISTS 等关键字。

1)IN | NOT IN

当表达式与子查询返回的结果集中的某个值相等时,返回 TRUE,否则返回 FALSE;若使用关键字 NOT,则返回值正好相反。

IN 操作符用来指定条件范围,范围中的每个条件都可以进行匹配。IN 取一组由逗号分割、括在圆括号中的和合法值。

-- 42、查询每门功成绩最好的前两名
select t.c_id, t.s_score, t.r
from (
         select *, row_number() over (partition by c_id order by s_score desc ) r
         from score
     ) t
where t.r in (1, 2);

2)EXISTS | NOT EXISTS

用于判断子查询的结果集是否为空,若子查询的结果集不为空,返回 TRUE,否则返回 FALSE;若使用关键字 NOT,则返回的值正好相反。

例 1

使用子查询在 tb_students_info 表和 tb_course 表中查询学习 Java 课程的学生姓名,SQL 语句和运行结果如下。

mysql> SELECT name FROM tb_students_info 
    -> WHERE course_id IN 
    ->(SELECT id FROM tb_course WHERE course_name = 'Java');
+-------+
| name  |
+-------+
| Dany  |
| Henry |
+-------+
2 rows in set (0.01 sec)

结果显示,学习 Java 课程的只有 Dany 和 Henry。上述查询过程也可以分为以下 2 步执行,实现效果是相同的。

1)首先单独执行内查询,查询出 tb_course 表中课程为 Java 的 id,SQL 语句和运行结果如下。

mysql> SELECT id FROM tb_course 
    -> WHERE course_name = 'Java';
+----+
| id |
+----+
|  1 |
+----+
1 row in set (0.00 sec)

可以看到,符合条件的 id 字段的值为 1。

2)然后执行外层查询,在 tb_students_info 表中查询 course_id 等于 1 的学生姓名。SQL 语句和运行结果如下。

mysql> SELECT name FROM tb_students_info 
    -> WHERE course_id IN (1);
+-------+
| name  |
+-------+
| Dany  |
| Henry |
+-------+
2 rows in set (0.00 sec)

习惯上,外层的 SELECT 查询称为父查询,圆括号中嵌入的查询称为子查询(子查询必须放在圆括号内)。MySQL 在处理上例的 SELECT 语句时,执行流程为:先执行子查询,再执行父查询。

例 2

与例 1 类似,在 SELECT 语句中使用 NOT IN 关键字,查询没有学习 Java 课程的学生姓名,SQL 语句和运行结果如下。

mysql> SELECT name FROM tb_students_info 
    -> WHERE course_id NOT IN (SELECT id FROM tb_course WHERE course_name = 'Java');
+--------+
| name   |
+--------+
| Green  |
| Jane   |
| Jim    |
| John   |
| Lily   |
| Susan  |
| Thomas |
| Tom    |
| LiMing |
+--------+
9 rows in set (0.01 sec)

可以看出,运行结果与例 1 刚好相反,没有学习 Java 课程的是除了 Dany 和 Henry 之外的学生。

例 3

使用=运算符,在 tb_course 表和 tb_students_info 表中查询出所有学习 Python 课程的学生姓名,SQL 语句和运行结果如下。

mysql> SELECT name FROM tb_students_info
    -> WHERE course_id = 
    ->(SELECT id FROM tb_course WHERE course_name = 'Python');
+------+
| name |
+------+
| Jane |
+------+
1 row in set (0.00 sec)

结果显示,学习 Python 课程的学生只有 Jane。

例 4

使用<>运算符,在 tb_course 表和 tb_students_info 表中查询出没有学习 Python 课程的学生姓名,SQL 语句和运行结果如下。

mysql> SELECT name FROM tb_students_info
    -> WHERE course_id <> 
    ->(SELECT id FROM tb_course WHERE course_name = 'Python');
+--------+
| name   |
+--------+
| Dany   |
| Green  |
| Henry  |
| Jim    |
| John   |
| Lily   |
| Susan  |
| Thomas |
| Tom    |
| LiMing |
+--------+
10 rows in set (0.00 sec)

可以看出,运行结果与例 3 刚好相反,没有学习 Python 课程的是除了 Jane 之外的学生。

例 5

查询 tb_course 表中是否存在 id=1 的课程,如果存在,就查询出 tb_students_info 表中的记录,SQL 语句和运行结果如下。

mysql> SELECT * FROM tb_students_info
    -> WHERE EXISTS
    ->(SELECT course_name FROM tb_course WHERE id=1);
+----+--------+------+------+--------+-----------+
| id | name   | age  | sex  | height | course_id |
+----+--------+------+------+--------+-----------+
|  1 | Dany   |   25 |    |    160 |         1 |
|  2 | Green  |   23 |    |    158 |         2 |
|  3 | Henry  |   23 |    |    185 |         1 |
|  4 | Jane   |   22 |    |    162 |         3 |
|  5 | Jim    |   24 |    |    175 |         2 |
|  6 | John   |   21 |    |    172 |         4 |
|  7 | Lily   |   22 |    |    165 |         4 |
|  8 | Susan  |   23 |    |    170 |         5 |
|  9 | Thomas |   22 |    |    178 |         5 |
| 10 | Tom    |   23 |    |    165 |         5 |
| 11 | LiMing |   22 |    |    180 |         7 |
+----+--------+------+------+--------+-----------+
11 rows in set (0.01 sec)

由结果可以看到,tb_course 表中存在 id=1 的记录,因此 EXISTS 表达式返回 TRUE,外层查询语句接收 TRUE 之后对表 tb_students_info 进行查询,返回所有的记录。

EXISTS 关键字可以和其它查询条件一起使用,条件表达式与 EXISTS 关键字之间用 AND 和 OR 连接。

例 6

查询 tb_course 表中是否存在 id=1 的课程,如果存在,就查询出 tb_students_info 表中 age 字段大于 24 的记录,SQL 语句和运行结果如下。

mysql> SELECT * FROM tb_students_info
    -> WHERE age>24 AND 
    ->EXISTS(SELECT course_name FROM tb_course WHERE id=1);
+----+------+------+------+--------+-----------+
| id | name | age  | sex  | height | course_id |
+----+------+------+------+--------+-----------+
|  1 | Dany |   25 |    |    160 |         1 |
+----+------+------+------+--------+-----------+
1 row in set (0.01 sec)

结果显示,从 tb_students_info 表中查询出了一条记录,这条记录的 age 字段取值为 25。内层查询语句从 tb_course 表中查询到记录,返回 TRUE。外层查询语句开始进行查询。根据查询条件,从 tb_students_info 表中查询 age 大于 24 的记录。

在完成较复杂的数据查询时,经常会使用到子查询,编写子查询语句时,要注意如下事项。

1) 子查询语句可以嵌套在 SQL 语句中任何表达式出现的位置

在 SELECT 语句中,子查询可以被嵌套在 SELECT 语句的列、表和查询条件中,即 SELECT 子句,FROM 子句、WHERE 子句、GROUP BY 子句和 HAVING 子句。

前面已经介绍了 WHERE 子句中嵌套子查询的使用方法,下面是子查询在 SELECT 子句和 FROM 子句中的使用语法。

嵌套在 SELECT 语句的 SELECT 子句中的子查询语法格式如下。

SELECT (子查询) FROM 表名;

提示:子查询结果为单行单列,但不必指定列别名。

嵌套在 SELECT 语句的 FROM 子句中的子查询语法格式如下。

SELECT * FROM (子查询) AS 表的别名;

注意:必须为表指定别名。一般返回多行多列数据记录,可以当作一张临时表。

2) 只出现在子查询中而没有出现在父查询中的表不能包含在输出列中

多层嵌套子查询的最终数据集只包含父查询(即最外层的查询)的 SELECT 子句中出现的字段,而子查询的输出结果通常会作为其外层子查询数据源或用于数据判断匹配。

常见错误如下:

SELECT * FROM (SELECT * FROM result);

这个子查询语句产生语法错误的原因在于主查询语句的 FROM 子句是一个子查询语句,因此应该为子查询结果集指定别名。正确代码如下。

SELECT * FROM (SELECT * FROM result) AS Temp;

REGEXP:正则表达式

正则表达式主要用来查询和替换符合某个模式(规则)的文本内容。例如,从一个文件中提取电话号码,查找一篇文章中重复的单词、替换文章中的敏感语汇等,这些地方都可以使用正则表达式。正则表达式强大且灵活,常用于非常复杂的查询。

MySQL 中,使用 REGEXP 关键字指定正则表达式的字符匹配模式,其基本语法格式如下:

属性名 REGEXP ‘匹配方式’

其中,“属性名”表示需要查询的字段名称;“匹配方式”表示以哪种方式来匹配查询。“匹配方式”中有很多的模式匹配字符,它们分别表示不同的意思。下表列出了 REGEXP 操作符中常用的匹配方式。

选项 说明 例子 匹配值示例
^ 匹配文本的开始字符 ‘^b’ 匹配以字母 b 开头的字符串 book、big、banana、bike
$ 匹配文本的结束字符 ‘st$’ 匹配以 st 结尾的字符串 test、resist、persist
. 匹配任何单个字符 ‘b.t’ 匹配任何 b 和 t 之间有一个字符 bit、bat、but、bite
* 匹配零个或多个在它前面的字符 ‘f*n’ 匹配字符 n 前面有任意个字符 f fn、fan、faan、abcn
+ 匹配前面的字符 1 次或多次 ‘ba+’ 匹配以 b 开头,后面至少紧跟一个 a ba、bay、bare、battle
<字符串> 匹配包含指定字符的文本 ‘fa’ 匹配包含‘fa’的文本 fan、afa、faad
[字符集合] 匹配字符集合中的任何一个字符 ‘[xz]’ 匹配 x 或者 z dizzy、zebra、x-ray、extra
[^] 匹配不在括号中的任何字符 ’[^abc]’ 匹配任何不包含 a、b 或 c 的字符串 desk、fox、f8ke
字符串{n,} 匹配前面的字符串至少 n 次 ‘b{2}’ 匹配 2 个或更多的 b bbb、bbbb、bbbbbbb
字符串 {n,m} 匹配前面的字符串至少 n 次, 至多 m 次 ‘b{2,4}’ 匹配最少 2 个,最多 4 个 b bbb、bbbb

MySQL 中的正则表达式与 Java 语言、PHP 语言等编程语言中的正则表达式基本一致。

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

字符^用来匹配以特定字符或字符串开头的记录。

例 1

在 tb_students_info 表中,查询 name 字段以“J”开头的记录,SQL 语句和执行过程如下。

mysql> SELECT * FROM tb_students_info 
    -> WHERE name REGEXP '^J';
+----+------+------+------+--------+-----------+
| id | name | age  | sex  | height | course_id |
+----+------+------+------+--------+-----------+
|  4 | Jane |   22 |    |    162 |         3 |
|  5 | Jim  |   24 |    |    175 |         2 |
|  6 | John |   21 |    |    172 |         4 |
+----+------+------+------+--------+-----------+
3 rows in set (0.01 sec)

例 2

在 tb_students_info 表中,查询 name 字段以“Ji”开头的记录,SQL 语句和执行过程如下。

mysql> SELECT * FROM tb_students_info
    -> WHERE name REGEXP '^Ji';
+----+------+------+------+--------+-----------+
| id | name | age  | sex  | height | course_id |
+----+------+------+------+--------+-----------+
|  5 | Jim  |   24 |    |    175 |         2 |
+----+------+------+------+--------+-----------+
1 row in set (0.00 sec)

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

字符$用来匹配以特定字符或字符串结尾的记录。

例 3

在 tb_students_info 表中,查询 name 字段以“y”结尾的记录,SQL 语句和执行过程如下。

mysql> SELECT * FROM tb_students_info
    -> WHERE name REGEXP 'y$';
+----+-------+------+------+--------+-----------+
| id | name  | age  | sex  | height | course_id |
+----+-------+------+------+--------+-----------+
|  1 | Dany  |   25 |    |    160 |         1 |
|  3 | Henry |   23 |    |    185 |         1 |
|  7 | Lily  |   22 |    |    165 |         4 |
+----+-------+------+------+--------+-----------+
3 rows in set (0.00 sec)

例 4

在 tb_students_info 表中,查询 name 字段以“ry”结尾的记录,SQL 语句和执行过程如下。

mysql> SELECT * FROM tb_students_info
    -> WHERE name REGEXP 'ry$';
+----+-------+------+------+--------+-----------+
| id | name  | age  | sex  | height | course_id |
+----+-------+------+------+--------+-----------+
|  3 | Henry |   23 |    |    185 |         1 |
+----+-------+------+------+--------+-----------+
1 row in set (0.00 sec)

替代字符串中的任意一个字符

字符.用来替代字符串中的任意一个字符。

例 5

在 tb_students_info 表中,查询 name 字段值包含“a”和“y”,且两个字母之间只有一个字母的记录,SQL 语句和执行过程如下。

mysql> SELECT * FROM tb_students_info
    -> WHERE name REGEXP 'a.y';
+----+------+------+------+--------+-----------+
| id | name | age  | sex  | height | course_id |
+----+------+------+------+--------+-----------+
|  1 | Dany |   25 |    |    160 |         1 |
+----+------+------+------+--------+-----------+
1 row in set (0.00 sec)

匹配多个字符

字符*+都可以匹配多个该符号之前的字符。不同的是,+表示至少一个字符,而*可以表示 0 个字符。

例 6

在 tb_students_info 表中,查询 name 字段值包含字母“T”,且“T”后面出现字母“h”的记录,SQL 语句和执行过程如下。

mysql> SELECT * FROM tb_students_info
    -> WHERE name REGEXP '^Th*';
+----+--------+------+------+--------+-----------+
| id | name   | age  | sex  | height | course_id |
+----+--------+------+------+--------+-----------+
|  9 | Thomas |   22 |    |    178 |         5 |
| 10 | Tom    |   23 |    |    165 |         5 |
+----+--------+------+------+--------+-----------+
2 rows in set (0.00 sec)

例 7

在 tb_students_info 表中,查询 name 字段值包含字母“T”,且“T”后面至少出现“h”一次的记录,SQL 语句和执行过程如下。

mysql> SELECT * FROM tb_students_info
    -> WHERE name REGEXP '^Th+';
+----+--------+------+------+--------+-----------+
| id | name   | age  | sex  | height | course_id |
+----+--------+------+------+--------+-----------+
|  9 | Thomas |   22 |    |    178 |         5 |
+----+--------+------+------+--------+-----------+
1 row in set (0.00 sec)

匹配指定字符串

正则表达式可以匹配字符串。当表中的记录包含这个字符串时,就可以将该记录查询出来。指定多个字符串时,需要用|隔开。只要匹配这些字符串中的任意一个即可。

例 8

在 tb_students_info 表中,查询 name 字段值包含字符串“an”的记录,SQL 语句和执行过程如下。

mysql> SELECT * FROM tb_students_info
    -> WHERE name REGEXP 'an';
+----+-------+------+------+--------+-----------+
| id | name  | age  | sex  | height | course_id |
+----+-------+------+------+--------+-----------+
|  1 | Dany  |   25 |    |    160 |         1 |
|  4 | Jane  |   22 |    |    162 |         3 |
|  8 | Susan |   23 |    |    170 |         5 |
+----+-------+------+------+--------+-----------+
3 rows in set (0.00 sec)

例 9

在 tb_students_info 表中,查询 name 字段值包含字符串“an”或“en”的记录,SQL 语句和执行过程如下。

mysql> SELECT * FROM tb_students_info
    -> WHERE name REGEXP 'an|en';
+----+-------+------+------+--------+-----------+
| id | name  | age  | sex  | height | course_id |
+----+-------+------+------+--------+-----------+
|  1 | Dany  |   25 |    |    160 |         1 |
|  2 | Green |   23 |    |    158 |         2 |
|  3 | Henry |   23 |    |    185 |         1 |
|  4 | Jane  |   22 |    |    162 |         3 |
|  8 | Susan |   23 |    |    170 |         5 |
+----+-------+------+------+--------+-----------+
5 rows in set (0.00 sec)

注意:字符串与|之间不能有空格。因为,查询过程中,数据库系统会将空格也当作一个字符,这样就查询不出想要的结果。

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

使用方括号[ ]可以将需要查询的字符组成一个字符集合。只要记录中包含方括号中的任意字符,该记录就会被查询出来。例如,通过“[abc]”可以查询包含 a、b 和 c 等 3 个字母中任意一个的记录。

例 10

在 tb_students_info 表中,查询 name 字段值包含字母“i”或“o”的记录,SQL 语句和执行过程如下。

mysql> SELECT * FROM tb_students_info
    -> WHERE name REGEXP '[io]';
+----+--------+------+------+--------+-----------+
| id | name   | age  | sex  | height | course_id |
+----+--------+------+------+--------+-----------+
|  5 | Jim    |   24 |    |    175 |         2 |
|  6 | John   |   21 |    |    172 |         4 |
|  7 | Lily   |   22 |    |    165 |         4 |
|  9 | Thomas |   22 |    |    178 |         5 |
| 10 | Tom    |   23 |    |    165 |         5 |
| 11 | LiMing |   22 |    |    180 |         7 |
+----+--------+------+------+--------+-----------+
6 rows in set (0.00 sec)

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

方括号[ ]还可以指定集合的区间。例如,“[a-z]”表示从 a~z 的所有字母;“[0-9]”表示从 0~9 的所有数字;“[a-z0-9]”表示包含所有的小写字母和数字;“[a-zA-Z]”表示匹配所有字符。

例 11

在 tb_students_info 表中,查询 name 字段值中包含 1、2 或 3 的记录,SQL 语句和执行过程如下。

mysql> SELECT * FROM tb_students_info
    -> WHERE name REGEXP '[123]';
Empty set (0.00 sec)

匹配集合“[123]”也可以写成“[1-3]”,即指定集合区间。

匹配指定字符以外的字符

[^字符集合]用来匹配不在指定集合中的任何字符。

例 12

在 tb_students_info 表中,查询 name 字段值包含字母 a~t 以外的字符的记录,SQL 语句和执行过程如下。

mysql> SELECT * FROM tb_students_info
    -> WHERE name REGEXP '[^a-t]' ;
+----+-------+------+------+--------+-----------+
| id | name  | age  | sex  | height | course_id |
+----+-------+------+------+--------+-----------+
|  1 | Dany  |   25 |    |    160 |         1 |
|  3 | Henry |   23 |    |    185 |         1 |
|  7 | Lily  |   22 |    |    165 |         4 |
|  8 | Susan |   23 |    |    170 |         5 |
+----+-------+------+------+--------+-----------+
4 rows in set (0.00 sec)

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

字符串{n,}表示字符串连续出现 n 次;字符串{n,m}表示字符串连续出现至少 n 次,最多 m 次。

例如,a{2,} 表示字母 a 连续出现至少 2 次,也可以大于 2 次;a{2,4} 表示字母 a 连续出现最少 2 次,最多不能超过 4 次。

例 13

在 tb_students_info 表中,查询 name 字段值出现字母‘e’ 至少 2 次的记录,SQL 语句如下:

mysql> SELECT * FROM tb_students_info WHERE name REGEXP 'e{2,}';
+----+-------+------+------+--------+-----------+
| id | name  | age  | sex  | height | course_id |
+----+-------+------+------+--------+-----------+
|  2 | Green |   23 |    |    158 |         2 |
+----+-------+------+------+--------+-----------+
1 row in set (0.00 sec)

例 14

在 tb_students_info 表中,查询 name 字段值出现字符串“i” 最少 1 次,最多 3 次的记录,SQL 语句如下:

mysql> SELECT * FROM tb_students_info WHERE name REGEXP 'i{1,3}';
+----+--------+------+------+--------+-----------+
| id | name   | age  | sex  | height | course_id |
+----+--------+------+------+--------+-----------+
|  5 | Jim    |   24 |    |    175 |         2 |
|  7 | Lily   |   22 |    |    165 |         4 |
| 11 | LiMing |   22 |    |    180 |         7 |
+----+--------+------+------+--------+-----------+
3 rows in set (0.00 sec)

INSERT:插入数据(添加数据)

数据库与表创建成功以后,需要向数据库的表中插入数据。在 MySQL 中可以使用 INSERT 语句向数据库已有的表中插入一行或者多行元组数据。

基本语法

INSERT 语句有两种语法形式,分别是 INSERT…VALUES 语句和 INSERT…SET 语句。

1) INSERT…VALUES语句

INSERT VALUES 的语法格式为:

INSERT INTO <表名> [ <列名1> [ ,  <列名n>] ]
VALUES (1) [ , (n) ];

语法说明如下。

  • <表名>:指定被操作的表名。
  • <列名>:指定需要插入数据的列名。若向表中的所有列插入数据,则全部的列名均可以省略,直接采用 INSERT<表名>VALUES(…) 即可。
  • VALUESVALUE 子句:该子句包含要插入的数据清单。数据清单中数据的顺序要和列的顺序相对应。

2) INSERT…SET语句

语法格式为:

INSERT INTO <表名>
SET <列名1> = <1>,
    <列名2> = <2>,
    

此语句用于直接给表中的某些列指定对应的列值,即要插入的数据的列名在 SET 子句中指定,col_name 为指定的列名,等号后面为指定的数据,而对于未指定的列,列值会指定为该列的默认值。

由 INSERT 语句的两种形式可以看出:

  • 使用 INSERT…VALUES 语句可以向表中插入一行数据,也可以插入多行数据;
  • 使用 INSERT…SET 语句可以指定插入行中每列的值,也可以指定部分列的值;
  • INSERT…SELECT 语句向表中插入其他表的数据。
  • 采用 INSERT…SET 语句可以向表中插入部分列的值,这种方式更为灵活;
  • INSERT…VALUES 语句可以一次插入多条数据。

在 MySQL 中,用单条 INSERT 语句处理多个插入要比使用多条 INSERT 语句更快。

当使用单条 INSERT 语句插入多行数据的时候,只需要将每行数据用圆括号括起来即可。

向表中的全部字段添加值

在 test_db 数据库中创建一个课程信息表 tb_courses,包含课程编号 course_id、课程名称 course_name、课程学分 course_grade 和课程备注 course_info,输入的 SQL 语句和执行结果如下所示。

mysql> CREATE TABLE tb_courses
    -> (
    -> course_id INT NOT NULL AUTO_INCREMENT,
    -> course_name CHAR(40) NOT NULL,
    -> course_grade FLOAT NOT NULL,
    -> course_info CHAR(100) NULL,
    -> PRIMARY KEY(course_id)
    -> );
Query OK, 0 rows affected (0.00 sec)

向表中所有字段插入值的方法有两种:一种是指定所有字段名;另一种是完全不指定字段名。

【实例 1】在 tb_courses 表中插入一条新记录,course_id 值为 1,course_name 值为“Network”,course_grade 值为 3,info 值为“Computer Network”。

在执行插入操作之前,查看 tb_courses 表的SQL语句和执行结果如下所示。

mysql> SELECT * FROM tb_courses;
Empty set (0.00 sec)

查询结果显示当前表内容为空,没有数据,接下来执行插入数据的操作,输入的 SQL 语句和执行过程如下所示。

mysql> INSERT INTO tb_courses
    -> (course_id,course_name,course_grade,course_info)
    -> VALUES(1,'Network',3,'Computer Network');
Query OK, 1 rows affected (0.08 sec)
mysql> SELECT * FROM tb_courses;
+-----------+-------------+--------------+------------------+
| course_id | course_name | course_grade | course_info      |
+-----------+-------------+--------------+------------------+
|         1 | Network     |            3 | Computer Network |
+-----------+-------------+--------------+------------------+
1 row in set (0.00 sec)

可以看到插入记录成功。在插入数据时,指定了 tb_courses 表的所有字段,因此将为每一个字段插入新的值。

INSERT 语句后面的列名称顺序可以不是 tb_courses 表定义时的顺序,即插入数据时,不需要按照表定义的顺序插入,只要保证值的顺序与列字段的顺序相同就可以。

【实例 2】在 tb_courses 表中插入一条新记录,course_id 值为 2,course_name 值为“Database”,course_grade 值为 3,info值为“MySQL”。输入的 SQL 语句和执行结果如下所示。

mysql> INSERT INTO tb_courses
    -> (course_name,course_info,course_id,course_grade)
    -> VALUES('Database','MySQL',2,3);
Query OK, 1 rows affected (0.08 sec)
mysql> SELECT * FROM tb_courses;
+-----------+-------------+--------------+------------------+
| course_id | course_name | course_grade | course_info      |
+-----------+-------------+--------------+------------------+
|         1 | Network     |            3 | Computer Network |
|         2 | Database    |            3 | MySQL            |
+-----------+-------------+--------------+------------------+
2 rows in set (0.00 sec)

使用 INSERT 插入数据时,允许列名称列表 column_list 为空,此时值列表中需要为表的每一个字段指定值,并且值的顺序必须和数据表中字段定义时的顺序相同。

【实例 3】在 tb_courses 表中插入一条新记录,course_id 值为 3,course_name 值为“Java”,course_grade 值为 4,info 值为“Jave EE”。输入的 SQL 语句和执行结果如下所示。

mysql> INSERT INTO tb_courses
    -> VLAUES(3,'Java',4,'Java EE');
Query OK, 1 rows affected (0.08 sec)
mysql> SELECT * FROM tb_courses;
+-----------+-------------+--------------+------------------+
| course_id | course_name | course_grade | course_info      |
+-----------+-------------+--------------+------------------+
|         1 | Network     |            3 | Computer Network |
|         2 | Database    |            3 | MySQL            |
|         3 | Java        |            4 | Java EE          |
+-----------+-------------+--------------+------------------+
3 rows in set (0.00 sec)

INSERT 语句中没有指定插入列表,只有一个值列表。在这种情况下,值列表为每一个字段列指定插入的值,并且这些值的顺序必须和 tb_courses 表中字段定义的顺序相同。

注意:虽然使用 INSERT 插入数据时可以忽略插入数据的列名称,若值不包含列名称,则 VALUES 关键字后面的值不仅要求完整,而且顺序必须和表定义时列的顺序相同。如果表的结构被修改,对列进行增加、删除或者位置改变操作,这些操作将使得用这种方式插入数据时的顺序也同时改变。如果指定列名称,就不会受到表结构改变的影响。

向表中指定字段添加值

为表的指定字段插入数据,是在 INSERT 语句中只向部分字段中插入值,而其他字段的值为表定义时的默认值。

【实例 4】在 tb_courses 表中插入一条新记录,course_name 值为“System”,course_grade 值为 3,course_info 值为“Operating System”,输入的 SQL 语句和执行结果如下所示。

mysql> INSERT INTO tb_courses
    -> (course_name,course_grade,course_info)
    -> VALUES('System',3,'Operation System');
Query OK, 1 rows affected (0.08 sec)
mysql> SELECT * FROM tb_courses;
+-----------+-------------+--------------+------------------+
| course_id | course_name | course_grade | course_info      |
+-----------+-------------+--------------+------------------+
|         1 | Network     |            3 | Computer Network |
|         2 | Database    |            3 | MySQL            |
|         3 | Java        |            4 | Java EE          |
|         4 | System      |            3 | Operating System |
+-----------+-------------+--------------+------------------+
4 rows in set (0.00 sec)

可以看到插入记录成功。如查询结果显示,这里的 course_id 字段自动添加了一个整数值 4。这时的 course_id 字段为表的主键,不能为空,系统自动为该字段插入自增的序列值。在插入记录时,如果某些字段没有指定插入值,MySQL 将插入该字段定义时的默认值。

使用 INSERT INTO…FROM 语句复制表数据

INSERT INTO…SELECT…FROM 语句用于快速地从一个或多个表中取出数据,并将这些数据作为行数据插入另一个表中。

SELECT 子句返回的是一个查询到的结果集,INSERT 语句将这个结果集插入指定表中,结果集中的每行数据的字段数、字段的数据类型都必须与被操作的表完全一致。

在数据库 test_db 中创建一个与 tb_courses 表结构相同的数据表 tb_courses_new,创建表的 SQL 语句和执行过程如下所示。

mysql> CREATE TABLE tb_courses_new
    -> (
    -> course_id INT NOT NULL AUTO_INCREMENT,
    -> course_name CHAR(40) NOT NULL,
    -> course_grade FLOAT NOT NULL,
    -> course_info CHAR(100) NULL,
    -> PRIMARY KEY(course_id)
    -> );
Query OK, 0 rows affected (0.00 sec)
mysql> SELECT * FROM tb_courses_new;
Empty set (0.00 sec)

【实例 5】从 tb_courses 表中查询所有的记录,并将其插入 tb_courses_new 表中。输入的 SQL 语句和执行结果如下所示。

mysql> INSERT INTO tb_courses_new
    -> (course_id,course_name,course_grade,course_info)
    -> SELECT course_id,course_name,course_grade,course_info
    -> FROM tb_courses;
Query OK, 4 rows affected (0.17 sec)
Records: 4  Duplicates: 0  Warnings: 0
mysql> SELECT * FROM tb_courses_new;
+-----------+-------------+--------------+------------------+
| course_id | course_name | course_grade | course_info      |
+-----------+-------------+--------------+------------------+
|         1 | Network     |            3 | Computer Network |
|         2 | Database    |            3 | MySQL            |
|         3 | Java        |            4 | Java EE          |
|         4 | System      |            3 | Operating System |
+-----------+-------------+--------------+------------------+
4 rows in set (0.00 sec)

UPDATE:修改数据(更新数据)

可以使用 UPDATE 语句来修改、更新一个或多个表的数据。

UPDATE 语句的基本语法

使用 UPDATE 语句修改单个表,语法格式为:

UPDATE <表名> SET 字段1=1 [,字段2=2 ] [WHERE 子句 ]
[ORDER BY 子句] [LIMIT 子句]

语法说明如下:

  • <表名>:用于指定要更新的表名称。
  • SET 子句:用于指定表中要修改的列名及其列值。其中,每个指定的列值可以是表达式,也可以是该列对应的默认值。如果指定的是默认值,可用关键字 DEFAULT 表示列值。
  • WHERE 子句:可选项。用于限定表中要修改的行。若不指定,则修改表中所有的行。
  • ORDER BY 子句:可选项。用于限定表中的行被修改的次序。
  • LIMIT 子句:可选项。用于限定被修改的行数。

注意:修改一行数据的多个列值时,SET 子句的每个值用逗号分开即可。

修改表中的数据

【实例 1】在 tb_courses_new 表中,更新所有行的 course_grade 字段值为 4,输入的 SQL 语句和执行结果如下所示。

mysql> UPDATE tb_courses_new
    -> SET course_grade=4;
Query OK, 3 rows affected (0.11 sec)
Rows matched: 4  Changed: 3  Warnings: 0
mysql> SELECT * FROM tb_courses_new;
+-----------+-------------+--------------+------------------+
| course_id | course_name | course_grade | course_info      |
+-----------+-------------+--------------+------------------+
|         1 | Network     |            4 | Computer Network |
|         2 | Database    |            4 | MySQL            |
|         3 | Java        |            4 | Java EE          |
|         4 | System      |            4 | Operating System |
+-----------+-------------+--------------+------------------+
4 rows in set (0.00 sec)

根据条件修改表中的数据

【实例 2】在 tb_courses 表中,更新 course_id 值为 2 的记录,将 course_grade 字段值改为 3.5,将 course_name 字段值改为“DB”,输入的 SQL 语句和执行结果如下所示。

mysql> UPDATE tb_courses_new
    -> SET course_name='DB',course_grade=3.5
    -> WHERE course_id=2;
Query OK, 1 row affected (0.13 sec)
Rows matched: 1  Changed: 1  Warnings: 0
mysql> SELECT * FROM tb_courses_new;
+-----------+-------------+--------------+------------------+
| course_id | course_name | course_grade | course_info      |
+-----------+-------------+--------------+------------------+
|         1 | Network     |            4 | Computer Network |
|         2 | DB          |          3.5 | MySQL            |
|         3 | Java        |            4 | Java EE          |
|         4 | System      |            4 | Operating System |
+-----------+-------------+--------------+------------------+
4 rows in set (0.00 sec)

注意:保证 UPDATE 以 WHERE 子句结束,通过 WHERE 子句指定被更新的记录所需要满足的条件,如果忽略 WHERE 子句,MySQL 将更新表中所有的行

DELETE:删除数据

删除单个表中的数据

使用 DELETE 语句从单个表中删除数据,语法格式为:

DELETE FROM <表名> [WHERE 子句] [ORDER BY 子句] [LIMIT 子句]

语法说明如下:

  • <表名>:指定要删除数据的表名。
  • ORDER BY 子句:可选项。表示删除时,表中各行将按照子句中指定的顺序进行删除。
  • WHERE 子句:可选项。表示为删除操作限定删除条件,若省略该子句,则代表删除该表中的所有行。
  • LIMIT 子句:可选项。用于告知服务器在控制命令被返回到客户端前被删除行的最大值。

注意:在不使用 WHERE 条件的时候,将删除所有数据。

删除表中的全部数据

【实例 1】删除 tb_courses_new 表中的全部数据,输入的 SQL 语句和执行结果如下所示。

mysql> DELETE FROM tb_courses_new;
Query OK, 3 rows affected (0.12 sec)
mysql> SELECT * FROM tb_courses_new;
Empty set (0.00 sec)

根据条件删除表中的数据

【实例 2】在 tb_courses_new 表中,删除 course_id 为 4 的记录,输入的 SQL 语句和执行结果如下所示。

mysql> DELETE FROM tb_courses
    -> WHERE course_id=4;
Query OK, 1 row affected (0.00 sec)
mysql> SELECT * FROM tb_courses;
+-----------+-------------+--------------+------------------+
| course_id | course_name | course_grade | course_info      |
+-----------+-------------+--------------+------------------+
|         1 | Network     |            3 | Computer Network |
|         2 | Database    |            3 | MySQL            |
|         3 | Java        |            4 | Java EE          |
+-----------+-------------+--------------+------------------+
3 rows in set (0.00 sec)

由运行结果可以看出,course_id 为 4 的记录已经被删除。

TRUNCATE:清空表记录

MySQL 提供了 DELETE 和 TRUNCATE 关键字来删除表中的数据。本节主要讲解 TRUNCATE 关键字的使用。

TRUNCATE 关键字用于完全清空一个表。其语法格式如下:

TRUNCATE [TABLE] 表名

其中,TABLE 关键字可省略。

例 1

新建表 tb_student_course,插入数据并查询,SQL 语句和运行结果如下:

mysql> CREATE TABLE `tb_student_course` (
    -> `id` int(4) NOT NULL AUTO_INCREMENT,
    -> `name` varchar(25) NOT NULL,
    -> PRIMARY KEY (`id`)
    -> );
Query OK, 0 rows affected (0.04 sec)

mysql> INSERT INTO tb_student_course(name) VALUES ('Java'),('MySQL'),('Python');
Query OK, 3 rows affected (0.05 sec)
Records: 3  Duplicates: 0  Warnings: 0

mysql> SELECT * FROM tb_student_course;
+----+--------+
| id | name   |
+----+--------+
|  1 | Java   |
|  2 | MySQL  |
|  3 | Python |
+----+--------+
3 rows in set (0.00 sec)

使用 TRUNCATE 语句清空 tb_student_course 表中的记录,SQL 语句和运行结果如下:

mysql> TRUNCATE TABLE tb_student_course;
Query OK, 0 rows affected (0.04 sec)

mysql> SELECT * FROM tb_student_course;
Empty set (0.00 sec)

TRUNCATE 和 DELETE 的区别

从逻辑上说,TRUNCATE 语句与 DELETE 语句作用相同,但是在某些情况下,两者在使用上有所区别。

  • DELETE 是 DML 类型的语句;TRUNCATE 是 DDL 类型的语句。它们都用来清空表中的数据。
  • DELETE 是逐行一条一条删除记录的;TRUNCATE 则是直接删除原来的表,再重新创建一个一模一样的新表,而不是逐行删除表中的数据,执行数据比 DELETE 快。因此需要删除表中全部的数据行时,尽量使用 TRUNCATE 语句, 可以缩短执行时间。
  • DELETE 删除数据后,配合事件回滚可以找回数据;TRUNCATE 不支持事务的回滚,数据删除后无法找回。
  • DELETE 删除数据后,系统不会重新设置自增字段的计数器;TRUNCATE 清空表记录后,系统会重新设置自增字段的计数器。
  • DELETE 的使用范围更广,因为它可以通过 WHERE 子句指定条件来删除部分数据;而 TRUNCATE 不支持 WHERE 子句,只能删除整体。
  • DELETE 会返回删除数据的行数,但是 TRUNCATE 只会返回 0,没有任何意义。

总结

当不需要该表时,用 DROP;当仍要保留该表,但要删除所有记录时,用 TRUNCATE;当要删除部分记录时,用 DELETE。


Comments

Content