欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

MySQL学习笔记(三)-约束和多表查询

程序员文章站 2022-05-29 16:48:12
...

MySQL学习笔记(三)

1 完整性约束

完整性约束是为了表的数据的正确性!如果数据不正确,那么一开始就不能添加到表中。

1.1 主键

当某一列添加了主键约束后,那么这一列的数据就不能重复出现。这样每行记录中其主键列的值就是这一行的唯一标识。例如学生的学号可以用来做唯一标识,而学生的姓名是不能做唯一标识的,因为学生有可能同名。

主键列的值不能为NULL,也不能重复!指定主键约束使用PRIMARY KEY关键字。

  • 创建表:定义列时指定主键 :

    CREATE TABLE stu(
    		sid	    CHAR(6) PRIMARY KEY,
    		sname	VARCHAR(20),
    		age		INT,
    		gender	VARCHAR(10) 
    );
    
  • 创建表:定义列之后独立指定主键:

    CREATE TABLE stu(
    		sid	    CHAR(6),
    		sname	VARCHAR(20),
    		age		INT,
    		gender	VARCHAR(10),
    		PRIMARY KEY(sid)
    );
    
  • 修改表时指定主键 :

    ALTER TABLE stu
    ADD PRIMARY KEY(sid);
    或者
    ALTER TABLE stu MODIFY sid CHAR(6) PRIMARY KEY;
    
  • 删除主键(只是删除主键约束,而不会删除主键列)

    ALTER TABLE stu DROP PRIMARY KEY
    

1.2 主键自增长

MySQL提供了主键自动增长的功能!这样用户就不用再为是否有主键是否重复而烦恼了。当主键设置为自动增长后,在没有给出主键值时,主键的值会自动生成,而且是最大主键值+1,也就不会出现重复主键的可能了。

  • 创建表时设置主键自增长(主键必须是整型才可以自增长):

    CREATE TABLE stu(
    		sid INT PRIMARY KEY AUTO_INCREMENT,
    		sname	VARCHAR(20),
    		age		INT,
    		gender	VARCHAR(10)
    );
    
    
  • 修改表时设置主键自增长:

    ALTER TABLE stu CHANGE sid sid INT AUTO_INCREMENT;
    
  • 修改表时删除主键自增长:

    ALTER TABLE stu CHANGE sid sid INT;
    

1.3 非空约束

指定非空约束的列不能没有值,也就是说在插入记录时,对添加了非空约束的列一定要给值;在修改记录时,不能把非空列的值设置为NULL。

指定非空约束 :

CREATE TABLE stu(
		sid INT PRIMARY KEY AUTO_INCREMENT,
		sname VARCHAR(10) NOT NULL,
		age		INT,
		gender	VARCHAR(10)
);

当为sname字段指定为非空后,在向stu表中插入记录时,必须给sname字段指定值,否则会报错 。

1.4 唯一约束

还可以为字段指定唯一约束!当为字段指定唯一约束后,那么字段的值必须是唯一的。这一点与主键相似!例如给stu表的sname字段指定唯一约束:

CREATE TABLE tab_ab(
	sid INT PRIMARY KEY AUTO_INCREMENT,
	sname VARCHAR(10) UNIQUE
);
INSERT INTO sname(sid, sname) VALUES(1001, 'zs');
INSERT INTO sname(sid, sname) VALUES(1002, 'zs');

当两次插入相同的名字时,MySQL会报错!

1.5 外键约束

主外键是构成表与表关联的唯一途径!

外键是另一张表的主键!例如员工表与部门表之间就存在关联关系,其中员工表中的部门编号字段就是外键,是相对部门表的外键。

  • 创建时指定外键

    CREATE TABLE emp(#雇员表
    	empno		INT,#员工编号
    	ename		VARCHAR(50),#员工姓名
    	job		VARCHAR(50),#员工工资
    	mgr		INT,#领导编号
    	hiredate	DATE,#入职日期
    	sal		DECIMAL(7,2),#月薪
    	comm		decimal(7,2),#奖金
    	deptno		INT,#部门编号
        CONSTRAINT fk_dept FOREIGN KEY(deptno) REFERENCES dept(deptno)
    ) ;
    
  • 修改时指定外键

    alter table emp add constraint fk_dept FOREIGN KEY(deptno) references dept(deptno);
    
  • 修改时删除外键

    alter table emp drop foreign key fk_dept;
    

1.6 表与表之间的关系

  • 一对一:例如t_person表和t_card表,即人和身份证。这种情况需要找出主从关系,即谁是主表,谁是从表。人可以没有身份证,但身份证必须要有人才行,所以人是主表,而身份证是从表。设计从表可以有两种方案:
    1. 在t_card表中添加外键列(相对t_user表),并且给外键添加唯一约束;
    2. 给t_card表的主键添加外键约束(相对t_user表),即t_card表的主键也是外键。
  • 一对多(多对一):最为常见的就是一对多!一对多和多对一,这是从哪个角度去看得出来的。t_user和t_section的关系,从t_user来看就是一对多,而从t_section的角度来看就是多对一!这种情况都是在多方创建外键!
  • 多对多:例如t_stu和t_teacher表,即一个学生可以有多个老师,而一个老师也可以有多个学生。这种情况通常需要创建中间表来处理多对多关系。例如再创建一张表t_stu_tea表,给出两个外键,一个相对t_stu表的外键,另一个相对t_teacher表的外键。

2 编码

2.1 MySQL编码

