MySQL学习(3)——表的增删查改(进阶)

1.数据库约束

1.1 约束类型

  1. NOT NULL - 指示某列不能存储null值
  2. UNIQUE - 保证某列的每行必须有唯一的值
  3. DEFAULT - 规定没有给列赋值时的默认值
  4. PRIMARY KEY - not null 和 unique 的结合,确保某列(或多列)有唯一的标识,有利于更快找到某个特定记录
  5. FOREIGN KEY - 保证一个表中的数据匹配另一个表中的值的参照完整性
  6. CHECK - 保证列中的值符合指定条件(mysql中没有,但是保留的其语法,即不会产生任何作用),了解就行

1.2 NULL约束 : 非空约束

创建表时,可以指定某列不为空:

语句实例:

-- 重新设置学生表结构
DROP TABLE IF EXISTS student;
CREATE TABLE student (
   id INT NOT NULL,
   sn INT,
   name VARCHAR(20),
   qq_mail VARCHAR(20)
);

结果演示

mysql> insert into student (sn) values (1);
ERROR 1364 (HY000): Field 'id' doesn't have a default value

一个非空的id在插入数据的时候没有默认值

1.3 UNIQUE :唯一约束

指定sn列是唯一的,不重复的:

语句实例:

-- 重新设置学生表结构
DROP TABLE IF EXISTS student;
CREATE TABLE student (
   id INT NOT NULL,
   sn INT UNIQUE,
   name VARCHAR(20),
   qq_mail VARCHAR(20)
);

结果演示

mysql> insert into student(id,sn) values (1,1);
Query OK, 1 row affected (0.01 sec)

mysql> insert into student(id,sn) values (1,1);
ERROR 1062 (23000): Duplicate entry '1' for key 'sn' 	

sn字段有一个重复的1

1.4 DEFAULT:默认值约束

指定列插入数据时,name列为空,默认值unkown:

语句实例:

-- 重新设置学生表结构
DROP TABLE IF EXISTS student;
CREATE TABLE student (
   id INT NOT NULL,
   sn INT UNIQUE,
   name VARCHAR(20) DEFAULT 'unkown',
   qq_mail VARCHAR(20)
);

结果演示

mysql> insert into student(id,sn) values (1,1);
Query OK, 1 row affected (0.02 sec)

mysql> select * from student;
+----+------+--------+---------+
| id | sn   | name   | qq_mail |
+----+------+--------+---------+
|  1 |    1 | unkown | NULL    |
+----+------+--------+---------+
1 row in set (0.00 sec)

mysql> insert into student(id,sn,name) values (1,2,null);
Query OK, 1 row affected (0.02 sec)

mysql> select * from student;
+----+------+--------+---------+
| id | sn   | name   | qq_mail |
+----+------+--------+---------+
|  1 |    1 | unkown | NULL    |
|  1 |    2 | NULL   | NULL    |
+----+------+--------+---------+
2 rows in set (0.00 sec)

插入数据不指定默认值时,默认就是’unkown’,如果给定默认值(如null),就会显示给定的值

1.5 PRIMARY KEY: 主键约束

指定id列为主键:

语句实例

-- 重新设置学生表结构
DROP TABLE IF EXISTS student;
CREATE TABLE student (
   id INT NOT NULL PRIMARY KEY,
   sn INT UNIQUE,
   name VARCHAR(20) DEFAULT 'unkown',
   qq_mail VARCHAR(20)
);

对于整数类型的主键,常配搭自增长auto_increment来使用,插入数据对应字段不给值的时候,使用最大值+1

语句实例

-- 主键是 NOT NULL 和 UNIQUE 的结合,可以不用 NOT NULL
id INT PRIMARY KEY auto_increment,

这个是mysql的语法,不是标准数据的语法

结合起来就是

-- 重新设置学生表结构
DROP TABLE IF EXISTS student;
CREATE TABLE student (
   id INT PRIMARY KEY auto_increment,
   sn INT UNIQUE,
   name VARCHAR(20) DEFAULT 'unkown',
   qq_mail VARCHAR(20)
);

结果演示

mysql> insert into student(sn) values (1);
Query OK, 1 row affected (0.02 sec)

mysql>  insert into student(sn) values (2);
Query OK, 1 row affected (0.01 sec)

mysql> select * from student;
+----+------+--------+---------+
| id | sn   | name   | qq_mail |
+----+------+--------+---------+
|  1 |    1 | unkown | NULL    |
|  2 |    2 | unkown | NULL    |
+----+------+--------+---------+
2 rows in set (0.00 sec)

mysql>  insert into student(id,sn) values (5,3);
Query OK, 1 row affected (0.01 sec)

mysql> select * from student;
+----+------+--------+---------+
| id | sn   | name   | qq_mail |
+----+------+--------+---------+
|  1 |    1 | unkown | NULL    |
|  2 |    2 | unkown | NULL    |
|  5 |    3 | unkown | NULL    |
+----+------+--------+---------+
3 rows in set (0.00 sec)

1.6 FOREIGN KEY: 外键约束

外键用于关联其他表的主键唯一键,语法:

foreign key (字段名) references 主表()

语句实例

  1. 创建班级表classes,id为主键
-- 创建班级表,有使用MySQL关键字作为字段时,需要使用``来标识,一般来说尽量避免
DROP TABLE IF EXISTS classes;
CREATE TABLE classes (
 id INT PRIMARY KEY auto_increment,
 name VARCHAR(20),
 `desc` VARCHAR(100)
);
  1. 创建学生表student,一个学生对应一个班级,一个班级对应多个学生,使用id为主键,classes_id为外键,关联班级表id
-- 重新设置学生表结构
DROP TABLE IF EXISTS student;
CREATE TABLE student (
   id INT PRIMARY KEY auto_increment,
   sn INT UNIQUE,
   name VARCHAR(20) DEFAULT 'unkown',
   qq_mail VARCHAR(20),
 classes_id int,
 FOREIGN KEY (classes_id) REFERENCES classes(id)
);

现在就能通过学生去找它对应的班级,同样的,班级也可以找到学生,但是要注意的是,一个学生找班级是唯一的(1对1),而一个班级找到的学生可以有很多(1对多)

1.7 CHECK约束(了解)

MySQL使用时不报错,但忽略该约束:

drop table if exists test_user;
create table test_user (
   id int,
   name varchar(20),
   sex varchar(1),
   check (sex ='男' or sex='女')
);

指定约束后可以插入其他值,mysql不用该语句,所以了解就行

2.表的设计

三大范式:

2.1 一对一

例如人和身份证:一个人有唯一的身份证,身份证只能确实一个唯一的人

两者关联互为一对一
MySQL学习(3)——表的增删查改(进阶)_第1张图片

2.2 一对多

例如学生和班级:一个学生只能有一个班级,但是!一个班级可以有多个学生

两者关联有一者是一对多,另一者是一对一

参考上面外键的实例
MySQL学习(3)——表的增删查改(进阶)_第2张图片

2.3 多对多

例如学生和课程:一个学生拥有多门课程,而一个课程同时也被很多学生学习

两者关联互为一对多
MySQL学习(3)——表的增删查改(进阶)_第3张图片

那么在数据库中,我们应该怎么创建如图的多对多的关系呢?

在java中我们可以在学生类里创建一个课程类型的数组然后通过学生对象的引用就可以获取到学生的课程信息,反之同理;那么在数据库当中我们没有引用,但是通过前面的例子,我们似乎可以使用外键来关联另一个表的主键,即相当于我可以通过学生表可以找到对应的课程表,但是!!!这样的话我们学生表只能保存一个课程信息,而我们的需求是学生对应多个课程,这样就不行了

假如我们设计多行学生信息表示同一个学生,然后关联每一个课程,这样是能够达到我们的一个学生关联多门课程的要求,但是,这样设计会存在问题,学生id是主键 ,不能重复的,另外,这样设计也会存在数据冗余,所以这种设计方式也不行

因此,我们可以再建一个考试表,通过考试这个业务行为让学生和课程间接的关联起来,这个表就相当于是一个中间表,让这个表通过两个外键分别指向两个表的主键,即下图的方式

MySQL学习(3)——表的增删查改(进阶)_第4张图片
语句实例

  1. 创建课程表
-- 创建课程表
DROP TABLE IF EXISTS course;
CREATE TABLE course (
   id INT PRIMARY KEY auto_increment,
   name VARCHAR(20)
);
  1. 创建学生表
-- 重新设置学生表结构
DROP TABLE IF EXISTS student;
CREATE TABLE student (
   id INT PRIMARY KEY auto_increment,
   sn INT UNIQUE,
   name VARCHAR(20) DEFAULT 'unkown',
   qq_mail VARCHAR(20)
);
  1. 创建考试信息表
-- 创建课程学生中间表:考试成绩表
DROP TABLE IF EXISTS score;
CREATE TABLE score (
   id INT PRIMARY KEY auto_increment,
   score DECIMAL(3, 1),
   student_id int,
   course_id int,
   FOREIGN KEY (student_id) REFERENCES student(id),
   FOREIGN KEY (course_id) REFERENCES course(id)
);

3.新增

和之前我们说过的新增不一样,这里的新增指的是将查询结果插入

语法:

INSERT INTO table_name [(column [, column ...])] SELECT ...

案例:创建一张用户表,设计有name姓名、email邮箱、sex性别、mobile手机号字段。需要把已有的学生数据复制进来,可以复制的字段为name、qq_mail

语句实例:

-- 创建用户表
DROP TABLE IF EXISTS test_user;
CREATE TABLE test_user (
   id INT primary key auto_increment,
   name VARCHAR(20) comment '姓名',
   age INT comment '年龄',
   email VARCHAR(20) comment '邮箱',
 sex varchar(1) comment '性别',
 mobile varchar(20) comment '手机号'
);

-- 将学生表中的所有数据复制到用户表
insert into test_user(name, email) select name, qq_mail from student;

结果演示:

mysql> -- 创建用户表
mysql> DROP TABLE IF EXISTS test_user;
Query OK, 0 rows affected (0.04 sec)
mysql> CREATE TABLE test_user (
    ->    id INT primary key auto_increment,
    ->    name VARCHAR(20) comment '姓名',
    ->    age INT comment '年龄',
    ->    email VARCHAR(20) comment '邮箱',
    ->  sex varchar(1) comment '性别',
    ->  mobile varchar(20) comment '手机号'
    -> );
Query OK, 0 rows affected (0.05 sec)

mysql> -- 将学生表中的所有数据复制到用户表
mysql> insert into test_user(name, email) select name, qq_mail from student;
Query OK, 8 rows affected (0.01 sec)
Records: 8  Duplicates: 0  Warnings: 0