mysql> SHOW VARIABLES LIKE 'char%';
+--------------------------+----------------------------+
| Variable_name            | Value                      |
+--------------------------+----------------------------+
| character_set_client     | utf8                       |
| character_set_connection | utf8                       |
| character_set_database   | latin1                     |
| character_set_filesystem | binary                     |
| character_set_results    | utf8                       |
| character_set_server     | latin1                     |
| character_set_system     | utf8                       |
| character_sets_dir       | /usr/share/mysql/charsets/ |
+--------------------------+----------------------------+
8 rows in set (0.00 sec)

  • character_set_client:你发送的数据必须与client指定的编码一致!!!服务器会使用该编码来解读客户端发送过来的数据;
  • character_set_connection:通过该编码与client一致!该编码不会导致乱码!当执行的是查询语句时,客户端发送过来的数据会先转换成connection指定的编码。但只要客户端发送过来的数据与client指定的编码一致,那么转换就不会出现问题;
  • character_set_database:数据库默认编码,在创建数据库时,如果没有指定编码,那么默认使用database编码;
  • character_set_server:MySQL服务器默认编码;
  • character_set_results:响应的编码,即查询结果返回给客户端的编码。这说明客户端必须使用result指定的编码来解码;

2.2 控制台编码

修改character_set_client、character_set_results、character_set_connection为GBK,就不会出现乱码了。但其实只需要修改character_set_client和character_set_results。

控制台的编码只能是GBK,而不能修改为UTF8,这就出现一个问题。客户端发送的数据是GBK,而character_set_client为UTF8,这就说明客户端数据到了服务器端后一定会出现乱码。既然不能修改控制台的编码,那么只能修改character_set_client为GBK了。

服务器发送给客户端的数据编码为character_set_result,它如果是UTF8,那么控制台使用GBK解码也一定会出现乱码。因为无法修改控制台编码,所以只能把character_set_result修改为GBK。

  • 修改character_set_client变量:set character_set_client=gbk;
  • 修改character_set_results变量:set character_set_results=gbk;

设置编码只对当前连接有效,这说明每次登录MySQL提示符后都要去修改这两个编码,但可以通过修改配置文件来处理这一问题。

2.3 MySQL工具

使用MySQL工具是不会出现乱码的,因为它们会每次连接时都修改character_set_client、character_set_results、character_set_connection的编码。这样对my.ini上的配置覆盖了,也就不会出现乱码了。

3 备份和恢复数据

3.1 备份数据

在控制台使用mysqldump命令可以用来生成指定数据库的脚本文本,但要注意,脚本文本中只包含数据库的内容,而不会存在创建数据库的语句!所以在恢复数据时,还需要自已手动创建一个数据库之后再去恢复数据。

mysqldump –u用户名 –p密码 数据库名>生成的脚本文件路径
mysqldump -uroot -proot mydb1>stu.sql

注意,mysqldump命令是在控制台下执行,无需登录mysql!!!

3.2 恢复数据

执行SQL脚本需要登录mysql,然后进入指定数据库,才可以执行SQL脚本!!!

执行SQL脚本不只是用来恢复数据库,也可以在平时编写SQL脚本,然后使用执行SQL 脚本来操作数据库!大家都知道,在黑屏下编写SQL语句时,就算发现了错误,可能也不能修改了。所以我建议大家使用脚本文件来编写SQL代码,然后执行之!

#登录mysql后执行下面代码
source stu.sql
或者
mysql -uroot -p123 mydb1<stu.sql

注意,在执行脚本时需要先行核查当前数据库中的表是否与脚本文件中的语句有冲突!例如在脚本文件中存在create table a的语句,而当前数据库中已经存在了a表,那么就会出错!

4 多表查询

多表查询有如下几种:

  • 合并结果集;
  • 连接查询

​ 内连接

​ 外连接

​ 左外连接

​ 右外连接

​ 全外连接(MySQL不支持)

​ 自然连接

  • 子查询

4.1 合并结果集

  1. 作用:合并结果集就是把两个select语句的查询结果合并到一起!
  2. 合并结果集有两种方式:
    • UNION:去除重复记录,例如:SELECT * FROM t1 UNION SELECT * FROM t2;
    • UNION ALL:不去除重复记录,例如:SELECT * FROM t1 UNION ALL SELECT * FROM t2。
  3. 要求:被合并的两个结果:列数、列类型必须相同。

t1表

a b
1 a
2 b
3 c
4 d

t2表

c d
3 c
4 d
5 e

执行SELECT * FROM t1 UNION ALL SELECT * FROM t2

结果:

a b
1 a
2 b
3 c
3 c
4 d
4 d
5 e

4.2 连接查询

连接查询就是求出多个表的乘积,例如t1连接t2,那么查询出的结果就是t1*t2。

mysql> select * from t1,t2;
+------+------+------+------+
| a    | b    | c    | d    |
+------+------+------+------+
|    1 | a    |    3 | c    |
|    1 | a    |    4 | d    |
|    1 | a    |    5 | e    |
|    2 | b    |    3 | c    |
|    2 | b    |    4 | d    |
|    2 | b    |    5 | e    |
|    3 | c    |    3 | c    |
|    3 | c    |    4 | d    |
|    3 | c    |    5 | e    |
|    4 | d    |    3 | c    |
|    4 | d    |    4 | d    |
|    4 | d    |    5 | e    |
+------+------+------+------+
12 rows in set (0.00 sec)

连接查询会产生笛卡尔积,假设集合A={a,b},集合B={0,1,2},则两个集合的笛卡尔积为{(a,0),(a,1),(a,2),(b,0),(b,1),(b,2)}。可以扩展到多个集合的情况。