mysql> select * from test_user;
+----+-----------------+------+------------------+------+--------+
| id | name            | age  | email            | sex  | mobile |
+----+-----------------+------+------------------+------+--------+
|  1 | 黑旋风李逵      | NULL | xuanfeng@qq.com  | NULL | NULL   |
|  2 | 菩提老祖        | NULL | NULL             | NULL | NULL   |
|  3 | 白素贞          | NULL | NULL             | NULL | NULL   |
|  4 | 许仙            | NULL | xuxian@qq.com    | NULL | NULL   |
|  5 | 不想毕业        | NULL | NULL             | NULL | NULL   |
|  6 | 好好说话        | NULL | say@qq.com       | NULL | NULL   |
|  7 | tellme          | NULL | NULL             | NULL | NULL   |
|  8 | 老外学中文      | NULL | foreigner@qq.com | NULL | NULL   |
+----+-----------------+------+------------------+------+--------+
8 rows in set (0.00 sec)

将select的结果集插入到用户表里

4.查询

4.1 聚合查询

4.1.1 聚合函数

常见的统计总数、计算平局值等操作,可以使用聚合函数来实现,常见的聚合函数有:

函数 说明
COUNT([DISTINCT] expr) 返回查询到的数据的 数量
SUM([DISTINCT] expr) 返回查询到的数据的 总和,不是数字没有意义
AVG([DISTINCT] expr) 返回查询到的数据的 平均值,不是数字没有意义
MAX([DISTINCT] expr) 返回查询到的数据的 最大值,不是数字没有意义
MIN([DISTINCT] expr) 返回查询到的数据的 最小值,不是数字没有意义

案例:

  1. count

语句实例

-- 统计班级共有多少同学
SELECT COUNT(*) FROM student;
SELECT COUNT(0) FROM student;

-- 统计班级收集的 qq_mail 有多少个,qq_mail 为 NULL 的数据不会计入结果
SELECT COUNT(qq_mail) FROM student;

结果演示

mysql> -- 统计班级共有多少同学
mysql> SELECT COUNT(*) FROM student;
+----------+
| COUNT(*) |
+----------+
|        8 |
+----------+
1 row in set (0.00 sec)

mysql> -- 统计班级收集的 qq_mail 有多少个,qq_mail 为 NULL 的数据不会计入结果
mysql> SELECT COUNT(qq_mail) FROM student;
+----------------+
| COUNT(qq_mail) |
+----------------+
|              4 |
+----------------+
1 row in set (0.00 sec)

count(*)的意思代表着取 select * from student;这个结果集的行数

例如

-- 取select 0 from student where id < 3;的结果集的行数
select count(0) from student where id < 3;

-- 结果演示
mysql> select count(0) from student where id < 3;
+----------+
| count(0) |
+----------+
|        2 |
+----------+
1 row in set (0.00 sec)

select 0 from student where id < 3; 它的结果集还是两行,只不过是常数0代替了表中的字段

  1. sum

语句实例:

-- 统计数学成绩总分
SELECT SUM(math) FROM exam_result;

-- 不及格 < 60 的总分,没有结果,返回 NULL
SELECT SUM(math) FROM exam_result WHERE math < 60;

结果演示

mysql> -- 统计数学成绩总分
mysql> SELECT SUM(math) FROM exam_result;
+-----------+
| SUM(math) |
+-----------+
|     581.5 |
+-----------+
1 row in set (0.00 sec)

mysql> -- 不及格 < 60 的总分,没有结果,返回 NULL
mysql> SELECT SUM(math) FROM exam_result WHERE math < 60;
+-----------+
| SUM(math) |
+-----------+
|      NULL |
+-----------+
1 row in set (0.00 sec)
  1. avg

语句实例:

mysql> -- 统计平均总分

结果演示

mysql> SELECT AVG(chinese + math + english) 平均总分 FROM exam_result;
+--------------+
| 平均总分     |
+--------------+
|    221.42857 |
+--------------+
1 row in set (0.03 sec)
  1. max

语句实例:

-- 返回英语最高分
SELECT MAX(english) FROM exam_result;

结果演示

mysql> -- 返回英语最高分
mysql> SELECT MAX(english) FROM exam_result;
+--------------+
| MAX(english) |
+--------------+
|         90.0 |
+--------------+
1 row in set (0.00 sec)
  1. min

语句实例:

-- 返回 > 70 分以上的数学最低分
SELECT MIN(math) FROM exam_result WHERE math > 70;

结果演示

mysql> -- 返回 > 70 分以上的数学最低分
mysql> SELECT MIN(math) FROM exam_result WHERE math > 70;
+-----------+
| MIN(math) |
+-----------+
|      73.0 |
+-----------+
1 row in set (0.00 sec)

4.1.2 GROUP BY子句

SELECT 中使用 GROUP BY 子句可以对指定列进行分组查询。需要满足:使用 GROUP BY 进行分组查询时,SELECT 指定的字段必须是“分组依据字段”,其他字段若想出现在SELECT 中则必须包含在聚合函数中

select column1, sum(column2), .. from table group by column1,column3;

案例:

准备测试表及数据:职员表,有id(主键)、name(姓名)、role(角色)、salary(薪水)

drop table if exists emp;
create table emp(
 id int primary key auto_increment,
 name varchar(20) not null,
 role varchar(20) not null,
 salary numeric(11,2)
);

insert into emp(name, role, salary) values
('马云','服务员', 1000.20),
('马化腾','游戏陪玩', 2000.99),
('孙悟空','游戏角色', 999.11),
('猪无能','游戏角色', 333.5),
('沙和尚','游戏角色', 700.33),
('隔壁老王','董事长', 12000.66);

查询每个角色的最高工资、最低工资和平均工资

select role,max(salary),min(salary),avg(salary) from emp group by role;

结果演示

mysql> select role,max(salary),min(salary),avg(salary) from emp group by role;
+--------------+-------------+-------------+--------------+
| role         | max(salary) | min(salary) | avg(salary)  |
+--------------+-------------+-------------+--------------+
| 服务员       |     1000.20 |     1000.20 |  1000.200000 |
| 游戏角色     |      999.11 |      333.50 |   677.646667 |
| 游戏陪玩     |     2000.99 |     2000.99 |  2000.990000 |
| 董事长       |    12000.66 |    12000.66 | 12000.660000 |
+--------------+-------------+-------------+--------------+
4 rows in set (0.00 sec)

4.1.3 HAVING

GROUP BY 子句进行分组以后,需要对分组结果再进行条件过滤时,不能使用 WHERE 语句,而需要用HAVING,因为having子句在group by字句后面,所以having子句中的字段要求和group by中字段要求相同

显示平均工资低于1500的角色和它的平均工资

语句实例:

select role,max(salary),min(salary),avg(salary) from emp group by role 
having avg(salary)<1500;

结果演示

mysql> select role,max(salary),min(salary),avg(salary) from emp group by role having avg(salary)<1500;
+--------------+-------------+-------------+-------------+
| role         | max(salary) | min(salary) | avg(salary) |
+--------------+-------------+-------------+-------------+
| 服务员       |     1000.20 |     1000.20 | 1000.200000 |
| 游戏角色     |      999.11 |      333.50 |  677.646667 |
+--------------+-------------+-------------+-------------+
2 rows in set (0.00 sec)

4.2 联合查询

实际开发中往往数据来自不同的表,所以需要多表联合查询。多表查询是对多张表的数据取笛卡尔积:

MySQL学习(3)——表的增删查改(进阶)_第5张图片
相当于是a表的每行数据去关联b表的每行数据,那么当a表有n行数据,b表有m行数据时,那么他们关联起来就有n*m条数据

注意: 关联查询可以对关联表使用别名

初始化测试数据:

insert into classes(name, `desc`) values
('计算机系2019级1班', '学习了计算机原理、C和Java语言、数据结构和算法'),
('中文系2019级3班','学习了中国传统文学'),
('自动化2019级5班','学习了机械自动化');

insert into student(sn, name, qq_mail, classes_id) values
('09982','黑旋风李逵','xuanfeng@qq.com',1),
('00835','菩提老祖',null,1),
('00391','白素贞',null,1),
('00031','许仙','xuxian@qq.com',1),
('00054','不想毕业',null,1),
('51234','好好说话','say@qq.com',2),
('83223','tellme',null,2),
('09527','老外学中文','foreigner@qq.com',2);

insert into course(name) values
('Java'),('中国传统文化'),('计算机原理'),('语文'),('高阶数学'),('英文');

insert into score(score, student_id, course_id) values
-- 黑旋风李逵
(70.5, 1, 1),(98.5, 1, 3),(33, 1, 5),(98, 1, 6),
-- 菩提老祖
(60, 2, 1),(59.5, 2, 5),
-- 白素贞
(33, 3, 1),(68, 3, 3),(99, 3, 5),
-- 许仙
(67, 4, 1),(23, 4, 3),(56, 4, 5),(72, 4, 6),
-- 不想毕业
(81, 5, 1),(37, 5, 5),
-- 好好说话
(56, 6, 2),(43, 6, 4),(79, 6, 6),
-- tellme
(80, 7, 2),(92, 7, 6);

4.2.1 内连接

语法:

select 字段 from1 别名1 [inner] join2 别名2 on 连接条件 and 其他条件;
select 字段 from1 别名1,2 别名2 where 连接条件 and 其他条件;

案例:

① 查询“许仙”同学的成绩

语句实例:

select sco.score from student stu inner join score sco on stu.id=sco.student_id
and stu.name='许仙';
-- 或者
select sco.score from student stu, score sco where stu.id=sco.student_id 
and stu.name='许仙';

结果演示

 select sco.score from student stu inner join score sco on stu.id=sco.student_id
    -> and stu.name='许仙';
+-------+
| score |
+-------+
|  67.0 |
|  23.0 |
|  56.0 |
|  72.0 |
+-------+
4 rows in set (0.00 sec)

mysql> select sco.score from student stu, score sco where stu.id=sco.student_id
    -> and stu.name='许仙';
+-------+
| score |
+-------+
|  67.0 |
|  23.0 |
|  56.0 |
|  72.0 |
+-------+
4 rows in set (0.00 sec)

② 查询所有同学的总成绩,及同学个人信息:

语句实例:

-- 成绩表对学生表是多对1关系,查询总成绩是根据成绩表的同学id来进行分组的
SELECT
 stu.sn,
 stu.NAME,
 stu.qq_mail,
 sum( sco.score ) 
FROM
 student stu
 JOIN score sco ON stu.id = sco.student_id
GROUP BY
 sco.student_id;

group by 字段:查询列需要是分组字段,或是聚合函数,非分组字段需要为分组的最小粒度(查询列需要为分组列的相同行集),即就是和查询列在同一行的其他列(例如学生表id,name,sn他们属于一个相同的行集)

结果演示

+-------+-----------------+-----------------+------------------+
| sn    | NAME            | qq_mail         | sum( sco.score ) |
+-------+-----------------+-----------------+------------------+
|  9982 | 黑旋风李逵      | xuanfeng@qq.com |            300.0 |
|   835 | 菩提老祖        | NULL            |            119.5 |
|   391 | 白素贞          | NULL            |            200.0 |
|    31 | 许仙            | xuxian@qq.com   |            218.0 |
|    54 | 不想毕业        | NULL            |            118.0 |
| 51234 | 好好说话        | say@qq.com      |            178.0 |
| 83223 | tellme          | NULL            |            172.0 |
+-------+-----------------+-----------------+------------------+
7 rows in set (0.00 sec)