那么多表查询产生这样的结果并不是我们想要的,那么怎么去除重复的,不想要的记录呢,当然是通过条件过滤。通常要查询的多个表之间都存在关联关系,那么就通过关联关系去除笛卡尔积。

你能想像到emp和dept表连接查询的结果么?emp一共14行记录,dept表一共4行记录,那么连接后查询出的结果是56行记录。

也就你只是想在查询emp表的同时,把每个员工的所在部门信息显示出来,那么就需要使用主外键来去除无用信息了。

实例

DROP DATABASE IF EXISTS exam;
CREATE DATABASE IF NOT EXISTS exam;

USE exam;

/*创建部门表*/
CREATE TABLE dept(
	deptno		INT 	PRIMARY KEY,
	dname		VARCHAR(50),
	loc 		VARCHAR(50)
);

/*创建雇员表*/
CREATE TABLE emp(
	empno		INT 	PRIMARY KEY,
	ename		VARCHAR(50),
	job		VARCHAR(50),
	mgr		INT,
	hiredate	DATE,
	sal		DECIMAL(7,2),
	COMM 		DECIMAL(7,2),
	deptno		INT,
	CONSTRAINT fk_emp FOREIGN KEY(mgr) REFERENCES emp(empno)
);

/*创建工资等级表*/
CREATE TABLE salgrade(
	grade		INT 	PRIMARY KEY,
	losal		INT,
	hisal		INT
);

/*创建学生表*/
CREATE TABLE stu(
	sid		INT 	PRIMARY KEY,
	sname		VARCHAR(50),
	age		INT,
	gander		VARCHAR(10),
	province	VARCHAR(50),
	tuition		INT
);







/*插入dept表数据*/
INSERT INTO dept VALUES (10, '教研部', '北京');
INSERT INTO dept VALUES (20, '学工部', '上海');
INSERT INTO dept VALUES (30, '销售部', '广州');
INSERT INTO dept VALUES (40, '财务部', '武汉');

/*插入emp表数据*/
INSERT INTO emp VALUES (1009, '曾阿牛', '董事长', NULL, '2001-11-17', 50000, NULL, 10);
INSERT INTO emp VALUES (1004, '刘备', '经理', 1009, '2001-04-02', 29750, NULL, 20);
INSERT INTO emp VALUES (1006, '关羽', '经理', 1009, '2001-05-01', 28500, NULL, 30);
INSERT INTO emp VALUES (1007, '张飞', '经理', 1009, '2001-09-01', 24500, NULL, 10);
INSERT INTO emp VALUES (1008, '诸葛亮', '分析师', 1004, '2007-04-19', 30000, NULL, 20);
INSERT INTO emp VALUES (1013, '庞统', '分析师', 1004, '2001-12-03', 30000, NULL, 20);
INSERT INTO emp VALUES (1002, '黛绮丝', '销售员', 1006, '2001-02-20', 16000, 3000, 30);
INSERT INTO emp VALUES (1003, '殷天正', '销售员', 1006, '2001-02-22', 12500, 5000, 30);
INSERT INTO emp VALUES (1005, '谢逊', '销售员', 1006, '2001-09-28', 12500, 14000, 30);
INSERT INTO emp VALUES (1010, '韦一笑', '销售员', 1006, '2001-09-08', 15000, 0, 30);
INSERT INTO emp VALUES (1012, '程普', '文员', 1006, '2001-12-03', 9500, NULL, 30);
INSERT INTO emp VALUES (1014, '黄盖', '文员', 1007, '2002-01-23', 13000, NULL, 10);
INSERT INTO emp VALUES (1011, '周泰', '文员', 1008, '2007-05-23', 11000, NULL, 20);


INSERT INTO emp VALUES (1001, '甘宁', '文员', 1013, '2000-12-17', 8000, NULL, 20);


/*插入salgrade表数据*/
INSERT INTO salgrade VALUES (1, 7000, 12000);
INSERT INTO salgrade VALUES (2, 12010, 14000);
INSERT INTO salgrade VALUES (3, 14010, 20000);
INSERT INTO salgrade VALUES (4, 20010, 30000);
INSERT INTO salgrade VALUES (5, 30010, 99990);

/*插入stu表数据*/
INSERT INTO `stu` VALUES ('1', '王永', '23', '男', '北京', '1500');
INSERT INTO `stu` VALUES ('2', '张雷', '25', '男', '辽宁', '2500');
INSERT INTO `stu` VALUES ('3', '李强', '22', '男', '北京', '3500');
INSERT INTO `stu` VALUES ('4', '宋永合', '25', '男', '北京', '1500');
INSERT INTO `stu` VALUES ('5', '叙美丽', '23', '女', '北京', '1000');
INSERT INTO `stu` VALUES ('6', '陈宁', '22', '女', '山东', '2500');
INSERT INTO `stu` VALUES ('7', '王丽', '21', '女', '北京', '1600');
INSERT INTO `stu` VALUES ('8', '李永', '23', '男', '北京', '3500');
INSERT INTO `stu` VALUES ('9', '张玲', '23', '女', '广州', '2500');
INSERT INTO `stu` VALUES ('10', '啊历', '18', '男', '山西', '3500');
INSERT INTO `stu` VALUES ('11', '王刚', '23', '男', '湖北', '4500');
INSERT INTO `stu` VALUES ('12', '陈永', '24', '男', '北京', '1500');
INSERT INTO `stu` VALUES ('13', '李雷', '24', '男', '辽宁', '2500');
INSERT INTO `stu` VALUES ('14', '李沿', '22', '男', '北京', '3500');
INSERT INTO `stu` VALUES ('15', '王小明', '25', '男', '北京', '1500');
INSERT INTO `stu` VALUES ('16', '王小丽', '23', '女', '北京', '1000');
INSERT INTO `stu` VALUES ('17', '唐宁', '22', '女', '山东', '2500');
INSERT INTO `stu` VALUES ('18', '唐丽', '21', '女', '北京', '1600');
INSERT INTO `stu` VALUES ('19', '啊永', '23', '男', '北京', '3500');
INSERT INTO `stu` VALUES ('20', '唐玲', '23', '女', '广州', '2500');
INSERT INTO `stu` VALUES ('21', '叙刚', '18', '男', '山西', '3500');
INSERT INTO `stu` VALUES ('22', '王累', '23', '男', '湖北', '4500');
INSERT INTO `stu` VALUES ('23', '赵安', '23', '男', '北京', '1500');
INSERT INTO `stu` VALUES ('24', '关雷', '25', '男', '辽宁', '2500');
INSERT INTO `stu` VALUES ('25', '李字', '22', '男', '北京', '3500');
INSERT INTO `stu` VALUES ('26', '叙安国', '25', '男', '北京', '1500');
INSERT INTO `stu` VALUES ('27', '陈浩难', '23', '女', '北京', '1000');
INSERT INTO `stu` VALUES ('28', '陈明', '22', '女', '山东', '2500');
INSERT INTO `stu` VALUES ('29', '孙丽', '21', '女', '北京', '1600');
INSERT INTO `stu` VALUES ('30', '李治国', '23', '男', '北京', '3500');
INSERT INTO `stu` VALUES ('31', '张娜', '23', '女', '广州', '2500');
INSERT INTO `stu` VALUES ('32', '安强', '18', '男', '山西', '3500');
INSERT INTO `stu` VALUES ('33', '王欢', '23', '男', '湖北', '4500');
INSERT INTO `stu` VALUES ('34', '周天乐', '23', '男', '北京', '1500');
INSERT INTO `stu` VALUES ('35', '关雷', '25', '男', '辽宁', '2500');
INSERT INTO `stu` VALUES ('36', '吴强', '22', '男', '北京', '3500');
INSERT INTO `stu` VALUES ('37', '吴合国', '25', '男', '北京', '1500');
INSERT INTO `stu` VALUES ('38', '正小和', '23', '女', '北京', '1000');
INSERT INTO `stu` VALUES ('39', '吴丽', '22', '女', '山东', '2500');
INSERT INTO `stu` VALUES ('40', '冯含', '21', '女', '北京', '1600');
INSERT INTO `stu` VALUES ('41', '陈冬', '23', '男', '北京', '3500');
INSERT INTO `stu` VALUES ('42', '关玲', '23', '女', '广州', '2500');
INSERT INTO `stu` VALUES ('43', '包利', '18', '男', '山西', '3500');
INSERT INTO `stu` VALUES ('44', '威刚', '23', '男', '湖北', '4500');
INSERT INTO `stu` VALUES ('45', '李永', '23', '男', '北京', '1500');
INSERT INTO `stu` VALUES ('46', '张关雷', '25', '男', '辽宁', '2500');
INSERT INTO `stu` VALUES ('47', '送小强', '22', '男', '北京', '3500');
INSERT INTO `stu` VALUES ('48', '关动林', '25', '男', '北京', '1500');
INSERT INTO `stu` VALUES ('49', '苏小哑', '23', '女', '北京', '1000');
INSERT INTO `stu` VALUES ('50', '赵宁', '22', '女', '山东', '2500');
INSERT INTO `stu` VALUES ('51', '陈丽', '21', '女', '北京', '1600');
INSERT INTO `stu` VALUES ('52', '钱小刚', '23', '男', '北京', '3500');
INSERT INTO `stu` VALUES ('53', '艾林', '23', '女', '广州', '2500');
INSERT INTO `stu` VALUES ('54', '郭林', '18', '男', '山西', '3500');
INSERT INTO `stu` VALUES ('55', '周制强', '23', '男', '湖北', '4500');

使用主外键关系做为条件来去除无用信息

mysql> SELECT emp.ename,emp.sal,emp.comm,dept.dname  FROM emp,dept
    -> where emp.deptno=dept.deptno;
+-----------+----------+----------+-----------+
| ename     | sal      | comm     | dname     |
+-----------+----------+----------+-----------+
| 张飞      | 24500.00 |     NULL | 教研部    |
| 曾阿牛    | 50000.00 |     NULL | 教研部    |
| 黄盖      | 13000.00 |     NULL | 教研部    |
| 甘宁      |  8000.00 |     NULL | 学工部    |
| 刘备      | 29750.00 |     NULL | 学工部    |
| 诸葛亮    | 30000.00 |     NULL | 学工部    |
| 周泰      | 11000.00 |     NULL | 学工部    |
| 庞统      | 30000.00 |     NULL | 学工部    |
| 黛绮丝    | 16000.00 |  3000.00 | 销售部    |
| 殷天正    | 12500.00 |  5000.00 | 销售部    |
| 谢逊      | 12500.00 | 14000.00 | 销售部    |
| 关羽      | 28500.00 |     NULL | 销售部    |
| 韦一笑    | 15000.00 |     0.00 | 销售部    |
| 程普      |  9500.00 |     NULL | 销售部    |
+-----------+----------+----------+-----------+

还可以为表指定别名,然后在引用列时使用别名即可。

SELECT e.ename,e.sal,e.comm,d.dname 
FROM emp AS e,dept AS d
WHERE e.deptno=d.deptno;