③ 查询所有同学的成绩,及同学个人信息:

语句实例:

-- 查询出来的都是有成绩的同学,“老外学中文”同学 没有显示
select * from student stu join score sco on stu.id=sco.student_id;

-- 学生表、成绩表、课程表3张表关联查询
SELECT
 stu.id,
 stu.sn,
 stu.NAME,
 stu.qq_mail,
 sco.score,
 sco.course_id,
 cou.NAME
FROM
 student stu
 JOIN score sco ON stu.id = sco.student_id
 JOIN course cou ON sco.course_id = cou.id
ORDER BY
 stu.id;

结果演示

mysql> -- 查询出来的都是有成绩的同学,"老外学中文"同学 没有显示
mysql> select * from student stu join score sco on stu.id=sco.student_id;
+----+-------+-----------------+-----------------+------------+----+-------+------------+-----------+
| id | sn    | name            | qq_mail         | classes_id | id | score | student_id | course_id |
+----+-------+-----------------+-----------------+------------+----+-------+------------+-----------+
|  1 |  9982 | 黑旋风李逵      | xuanfeng@qq.com |          1 |  1 |  70.5 |          1 |         1 |
|  1 |  9982 | 黑旋风李逵      | xuanfeng@qq.com |          1 |  2 |  98.5 |          1 |         3 |
|  1 |  9982 | 黑旋风李逵      | xuanfeng@qq.com |          1 |  3 |  33.0 |          1 |         5 |
|  1 |  9982 | 黑旋风李逵      | xuanfeng@qq.com |          1 |  4 |  98.0 |          1 |         6 |
|  2 |   835 | 菩提老祖        | NULL            |          1 |  5 |  60.0 |          2 |         1 |
|  2 |   835 | 菩提老祖        | NULL            |          1 |  6 |  59.5 |          2 |         5 |
|  3 |   391 | 白素贞          | NULL            |          1 |  7 |  33.0 |          3 |         1 |
|  3 |   391 | 白素贞          | NULL            |          1 |  8 |  68.0 |          3 |         3 |
|  3 |   391 | 白素贞          | NULL            |          1 |  9 |  99.0 |          3 |         5 |
|  4 |    31 | 许仙            | xuxian@qq.com   |          1 | 10 |  67.0 |          4 |         1 |
|  4 |    31 | 许仙            | xuxian@qq.com   |          1 | 11 |  23.0 |          4 |         3 |
|  4 |    31 | 许仙            | xuxian@qq.com   |          1 | 12 |  56.0 |          4 |         5 |
|  4 |    31 | 许仙            | xuxian@qq.com   |          1 | 13 |  72.0 |          4 |         6 |
|  5 |    54 | 不想毕业        | NULL            |          1 | 14 |  81.0 |          5 |         1 |
|  5 |    54 | 不想毕业        | NULL            |          1 | 15 |  37.0 |          5 |         5 |
|  6 | 51234 | 好好说话        | say@qq.com      |          2 | 16 |  56.0 |          6 |         2 |
|  6 | 51234 | 好好说话        | say@qq.com      |          2 | 17 |  43.0 |          6 |         4 |
|  6 | 51234 | 好好说话        | say@qq.com      |          2 | 18 |  79.0 |          6 |         6 |
|  7 | 83223 | tellme          | NULL            |          2 | 19 |  80.0 |          7 |         2 |
|  7 | 83223 | tellme          | NULL            |          2 | 20 |  92.0 |          7 |         6 |
+----+-------+-----------------+-----------------+------------+----+-------+------------+-----------+
20 rows in set (0.00 sec)

-- 三张表关联
+----+-------+-----------------+-----------------+-------+-----------+--------------------+
| id | sn    | NAME            | qq_mail         | score | course_id | NAME               |
+----+-------+-----------------+-----------------+-------+-----------+--------------------+
|  1 |  9982 | 黑旋风李逵      | xuanfeng@qq.com |  70.5 |         1 | Java               |
|  1 |  9982 | 黑旋风李逵      | xuanfeng@qq.com |  98.5 |         3 | 计算机原理         |
|  1 |  9982 | 黑旋风李逵      | xuanfeng@qq.com |  33.0 |         5 | 高阶数学           |
|  1 |  9982 | 黑旋风李逵      | xuanfeng@qq.com |  98.0 |         6 | 英文               |
|  2 |   835 | 菩提老祖        | NULL            |  60.0 |         1 | Java               |
|  2 |   835 | 菩提老祖        | NULL            |  59.5 |         5 | 高阶数学           |
|  3 |   391 | 白素贞          | NULL            |  33.0 |         1 | Java               |
|  3 |   391 | 白素贞          | NULL            |  68.0 |         3 | 计算机原理         |
|  3 |   391 | 白素贞          | NULL            |  99.0 |         5 | 高阶数学           |
|  4 |    31 | 许仙            | xuxian@qq.com   |  67.0 |         1 | Java               |
|  4 |    31 | 许仙            | xuxian@qq.com   |  23.0 |         3 | 计算机原理         |
|  4 |    31 | 许仙            | xuxian@qq.com   |  56.0 |         5 | 高阶数学           |
|  4 |    31 | 许仙            | xuxian@qq.com   |  72.0 |         6 | 英文               |
|  5 |    54 | 不想毕业        | NULL            |  81.0 |         1 | Java               |
|  5 |    54 | 不想毕业        | NULL            |  37.0 |         5 | 高阶数学           |
|  6 | 51234 | 好好说话        | say@qq.com      |  56.0 |         2 | 中国传统文化       |
|  6 | 51234 | 好好说话        | say@qq.com      |  43.0 |         4 | 语文               |
|  6 | 51234 | 好好说话        | say@qq.com      |  79.0 |         6 | 英文               |
|  7 | 83223 | tellme          | NULL            |  80.0 |         2 | 中国传统文化       |
|  7 | 83223 | tellme          | NULL            |  92.0 |         6 | 英文               |
+----+-------+-----------------+-----------------+-------+-----------+--------------------+
20 rows in set (0.00 sec)