4.2.1 内连接

上面的连接语句就是内连接,但它不是SQL标准中的查询方式,可以理解为方言!SQL标准的内连接为:

SELECT * 
FROM emp e 
INNER JOIN dept d 
ON e.deptno=d.deptno;

内连接的特点:查询结果必须满足条件,不满足on后面的条件结果不会出现。

我们向emp表中插入一条记录 :

INSERT INTO emp VALUES(1015,'张三','保洁员',1009,'1999-12-31',8000,20000,50);

其中deptno为50,而在dept表中只有10、20、30、40部门,那么上面的查询结果中就不会出现“张三”这条记录,因为它不能满足e.deptno=d.deptno这个条件。

4.2.2 外连接

外连接的特点:查询出的结果存在不满足条件的可能。

左连接:

mysql> select e.ename,e.sal,e.comm,d.dname from emp e left outer join dept d on e.deptno=d.deptno;
+-----------+----------+----------+-----------+
| ename     | sal      | comm     | dname     |
+-----------+----------+----------+-----------+
| 张飞      | 24500.00 |     NULL | 教研部    |
| 曾阿牛    | 50000.00 |     NULL | 教研部    |
| 黄盖      | 13000.00 |     NULL | 教研部    |
| 甘宁      |  8000.00 |     NULL | 学工部    |
| 刘备      | 29750.00 |     NULL | 学工部    |
| 诸葛亮    | 30000.00 |     NULL | 学工部    |
| 周泰      | 11000.00 |     NULL | 学工部    |
| 庞统      | 30000.00 |     NULL | 学工部    |
| 黛绮丝    | 16000.00 |  3000.00 | 销售部    |
| 殷天正    | 12500.00 |  5000.00 | 销售部    |
| 谢逊      | 12500.00 | 14000.00 | 销售部    |
| 关羽      | 28500.00 |     NULL | 销售部    |
| 韦一笑    | 15000.00 |     0.00 | 销售部    |
| 程普      |  9500.00 |     NULL | 销售部    |
| 张三      |  8000.00 | 20000.00 | NULL      |
+-----------+----------+----------+-----------+
15 rows in set (0.00 sec)


左连接是先查询出左表(即以左表为主),然后查询右表,右表中满足条件的显示出来,不满足条件的显示NULL。

这么说你可能不太明白,我们还是用上面的例子来说明。其中emp表中“张三”这条记录中,部门编号为50,而dept表中不存在部门编号为50的记录,所以“张三”这条记录,不能满足e.deptno=d.deptno这条件。但在左连接中,因为emp表是左表,所以左表中的记录都会查询出来,即“张三”这条记录也会查出,但相应的右表部分显示NULL。

右连接

mysql> select e.ename,e.sal,e.comm,d.dname from emp e right outer join dept d on e.deptno=d.deptno;
+-----------+----------+----------+-----------+
| ename     | sal      | comm     | dname     |
+-----------+----------+----------+-----------+
| 甘宁      |  8000.00 |     NULL | 学工部    |
| 黛绮丝    | 16000.00 |  3000.00 | 销售部    |
| 殷天正    | 12500.00 |  5000.00 | 销售部    |
| 刘备      | 29750.00 |     NULL | 学工部    |
| 谢逊      | 12500.00 | 14000.00 | 销售部    |
| 关羽      | 28500.00 |     NULL | 销售部    |
| 张飞      | 24500.00 |     NULL | 教研部    |
| 诸葛亮    | 30000.00 |     NULL | 学工部    |
| 曾阿牛    | 50000.00 |     NULL | 教研部    |
| 韦一笑    | 15000.00 |     0.00 | 销售部    |
| 周泰      | 11000.00 |     NULL | 学工部    |
| 程普      |  9500.00 |     NULL | 销售部    |
| 庞统      | 30000.00 |     NULL | 学工部    |
| 黄盖      | 13000.00 |     NULL | 教研部    |
| NULL      |     NULL |     NULL | 财务部    |
+-----------+----------+----------+-----------+
15 rows in set (0.00 sec)

4.2.3 连接心得

连接不限与两张表,连接查询也可以是三张、四张,甚至N张表的连接查询。通常连接查询不可能需要整个笛卡尔积,而只是需要其中一部分,那么这时就需要使用条件来去除不需要的记录。这个条件大多数情况下都是使用主外键关系去除。

两张表的连接查询一定有一个主外键关系,三张表的连接查询就一定有两个主外键关系,所以在大家不是很熟悉连接查询时,首先要学会去除无用笛卡尔积,那么就是用主外键关系作为条件来处理。如果两张表的查询,那么至少有一个主外键条件,三张表连接至少有两个主外键条件。

4.2.4 自然连接

大家也都知道,连接查询会产生无用笛卡尔积,我们通常使用主外键关系等式来去除它。而自然连接无需你去给出主外键等式,它会自动找到这一等式:两张连接的表中名称和类型完成一致的列作为条件,例如emp和dept表都存在deptno列,并且类型一致,所以会被自然连接找到!

当然自然连接还有其他的查找条件的方式,但其他方式都可能存在问题!

SELECT * FROM emp NATURAL JOIN dept;
SELECT * FROM emp NATURAL LEFT JOIN dept;
SELECT * FROM emp NATURAL RIGHT JOIN dept;

4.3 子查询

4.3.1 子查询简介

子查询就是嵌套查询,即SELECT中包含SELECT,如果一条语句中存在两个,或两个以上SELECT,那么就是子查询语句了。

子查询出现的位置:

  • where后,作为条件的一部分;
  • from后,作为被查询的一条表;

当子查询出现在where后作为条件时,还可以使用如下关键字:

  • any
  • all

子查询结果集的形式:

  • 单行单列(用于条件)
  • 单行多列(用于条件)
  • 多行单列(用于条件)
  • 多行多列(用于表)

4.3.2 子查询练习

  1. 工资高于甘宁的员工。

    分析:

    查询条件:工资>甘宁工资,其中甘宁工资需要一条子查询。

    第一步:查询甘宁的工资

    SELECT sal FROM emp WHERE ename='甘宁';
    

    第二步:查询高于甘宁工资的员工

    SELECT * FROM emp WHERE sal > (${第一步})
    

    第三步:合并语句

    SELECT * FROM emp WHERE sal > (SELECT sal FROM emp WHERE ename='甘宁')
    #子查询作为条件
    #子查询形式为单行单列
    
  2. 工资高于30部门所有人的员工信息

    分析:

    查询条件:工资高于30部门所有人工资,其中30部门所有人工资是子查询。高于所有需要使用all关键字。

    第一步:查询30部门所有人工资 。

    SELECT sal FROM emp WHERE deptno=30;
    

    第二步:查询高于30部门所有人工资的员工信息

    SELECT * FROM emp WHERE sal > ALL (${第一步})
    

    第三步:合并语句

    SELECT * FROM emp WHERE sal > ALL (SELECT sal FROM emp WHERE deptno=30);
    #子查询作为条件
    #子查询形式为多行单列(当子查询结果集形式为多行单列时可以使用ALL或ANY关键字)
    
  3. 查询工作和工资与殷天正完全相同的员工信息。

    分析:

    查询条件:工作和工资与殷天正完全相同,这是子查询

    第一步:查询出殷天正的工作和工资

    SELECT job,sal FROM emp WHERE ename='殷天正'
    

    第二步:查询出与殷天正工作和工资相同的人

    SELECT * FROM emp WHERE (job,sal) IN (${第一步})
    

    第三步:合并语句

    SELECT * FROM emp WHERE (job,sal) IN (SELECT job,sal FROM emp WHERE ename='殷天正');
    #子查询作为条件
    #子查询形式为单行多列
    
  4. 查询员工编号为1006的员工名称、员工工资、部门名称、部门地址。

    分析:

    查询列:员工名称、员工工资、部门名称、部门地址

    查询表:emp和dept,分析得出,不需要外连接(外连接的特性:某一行(或某些行)记录上会出现一半有值,一半为NULL值)

    条件:员工编号为1006

    第一步:去除多表,只查一张表,这里去除部门表,只查员工表

    SELECT ename, sal FROM emp e WHERE empno=1006;
    

    第二步:让第一步与dept做内连接查询,添加主外键条件去除无用笛卡尔积

    SELECT e.ename, e.sal, d.dname, d.loc 
    FROM emp e, dept d 
    WHERE e.deptno=d.deptno AND empno=1006
    

    第二步中的dept表表示所有行所有列的一张完整的表,这里可以把dept替换成所有行,但只有dname和loc列的表,这需要子查询。

    第三步:查询dept表中dname和loc两列,因为deptno会被作为条件,用来去除无用笛卡尔积,所以需要查询它。

    SELECT dname,loc,deptno FROM dept;
    

    第四步:替换第二步中的dept :

    SELECT e.ename, e.sal, d.dname, d.loc 
    FROM emp e, (SELECT dname,loc,deptno FROM dept) d 
    WHERE e.deptno=d.deptno AND e.empno=1006
    #	子查询作为表
    #	子查询形式为多行多列
    

5 查询练习

5.1 DQL练习

5.1.1 练习题

1. 查询出部门编号为30的所有员工
2. 所有销售员的姓名、编号和部门编号。
3. 找出奖金高于工资的员工。
4. 找出奖金高于工资60%的员工。
5. 找出部门编号为10中所有经理,和部门编号为20中所有销售员的详细资料。
6. 找出部门编号为10中所有经理,部门编号为20中所有销售员,还有即不是经理又不是销售员但其工资大或等于20000的所有员工详细资料。
7. 有奖金的工种。
8. 无奖金或奖金低于1000的员工。
9. 查询名字由三个字组成的员工。
10.查询2000年入职的员工。
11. 查询所有员工详细信息,用编号升序排序
12. 查询所有员工详细信息,用工资降序排序,如果工资相同使用入职日期升序排序
13. 查询每个部门的平均工资
14. 查询每个部门的雇员数量。 
15. 查询每种工作的最高工资、最低工资、人数
16. 显示非销售人员工作名称以及从事同一工作雇员的月工资的总和,并且要满足从事同一工作的雇员的月工资合计大于50000,输出结果按月工资的合计升序排列

5.1.2 练习题答案

/*1. 查询出部门编号为30的所有员工*/
/*
分析:
1). 列:没有说明要查询的列,所以查询所有列
2). 表:只一张表,emp
3). 条件:部门编号为30,即deptno=30
*/
SELECT * FROM emp WHERE deptno=30;

/**********************************************/

/*2. 所有销售员的姓名、编号和部门编号。*/
/*
分析:
列:姓名ename、编号empno、部门编号deptno
表:emp
条件:所有销售员,即job='销售员'
*/
SELECT ename,empno,deptno FROM emp WHERE job='销售员'

/**********************************************/

/*3. 找出奖金高于工资的员工。*/
/*
分析:
列:所有列
表:emp
条件:奖金>工资,即comm>sal
*/
SELECT * FROM emp WHERE comm>sal;

/**********************************************/