4.2.2 外连接

外连接分为左外连接和右外连接。如果联合查询,左侧的表完全显示我们就说是左外连接;右侧的表完全显示我们就说是右外连接

语法:

-- 左外连接,表1完全显示
select 字段名  from 表名1 left join 表名2 on 连接条件;

-- 右外连接,表2完全显示
select 字段 from 表名1 right join 表名2 on 连接条件;

案例:
查询所有同学的成绩,及同学的个人信息,如果该同学没有成绩,也需要显示,我们刚刚的表里的“老外学中文”同学就没有成绩,因此他没有在表中显示,现在我们要让他也显示在表中

语句实例:

-- “老外学中文”同学 没有考试成绩,也显示出来了
select * from student stu left join score sco on stu.id=sco.student_id;

-- 对应的右外连接为:
select * from score sco right join student stu on stu.id=sco.student_id;

-- 学生表、成绩表、课程表3张表关联查询
SELECT
 stu.id,
 stu.sn,
 stu.NAME,
 stu.qq_mail,
 sco.score,
 sco.course_id,
 cou.NAME
FROM
 student stu
 LEFT JOIN score sco ON stu.id = sco.student_id
 LEFT JOIN course cou ON sco.course_id = cou.id
ORDER BY
 stu.id;

结果演示

+----+-------+-----------------+------------------+------------+------+-------+------------+-----------+
| id | sn    | name            | qq_mail          | classes_id | id   | score | student_id | course_id |
+----+-------+-----------------+------------------+------------+------+-------+------------+-----------+
|  1 |  9982 | 黑旋风李逵      | xuanfeng@qq.com  |          1 |    1 |  70.5 |          1 |         1 |
|  1 |  9982 | 黑旋风李逵      | xuanfeng@qq.com  |          1 |    2 |  98.5 |          1 |         3 |
|  1 |  9982 | 黑旋风李逵      | xuanfeng@qq.com  |          1 |    3 |  33.0 |          1 |         5 |
|  1 |  9982 | 黑旋风李逵      | xuanfeng@qq.com  |          1 |    4 |  98.0 |          1 |         6 |
|  2 |   835 | 菩提老祖        | NULL             |          1 |    5 |  60.0 |          2 |         1 |
|  2 |   835 | 菩提老祖        | NULL             |          1 |    6 |  59.5 |          2 |         5 |
|  3 |   391 | 白素贞          | NULL             |          1 |    7 |  33.0 |          3 |         1 |
|  3 |   391 | 白素贞          | NULL             |          1 |    8 |  68.0 |          3 |         3 |
|  3 |   391 | 白素贞          | NULL             |          1 |    9 |  99.0 |          3 |         5 |
|  4 |    31 | 许仙            | xuxian@qq.com    |          1 |   10 |  67.0 |          4 |         1 |
|  4 |    31 | 许仙            | xuxian@qq.com    |          1 |   11 |  23.0 |          4 |         3 |
|  4 |    31 | 许仙            | xuxian@qq.com    |          1 |   12 |  56.0 |          4 |         5 |
|  4 |    31 | 许仙            | xuxian@qq.com    |          1 |   13 |  72.0 |          4 |         6 |
|  5 |    54 | 不想毕业        | NULL             |          1 |   14 |  81.0 |          5 |         1 |
|  5 |    54 | 不想毕业        | NULL             |          1 |   15 |  37.0 |          5 |         5 |
|  6 | 51234 | 好好说话        | say@qq.com       |          2 |   16 |  56.0 |          6 |         2 |
|  6 | 51234 | 好好说话        | say@qq.com       |          2 |   17 |  43.0 |          6 |         4 |
|  6 | 51234 | 好好说话        | say@qq.com       |          2 |   18 |  79.0 |          6 |         6 |
|  7 | 83223 | tellme          | NULL             |          2 |   19 |  80.0 |          7 |         2 |
|  7 | 83223 | tellme          | NULL             |          2 |   20 |  92.0 |          7 |         6 |
|  8 |  9527 | 老外学中文      | foreigner@qq.com |          2 | NULL |  NULL |       NULL |      NULL |
+----+-------+-----------------+------------------+------------+------+-------+------------+-----------+
21 rows in set (0.00 sec)

外连接的左/右连接的左/右表,全部显示,不以连接条件为依据,其他条件还是会生效

这里细心的小伙伴可能心里就会发现,对于我们上面实例语句中得最后一个LEFT JOIN score sco ON stu.id = sco.student_id

LEFT JOIN course cou ON sco.course_id = cou.id
这个语句来说,course关联的是前面student和score关联的结果集

例如我们将语句变成下面这样

 SELECT
 stu.id,
 stu.sn,
 stu.NAME,
 stu.qq_mail,
 sco.score,
 sco.course_id,
 cou.NAME
FROM
 student stu
 LEFT JOIN score sco ON stu.id = sco.student_id
JOIN course cou ON sco.course_id = cou.id
AND sco.score > 60;

结果演示