/*4. 找出奖金高于工资60%的员工。*/
/*
分析:
列:所有列
表:emp
条件:奖金>工资*0.6,即comm>sal*0.6
*/
SELECT * FROM emp WHERE comm>sal*0.6;

/**********************************************/

/*5. 找出部门编号为10中所有经理,和部门编号为20中所有销售员的详细资料。*/
/*
分析:
列:所有列
表:emp
条件:部门编号=10并且job为经理,和部门编号=20并且job为销售员
*/
SELECT * FROM emp WHERE (deptno=10 AND job='经理') OR (deptno=20 AND job='销售员');

/**********************************************/

/*6. 找出部门编号为10中所有经理,部门编号为20中所有销售员,还有即不是经理又不是销售员但其工资大或等于20000的所有员工详细资料。*/
/*
分析:
列:所有列
表:emp
条件:deptno=10 and job='经理', depnto=20 and job='销售员', job not in ('销售员','经理') and sal>=20000
*/

SELECT * FROM emp 
WHERE 
  (deptno=10 AND job='经理') 
  OR (deptno=20 AND job='销售员') 
  OR job NOT IN ('经理','销售员') AND sal>=20000;

/**********************************************/

/*7. 有奖金的工种。*/
/*
列:工作(不能重复出现)
表:emp
条件:comm is not null
*/
SELECT DISTINCT job FROM emp WHERE comm IS NOT NULL;

/**********************************************/

/*8. 无奖金或奖金低于1000的员工。*/
/*
分析:
列:所有列
表:emp
条件:comm is null 或者 comm < 1000
*/

SELECT * FROM emp WHERE comm IS NULL OR comm < 1000;

/**********************************************/

/*9. 查询名字由三个字组成的员工。*/
/*
分析:
列:所有
表:emp
条件:ename like '___'
*/
SELECT * FROM emp WHERE ename LIKE '___'

/**********************************************/

/*10.查询2000年入职的员工。*/
/*
分析:
列:所有
表:emp
条件:hiredate like '2000%'
*/
SELECT * FROM emp WHERE hiredate LIKE '2000%';

/**********************************************/

/*11. 查询所有员工详细信息,用编号升序排序*/
/*
分析;
列:所有
表:emp
条件:无
排序:empno asc
*/
SELECT * FROM emp ORDER BY empno ASC;

/**********************************************/

/*12. 查询所有员工详细信息,用工资降序排序,如果工资相同使用入职日期升序排序*/
/*
分析:
列:所有
表:emp
条件:无
排序:sal desc, hiredate asc
*/
SELECT * FROM emp ORDER BY sal DESC, hiredate ASC

/**********************************************/

/*13. 查询每个部门的平均工资*/
/*
分析:
列:部门编号、平均工资(平均工资就是分组信息)
表:emp
条件:无
分组:每个部门,即使用部门分组,平均工资,使用avg()函数
*/
SELECT deptno, AVG(sal) FROM emp GROUP BY deptno;

/**********************************************/

/*14. 求出每个部门的雇员数量。*/
/*
分析:
列:部门编号、人员数量(人员数量即记录数,这是分组信息)
表:emp
条件:无
分组:每个部门是分组信息,人员数量,使用count()函数
*/
SELECT deptno, COUNT(1) FROM emp GROUP BY deptno;

/**********************************************/

/*
15. 查询每种工作的最高工资、最低工资、人数
列:部门、最高工资、最低工资、人数(其中最高工资、最低工资、人数,都是分组信息)
表:emp
条件:无
分组:每种工资是分组信息,最高工资使用max(sal),最低工资使用min(sal),人数使用count(*)
*/
SELECT job, MAX(sal), MIN(sal), COUNT(1) FROM emp GROUP BY job;

/**********************************************/

/*16. 显示非销售人员工作名称以及从事同一工作雇员的月工资的总和,并且要满足从事同一工作的雇员的月工资合计大于50000,输出结果按月工资的合计升序排列*/
/*
列:工作名称、工资和(分组信息)
表:emp
条件:无
分组:从事同一工作的工资和,即使用job分组
分组条件:工资合计>50000,这是分组条件,而不是where条件
排序:工资合计排序,即sum(sal) asc
*/
select job,sum(sal) from emp where job!='销售员' group by job having sum(sal)>50000 order by sum(sal) asc; 

5.2 子查询练习

5.2.1 练习题

1. 查出至少有一个员工的部门。显示部门编号、部门名称、部门位置、部门人数。
2. 列出薪金比关羽高的所有员工。
3. 列出所有员工的姓名及其直接上级的姓名。
4. 列出受雇日期早于直接上级的所有员工的编号、姓名、部门名称。
5. 列出部门名称和这些部门的员工信息,同时列出那些没有员工的部门。
6. 列出所有文员的姓名及其部门名称,部门的人数。
7. 列出最低薪金大于15000的各种工作及从事此工作的员工人数。
8. 列出在销售部工作的员工的姓名,假定不知道销售部的部门编号。
9. 列出薪金高于公司平均薪金的所有员工信息,所在部门名称,上级领导,工资等级。
10.列出与庞统从事相同工作的所有员工及部门名称。
11.列出薪金高于在部门30工作的所有员工的薪金的员工姓名和薪金、部门名称。
12.列出每个部门的员工数量、平均工资。

5.2.2 练习题答案