+----+-------+-----------------+-----------------+-------+-----------+--------------------+
| id | sn    | NAME            | qq_mail         | score | course_id | NAME               |
+----+-------+-----------------+-----------------+-------+-----------+--------------------+
|  1 |  9982 | 黑旋风李逵      | xuanfeng@qq.com |  70.5 |         1 | Java               |
|  1 |  9982 | 黑旋风李逵      | xuanfeng@qq.com |  98.5 |         3 | 计算机原理         |
|  1 |  9982 | 黑旋风李逵      | xuanfeng@qq.com |  98.0 |         6 | 英文               |
|  3 |   391 | 白素贞          | NULL            |  68.0 |         3 | 计算机原理         |
|  3 |   391 | 白素贞          | NULL            |  99.0 |         5 | 高阶数学           |
|  4 |    31 | 许仙            | xuxian@qq.com   |  67.0 |         1 | Java               |
|  4 |    31 | 许仙            | xuxian@qq.com   |  72.0 |         6 | 英文               |
|  5 |    54 | 不想毕业        | NULL            |  81.0 |         1 | Java               |
|  6 | 51234 | 好好说话        | say@qq.com      |  79.0 |         6 | 英文               |
|  7 | 83223 | tellme          | NULL            |  80.0 |         2 | 中国传统文化       |
|  7 | 83223 | tellme          | NULL            |  92.0 |         6 | 英文               |
+----+-------+-----------------+-----------------+-------+-----------+--------------------+
11 rows in set (0.00 sec)

那么我们就会看到此时第一次连接的结果集就会严格按照course连接的条件进行连接,这也就提醒我们什么时候该用什么样的连接,这一点值得小伙伴们认真斟酌

4.2.3 自连接

自连接是指在同一张表连接自身进行查询,多使用在不同行的同一列来进行比较

案例:

显示所有“计算机原理”成绩比“Java”成绩高的成绩信息

语句实例:

 -- 先查询“计算机原理”和“Java”课程的id
select id,name from course where name='Java' or name='计算机原理';
-- 再查询成绩表中,“计算机原理”成绩比“Java”成绩 好的信息
SELECT
 s1.* 
FROM
 score s1,
 score s2 
WHERE
 s1.student_id = s2.student_id
 AND s1.score < s2.score
 AND s1.course_id = 1
 AND s2.course_id = 3;
-- 也可以使用join on 语句来进行自连接查询
SELECT
 s1.* 
FROM
 score s1
 JOIN score s2 ON s1.student_id = s2.student_id
 AND s1.score < s2.score
 AND s1.course_id = 1
 AND s2.course_id = 3;

以上查询只显示了成绩信息,并且是分步执行的。要显示学生及成绩信息,并在一条语句显示:

语句实例:

SELECT
 stu.*,
 s1.score Java,
 s2.score 计算机原理
FROM
 score s1
 JOIN score s2 ON s1.student_id = s2.student_id
 JOIN student stu ON s1.student_id = stu.id
 JOIN course c1 ON s1.course_id = c1.id
 JOIN course c2 ON s2.course_id = c2.id
 AND s1.score < s2.score
 AND c1.NAME = 'Java'
 AND c2.NAME = '计算机原理';

结果演示

+----+------+-----------------+-----------------+------------+------+-----------------+
| id | sn   | name            | qq_mail         | classes_id | Java | 计算机原理      |
+----+------+-----------------+-----------------+------------+------+-----------------+
|  1 | 9982 | 黑旋风李逵      | xuanfeng@qq.com |          1 | 70.5 |            98.5 |
|  3 |  391 | 白素贞          | NULL            |          1 | 33.0 |            68.0 |
+----+------+-----------------+-----------------+------------+------+-----------------+
2 rows in set (0.00 sec)

连接的不是表!而是连接的结果集!

4.2.4 子查询

子查询是指嵌入在其他sql语句中的select语句,也叫嵌套查询

单行子查询:返回一行记录的子查询

查询与“不想毕业” 同学的同班同学:

语句实例

select * from student where classes_id=(select classes_id from student where name='不想毕业');

结果演示

+----+------+-----------------+-----------------+------------+
| id | sn   | name            | qq_mail         | classes_id |
+----+------+-----------------+-----------------+------------+
|  1 | 9982 | 黑旋风李逵      | xuanfeng@qq.com |          1 |
|  2 |  835 | 菩提老祖        | NULL            |          1 |
|  3 |  391 | 白素贞          | NULL            |          1 |
|  4 |   31 | 许仙            | xuxian@qq.com   |          1 |
|  5 |   54 | 不想毕业        | NULL            |          1 |
+----+------+-----------------+-----------------+------------+
5 rows in set (0.00 sec)

括号里的部分就是一个结果集!!! 小伙伴今后如果看见这样的语句觉得不太懂的话,可以先去执行括号里的语句,看一看他的结果是什么,而括号外面的语句就是以这个括号里面作为结果集来查询的

多行子查询:返回多行记录的子查询
案例:查询“语文”或“英文”课程的成绩信息

  1. [NOT] IN关键字:

语句实例:

-- 使用IN
select * from score where course_id in (select id from course where
name='语文' or name='英文');

-- 使用 NOT IN
select * from score where course_id not in (select id from course where
name!='语文' and name!='英文');

结果演示

+----+-------+------------+-----------+
| id | score | student_id | course_id |
+----+-------+------------+-----------+
| 17 |  43.0 |          6 |         4 |
|  4 |  98.0 |          1 |         6 |
| 13 |  72.0 |          4 |         6 |
| 18 |  79.0 |          6 |         6 |
| 20 |  92.0 |          7 |         6 |
+----+-------+------------+-----------+
5 rows in set (0.00 sec)

可以使用多列包含:

-- 插入重复的分数:score, student_id, course_id列重复
insert into score(score, student_id, course_id) values
-- 黑旋风李逵
(70.5, 1, 1),(98.5, 1, 3),
-- 菩提老祖
(60, 2, 1);
-- 查询重复的分数
SELECT
 * 
FROM
 score 
WHERE
 ( score, student_id, course_id ) IN ( SELECT score, student_id, 
course_id FROM score GROUP BY score, student_id, course_id HAVING
count( 0 ) > 1 );

结果演示

+----+-------+------------+-----------+
| id | score | student_id | course_id |
+----+-------+------------+-----------+
|  1 |  70.5 |          1 |         1 |
|  2 |  98.5 |          1 |         3 |
|  5 |  60.0 |          2 |         1 |
| 21 |  70.5 |          1 |         1 |
| 22 |  98.5 |          1 |         3 |
| 23 |  60.0 |          2 |         1 |
+----+-------+------------+-----------+
6 rows in set (0.00 sec)
  1. [NOT] EXISTS关键字:

语句实例

-- 使用 EXISTS
select * from score sco where exists (select sco.id from course cou 
where (name='语文' or name='英文') and cou.id = sco.course_id);

-- 使用 NOT EXISTS
select * from score sco where not exists (select sco.id from course cou 
where (name!='语文' and name!='英文') and cou.id = sco.course_id);

结果演示

+----+-------+------------+-----------+
| id | score | student_id | course_id |
+----+-------+------------+-----------+
|  4 |  98.0 |          1 |         6 |
| 13 |  72.0 |          4 |         6 |
| 17 |  43.0 |          6 |         4 |
| 18 |  79.0 |          6 |         6 |
| 20 |  92.0 |          7 |         6 |
+----+-------+------------+-----------+
5 rows in set (0.00 sec)

在from子句中使用子查询:子查询语句出现在from子句中。这里要用到数据查询的技巧,把一个子查询当做一个临时表使用

查询所有比“中文系2019级3班”平均分高的成绩信息:

语句实例

-- 获取“中文系2019级3班”的平均分,将其看作临时表
SELECT
 avg( sco.score ) score 
FROM
 score sco
 JOIN student stu ON sco.student_id = stu.id
 JOIN classes cls ON stu.classes_id = cls.id
WHERE
 cls.NAME = '中文系2019级3班';

查询成绩表中,比以上临时表平均分高的成绩:

SELECT
 * 
FROM
 score sco,
 (
 SELECT
 avg( sco.score ) score 
 FROM
 score sco
 JOIN student stu ON sco.student_id = stu.id
 JOIN classes cls ON stu.classes_id = cls.id
 WHERE
 	cls.NAME = '中文系2019级3班'
 ) tmp 
WHERE
 sco.score > tmp.score;

结果演示

+----+-------+------------+-----------+----------+
| id | score | student_id | course_id | score    |
+----+-------+------------+-----------+----------+
|  1 |  70.5 |          1 |         1 | 70.00000 |
|  2 |  98.5 |          1 |         3 | 70.00000 |
|  4 |  98.0 |          1 |         6 | 70.00000 |
|  9 |  99.0 |          3 |         5 | 70.00000 |
| 13 |  72.0 |          4 |         6 | 70.00000 |
| 14 |  81.0 |          5 |         1 | 70.00000 |
| 18 |  79.0 |          6 |         6 | 70.00000 |
| 19 |  80.0 |          7 |         2 | 70.00000 |
| 20 |  92.0 |          7 |         6 | 70.00000 |
| 21 |  70.5 |          1 |         1 | 70.00000 |
| 22 |  98.5 |          1 |         3 | 70.00000 |
+----+-------+------------+-----------+----------+
11 rows in set (0.00 sec)

外表数据遍历,子查询结果集有/没有数据,外表使用exists就是显示/不显示当前行,not exists取反

4.2.5 合并查询

在实际应用中,为了合并多个select的执行结果,可以使用集合操作符 union,union all。使用UNION和UNION ALL时,前后查询的结果集中,字段需要一致,union效率比or更高,具体我们后面讲解

  1. union

该操作符用于取得两个结果集的并集。当使用该操作符时,会自动去掉结果集中的重复行

案例:查询id小于3,或者名字为“英文”的课程:

语句实例:

select * from course where id<3
union
select * from course where name='英文';

-- 或者使用or来实现
select * from course where id<3 or name='英文';

结果演示

+----+--------------------+
| id | name               |
+----+--------------------+
|  1 | Java               |
|  2 | 中国传统文化       |
|  6 | 英文               |
+----+--------------------+
3 rows in set (0.00 sec)
  1. union all

该操作符用于取得两个结果集的并集。当使用该操作符时,不会去掉结果集中的重复行

案例:查询id小于3,或者名字为“Java”的课程:

语句实例:

-- 可以看到结果集中出现重复数据Java
select * from course where id<3
union all
select * from course where name='英文';

结果演示

+----+--------------------+
| id | name               |
+----+--------------------+
|  1 | Java               |
|  2 | 中国传统文化       |
|  6 | 英文               |
+----+--------------------+
3 rows in set (0.00 sec)

5. 内容重点总结

  1. 数据库的几种约束
  2. 表的关系:一对一,一对多,多对多
  3. 新增
  4. 查询:聚合函数,分组查询,内连接,外连接,自连接,子查询,合并查询

SQL查询中各个关键字的执行先后顺序: from > on> join > where > group by > with > having >select > distinct > order by > limit

你可能感兴趣的