/*1. 查出至少有一个员工的部门。显示部门编号、部门名称、部门位置、部门人数。*/
/*
列:部门编号、部门名称、部门位置、部门人数(分组)
列:dept、emp(部门人数没有员工表不行)
条件:没有
分组条件:人数>1

部门编号、部门名称、部门位置在dept表中都有,只有部门人数需要使用emp表,使用deptno来分组得到。
我们让dept和(emp的分组查询),这两张表进行连接查询
*/
SELECT
z.*,d.dname,d.loc
FROM dept d, (SELECT deptno, COUNT(*) cnt FROM emp GROUP BY deptno) z
WHERE z.deptno=d.deptno;

/**************************************************/

/*2. 列出薪金比关羽高的所有员工。*/
/*
列:所有
表:emp
条件:sal>关羽的sal,其中关羽的sal需要子查询
*/
SELECT *
FROM emp e
WHERE e.sal > (SELECT sal FROM emp WHERE ename='关羽')

/**************************************************/

/*3. 列出所有员工的姓名及其直接上级的姓名。*/
/*
列:员工名、领导名
表:emp、emp
条件:领导.empno=员工.mgr

emp表中存在自身关联,即empno和mgr的关系。
我们需要让emp和emp表连接查询。因为要求是查询所有员工的姓名,所以不能用内连接,因为曾阿牛是BOSS,没有上级,内连接是查询不到它的。
*/
SELECT e.ename, IFNULL(m.ename, 'BOSS') AS lead
FROM emp e LEFT JOIN emp m
ON e.mgr=m.empno;

/**************************************************/

/*4. 列出受雇日期早于直接上级的所有员工的编号、姓名、部门名称。*/
/*
列:编号、姓名、部门名称
表:emp、dept
条件:hiredate < 领导.hiredate

emp表需要查。部门名称在dept表中,所以也需要查。领导的hiredate需要查,这说明需要两个emp和一个dept连接查询
即三个表连接查询
*/
SELECT e.empno, e.ename, d.dname
FROM emp e LEFT JOIN emp m 
ON e.mgr=m.empno 
LEFT JOIN dept d ON e.deptno=d.deptno
WHERE e.hiredate<m.hiredate;

/**************************************************/

/*5. 列出部门名称和这些部门的员工信息,同时列出那些没有员工的部门。*/
/*
列:员工表所有列、部门名称
表:emp, dept
要求列出没有员工的部门,这说明需要以部门表为主表使用外连接
*/
SELECT e.*, d.dname
FROM emp e RIGHT JOIN dept d
ON e.deptno=d.deptno;

/**************************************************/

/*6. 列出所有文员的姓名及其部门名称,部门的人数。*/
/*
列:姓名、部门名称、部门人数
表:emp emp dept
条件:job=文员
分组:emp以deptno得到部门人数
连接:emp连接emp分组,再连接dept
*/
SELECT e.ename, d.dname, z.cnt
FROM emp e, (SELECT deptno, COUNT(*) cnt FROM emp GROUP BY deptno) z, dept d
WHERE e.deptno=d.deptno AND z.deptno=d.deptno;

/**************************************************/

/*7. 列出最低薪金大于15000的各种工作及从事此工作的员工人数。*/
/*
列:工作,该工作人数
表:emp
分组:使用job分组
分组条件:min(sal)>15000
*/
SELECT job, COUNT(*)
FROM emp e
GROUP BY job
HAVING MIN(sal) > 15000;

/**************************************************/

/*8. 列出在销售部工作的员工的姓名,假定不知道销售部的部门编号。*/
/*
列:姓名
表:emp, dept
条件:所在部门名称为销售部,这需要通过部门名称查询为部门编号,作为条件
*/
SELECT e.ename
FROM emp e
WHERE e.deptno = (SELECT deptno FROM dept WHERE dname='销售部');

/**************************************************/

/*9. 列出薪金高于公司平均薪金的所有员工信息,所在部门名称,上级领导,工资等级。*/
/*
列:员工所有信息(员工表),部门名称(部门表),上级领导(员工表),工资等级(等级表)
表:emp, dept, emp, salgrade
条件:sal>平均工资,子查询
所有员工,说明需要左外
*/
SELECT e.*, d.dname, m.ename, s.grade
FROM emp e 
  NATURAL LEFT JOIN dept d
  LEFT JOIN emp m ON m.empno=e.mgr
  LEFT JOIN salgrade s ON e.sal BETWEEN s.losal AND s.hisal
WHERE e.sal > (SELECT AVG(sal) FROM emp);

/**************************************************/

/*10.列出与庞统从事相同工作的所有员工及部门名称。*/
/*
列:员工表所有列,部门表名称
表:emp, dept
条件:job=庞统的工作,需要子查询,与部门表连接得到部门名称
*/
SELECT e.*, d.dname
FROM emp e, dept d
WHERE e.deptno=d.deptno AND e.job=(SELECT job FROM emp WHERE ename='庞统');

/**************************************************/

/*11.列出薪金高于在部门30工作的所有员工的薪金的员工姓名和薪金、部门名称。*/
/*
列:姓名、薪金、部门名称(需要连接查询)
表:emp, dept
条件:sal > all(30部门薪金),需要子查询
*/

SELECT e.ename, e.sal, d.dname
FROM emp e, dept d
WHERE e.deptno=d.deptno AND sal > ALL(SELECT sal FROM emp WHERE deptno=30)

/**************************************************/

/*12.列出在每个部门工作的员工数量、平均工资。*/
/*
列:部门名称, 部门员工数,部门平均工资
表:emp, dept
分组:deptno
*/
SELECT d.dname, e.cnt, e.avgsal
FROM (SELECT deptno, COUNT(*) cnt, AVG(sal) avgsal FROM emp GROUP BY deptno) e, dept d
WHERE e.deptno=d.deptno;