学习MySQL看这一篇就够了


整体大纲

image-20201104222910225

1. 为什么要学习数据库

数据库的好处

  1. 能够持久化到本地
  2. 可以实现结构化查询,方便管理

2. 数据库的相关概念

  1. DB:数据库,存储数据的容器(文件)
  2. DBMS:数据库管理系统,又称为数据库软件(产品),用于创建或管理DB中的数据
  3. SQL:结构化查询语言,用于和DBMS通信的语言,不是某个数据库软件特有的,而是几乎所有的主流数据库软件通用的语言

3. 数据库存储数据的特点

  1. 将数据放到表中,表再放到库中
  2. 一个数据库中可以有多张表,每张表都有一个名字,用来标识自己。表名具有唯一性
  3. 表具有一些特性,这些特性定义了数据在表中如何存储,类似java中 “类” 的设计
  4. 表由列组成,我们也称为字段。所有表都是由一个或多个列组成的,每一列类似 java 中的 “属性”
  5. 表中的数据是按行存储的,每一行类似于java中的 “对象”

常见的数据库管理系统

MySQL、Oracle、SQL Server、db2

MySQL的背景

前身属于瑞典的一加公司,MySQL AB

08年被sun公司收购

09年sun公司被Oracle公司收购,所以它现在是属于Oracle公司的

MySQL的优点

1、开源、免费、成本低

2、性能高、移植性也好

3、体积小,便于安装

4. 初始MySQL

4.1 MySQL服务的登录和退出

-- 方式一:
-- 打开终端,输入 mysql -h 主机名 -P 端口号 -u用户名 -p密码
xiexu@Macintosh ~ % mysql -h localhost -P 3306 -uroot -p283619
...
mysql>

-- 方式二: (如果是本机电脑一般用这种)
-- mysql -u用户名 -p密码
xiexu@Macintosh ~ % mysql -uroot -p283619
...
mysql>

-- 退出:
exit或ctrl+C

4.2 MySQL的常见命令

-- 1.查看当前所有的数据库 
show databases;
mysql> show databases;
+--------------------+
| Database           |
+--------------------+
| information_schema |
| mysql              |
| performance_schema |
| test               |
+--------------------+
4 rows in set (0.00 sec)

-- 2.打开指定的库
use 库名;
mysql> use test;
Database changed

-- 3.查看当前库的所有表
show tables;
mysql> show tables;
+----------------+
| Tables_in_test |
+----------------+
| books          |
+----------------+
1 row in set (0.00 sec)

-- 4.查看其它库的所有表
show tables from 库名;
mysql> show tables from mysql;
+---------------------------+
| Tables_in_mysql           |
+---------------------------+
| columns_priv              |
| component                 |
| db                        |
|...                        |
+---------------------------+
33 rows in set (0.00 sec)

-- 5.查看当前所在的库
select database();
mysql> select database();
+------------+
| database() |
+------------+
| test       |
+------------+
1 row in set (0.00 sec)

-- 6.创建表
create table 表名(
	列名 列类型,
	列名 列类型,
	...
);
mysql> create table stuinfo(
    -> id int,
    -> name varchar(20));
Query OK, 0 rows affected (0.00 sec)

-- 7.查看表的结构
desc 表名;
mysql> desc stuinfo;
+-------+-------------+------+-----+---------+-------+
| Field | Type        | Null | Key | Default | Extra |
+-------+-------------+------+-----+---------+-------+
| id    | int         | YES  |     | NULL    |       |
| name  | varchar(20) | YES  |     | NULL    |       |
+-------+-------------+------+-----+---------+-------+
2 rows in set (0.01 sec)

-- 8.查看服务器的版本
-- 方式一:已经登录到mysql服务端
select version();
mysql> select version();
+-----------+
| version() |
+-----------+
| 8.0.19    |
+-----------+
1 row in set (0.00 sec)
-- 方式二:没有登录到mysql服务端
mysql --version
xiexu@Macintosh ~ % mysql --version
/usr/local/mysql/bin/mysql  Ver 8.0.19 for macos10.15 on x86_64 (MySQL Community Server - GPL)
--或者输入:
mysql -V
xiexu@Macintosh ~ % mysql -V
/usr/local/mysql/bin/mysql  Ver 8.0.19 for macos10.15 on x86_64 (MySQL Community Server - GPL)

4.3 MySQL的语法规范

1.不区分大小写,但建议关键字大写,表名、列名小写
2.每条命令最好用分号结尾
3.每条命令根据需要,可以进行缩进 或换行
4.注释(注意空格)
      单行注释:#注释文字
      单行注释:-- 注释文字
      多行注释:/* 注释文字  */

4.4 SQL的语言分类

DQL(Data Query Language):数据查询语言
select
DML(Data Manipulate Language):数据操作语言
insertupdatedelete
DDL(Data Define Languge):数据定义语言
createdropalter
TCL(Transaction Control Language):事务控制语言
commitrollback

4.5 导入数据库表

为了让我们可以更加方面的操作和练习后面的内容,在这里提供了一个数据库文件,里面包含四张表

具体的操作步骤

image-20201002190817710

image-20201002190826486

表结构名称介绍

image-20201002190914342

image-20201002190925957

image-20201002190935829

image-20201002190944607

5. DQL语言的学习

5.1 基础查询

-- 进阶1:基础查询
/*
语法:
select 查询列表 from 表名;

类似于: System.out.println(要打印的东西)

特点:
	1、查询列表可以是: 表中的字段、常量值、表达式、函数
	2、查询的结果是一个虚拟的表格(临时存在的)
*/

-- 指定使用的数据库
USE myemployees;

-- 1. 查询表中的单个字段
SELECT last_name FROM employees;

-- 2.查询表中的多个字段
SELECT last_name,salary,email FROM employees;

-- 3.查询表中的所有字段
-- 方式一:
SELECT 
    `employee_id`,
    `first_name`,
    `last_name`,
    `phone_number`,
    `job_id`,
    `salary`,
    `commission_pct`,
    `manager_id`,
    `department_id`,
    `hiredate` 
FROM
    employees;
-- 注意:``表示的是里面的内容是一个字段,而不是关键字,建议每次查询字段都要加上这个
		
-- 方式二:
SELECT * FROM employees;

-- 4.查询常量值
-- 注意:字符型和日期型的常量值必须用单引号引起来,数值型不需要
SELECT 100;

-- 5.查询表达式
SELECT 100%98;

-- 6.查询函数
SELECT VERSION();

-- 7.为字段起别名
/*
1、便于理解
2、如果要查询的字段有重名的情况,使用别名可以区分开来
*/
-- 方式一:使用as
SELECT 100%98 AS 结果;
SELECT last_name AS,first_name ASFROM employees;

-- 方式二:使用空格
SELECT last_name 姓,first_name 名 FROM employees;

-- 案例:查询salary,显示结果为 out put
-- 注意:如果起的别名是关键字,建议使用双引号括起来
SELECT salary AS "out put" FROM employees;

-- 8.去重
-- 案例:查询员工表中涉及到的所有的部门编号
SELECT DISTINCT department_id FROM employees;
-- 根据a和b两个字段去重的
SELECT DISTINCT a,b FROM 表名;

-- 9.+号的作用
/*
java中的+号:
1、运算符,两个操作数都为数值型
2、连接符,只要有一个操作数为字符串

mysql中的+号:
仅仅只有一个功能:运算符

select 100+90; 两个操作数都为数值型,则做加法运算
select '123'+90;只要其中一方为字符型,试图将字符型数值转换成数值型
								如果转换成功,则继续做加法运算
select 'john'+90;	如果转换失败,则将字符型数值转换成0

select null+10; 只要其中一方为null,则结果肯定为null
*/

-- 10.concat函数
/*
1.作用:拼接字符
2.语法格式:select concat(字符1,字符2,...)
*/
-- 案例:查询员工名和姓连接成一个字段,并显示为 姓名
SELECT CONCAT('a','b','c') AS 结果;

SELECT 
	CONCAT(last_name,first_name) AS 姓名
FROM
	employees;
	
-- 11.isnull函数
/*
1.作用:判断某字段或表达式是否为null,如果是null,返回1,否则返回0
2.语法格式:select isnull(字段名) from 表名;
*/
SELECT
	ISNULL( commission_pct ),
	commission_pct 
FROM
	employees;
	
-- IFNULL函数
/*
1.作用:判断某字段或表达式是否为null,如果为null,就返回指定的值,否则返回原本的值
2.语法格式: select IFNULL(字段名,指定的值) from 表名;
*/
SELECT 
	IFNULL(commission_pct,0) AS 奖金率,
	commission_pct
FROM 
	employees;

5.1.1 基础查询练习

-- 1.显示表departments的结构,并查询其中的全部数据
DESC departments;
SELECT * FROM `departments`;

-- 2.显示出表employees中的全部job_id(不能重复)
SELECT DISTINCT job_id FROM employees;

-- 3.显示出表employees的全部列,各个列之间用逗号连接,列头显示成OUT_PUT
SELECT
	CONCAT(`first_name`,',',`last_name`,',',`job_id`,',',IFNULL(commission_pct,0)) AS out_put
FROM
	employees;

5.2 条件查询

-- 进阶2:条件查询
/*
语法:
		select 
			查询列表
		from
			表名
		where
			筛选条件;
			
分类:
		一、按条件表达式筛选
		简单条件运算符:>   <   =   !=   <>   >=   <=
		注意:<>表示的是不等于,使用不等于的话建议用这个
		
		二、按逻辑表达式筛选
		逻辑运算符:
							&&       ||       !
							and      or				not
		作用:用于连接条件表达式
		说明:
				&&和and:两个条件都为true,结果为true,反之为false
				||或or: 只要有一个条件为true,结果为true,反之为false
				!或not: 如果连接的条件本身为false,结果为true,反之为false
		
		三、模糊查询
							like
							between and
							in
							is null		
*/

-- 1.按条件表达式筛选
-- 案例1:查询员工工资>12000的员工信息
SELECT 
	*
FROM
	employees
WHERE
	salary>12000;

-- 案例2:查询部门编号不等于90号的员工名和部门编号
SELECT 
	last_name,
	department_id
FROM
	employees
WHERE
	department_id<>90;
	
-- 案例3:查询员工号为 176 的员工的姓名和部门号和年薪
SELECT
	last_name,
	department_id,
	salary * 12 * ( 1+IFNULL ( commission_pct, 0 ) ) AS 年薪 
FROM
	employees 
WHERE
	employee_id = 176;
	
-- 2.按逻辑表达式筛选
-- 案例1:查询工资在10000到20000之间的员工名、工资以及奖金
SELECT
	last_name,
	salary,
	commission_pct
FROM
	employees
WHERE
	salary>=10000 AND salary<=20000;

-- 案例2:查询部门编号不是在90到110之间,或者工资高于15000的员工信息
-- 方式一:
SELECT
	*
FROM
	employees
WHERE
	NOT(department_id>=90 AND department_id<=110) OR salary>15000;
	
-- 方式二:
SELECT
	* 
FROM
	employees 
WHERE
	department_id < 90 OR department_id > 110 
	OR salary > 15000
	
-- 3.模糊查询

-- 3.1 like
/*
		特点:
		①一般和通配符搭配使用,可以判断字符型或数值型
			通配符:
			% 任意多个字符,包含0个字符
			_ 任意单个字符
*/
-- 案例1:查询员工名中包含字符a的员工信息
SELECT
	* 
FROM
	employees 
WHERE
	last_name LIKE '%a%';

-- 案例2:查询员工名中第三个字符为n,第五个字符为l的员工名和工资
SELECT
	last_name,
	salary 
FROM
	employees 
WHERE
	last_name LIKE '__n_l%';
	
-- 案例3:查询员工名中第二个字符为'_'的员工名
-- 方式一:使用转义字符 "\_"
SELECT
	last_name,
	salary 
FROM
	employees 
WHERE
	last_name LIKE '_\_%';

-- 方式二:使用ESCAPE '任意字符',将这个字符当做转义字符使用(推荐使用)
SELECT
	last_name,
	salary 
FROM
	employees 
WHERE
	last_name LIKE '_$_%' ESCAPE '$';
	
-- 案例4:查询部门编号是1开头的,并且是3位数的员工信息
SELECT
	* 
FROM
	employees 
WHERE
	department_id LIKE '1__';
	
-- 3.2 between and
/*
①使用between and 可以提高语句的简洁度
②包含临界值
③两个临界值不要调换顺序
*/
-- 案例1:查询员工编号在100到120之间的员工信息
-- 方式一:
SELECT
	* 
FROM
	employees 
WHERE
	employee_id >= 100 
	AND employee_id <= 120;

-- 方式二:推荐使用
SELECT
	* 
FROM
	employees 
WHERE
	employee_id BETWEEN 100 AND 120;

-- 3.3 in
/*
含义:判断某字段的值是否属于in列表中的某一项
特点:
	①使用in提高语句简洁度
	②in列表的值类型必须一致或兼容(同一字段)
	③in列表中不支持通配符
*/
-- 案例:查询员工的工种编号是 IT_PROG、AD_VP、AD_PRES中的一个的员工名和工种编号
-- 方式一:
SELECT
	last_name,
	job_id
FROM
	employees
WHERE
	job_id = 'IT_PROT' OR job_id = 'AD_VP' OR JOB_ID ='AD_PRES';
	

-- 方式二:推荐使用
SELECT
	last_name,
	job_id
FROM
	employees
WHERE
	job_id IN( 'IT_PROT' ,'AD_VP','AD_PRES');
	
-- 3.4 is null
/*
	!=或<>不能用于判断null值
	is null或is not null 可以判断null值
*/
-- 案例1:查询没有奖金的员工名和奖金率
SELECT
	last_name,
	commission_pct
FROM
	employees
WHERE
	commission_pct IS NULL;

-- 案例2:查询有奖金的员工名和奖金率
SELECT
	last_name,
	commission_pct
FROM
	employees
WHERE
	commission_pct IS NOT NULL;

-- 错误写法,执行报错
SELECT
	last_name,
	commission_pct
FROM
	employees
WHERE 
	salary IS 12000;

-- 3.5 安全等于 <=>
/*
面试题:isnull和<=>的区别?
IS NULL:仅仅可以判断NULL值,可读性较高,建议使用
 <=>   :既可以判断NULL值,又可以判断普通的数值,可读性较低
*/
-- 案例1:查询没有奖金的员工名和奖金率
SELECT
	last_name,
	commission_pct 
FROM
	employees 
WHERE
	commission_pct <=> NULL;

-- 案例2:查询工资为12000的员工信息
SELECT
	last_name,
	salary 
FROM
	employees 
WHERE
	salary <=> 12000;

5.2.1 条件查询练习

-- 1.查询工资大于 12000 的员工姓名和工资
SELECT
	last_name,
	salary 
FROM
	employees 
WHERE
	salary > 12000;

-- 2.查询员工号为 176 的员工的姓名和部门号和年薪
SELECT
	last_name,
	department_id,
	salary * 12 * ( 1+IFNULL ( commission_pct, 0 ) ) AS 年薪 
FROM
	employees;

-- 3.选择工资不在 5000 到 12000 的员工的姓名和工资
SELECT
	last_name,
	salary 
FROM
	employees 
WHERE
	NOT ( salary < 5000 AND salary > 12000 );

-- 4.选择在 20 或 50 号部门工作的员工姓名和部门号
SELECT
	last_name,
	department_id 
FROM
	employees 
WHERE
	department_id = 20 
	OR department_id = 50;

-- 5.选择公司中没有管理者的员工姓名及 job_id
SELECT
	last_name,
	job_id 
FROM
	employees 
WHERE
	manager_id IS NULL;

-- 6.选择公司中有奖金的员工姓名,工资和奖金级别
SELECT
	last_name,
	salary,
	commission_pct 
FROM
	employees 
WHERE
	commission_pct IS NOT NULL;

-- 7.选择员工姓名的第三个字母是 'a' 的员工姓名
SELECT
	last_name 
FROM
	employees 
WHERE
	last_name LIKE '__a%';

-- 8.选择姓名中有字母 'a' 和 'e' 的员工姓名
SELECT
	last_name 
FROM
	employees 
WHERE
	last_name LIKE '%a%' 
	AND last_name LIKE '%e%';

-- 9.显示出表 employees 表中 first_name 以 'e'结尾的员工信息
SELECT
	* 
FROM
	employees 
WHERE
	first_name LIKE '%e';

-- 10.显示出表 employees 部门编号在 80-100 之间 的姓名、职位
SELECT
	last_name,
	job_id 
FROM
	employees 
WHERE
	department_id BETWEEN 80 
	AND 100;

-- 11.显示出表 employees 的 manager_id 是 100,101,110 的员工姓名、职位
SELECT
	last_name,
	job_id 
FROM
	employees 
WHERE
	manager_id IN ( 100, 101, 110 );

5.2.2 经典面试题

试问:select * from employees;select * from employees where `commission_pct` like '%%' and `last_name` like '%%';
结果是否一样?并说明原因

回答:如果判断的字段中有null值,结果就是不一样的;而如果判断的字段中没有null值,结果就是一样的。
			因为like '%%' 表示的是不为空,而如果`commission_pct`或者`last_name`有一个为空,结果就不一样了。

5.3 排序查询

-- 进阶3:排序查询
/*
1.语法: [ ]里面的内容表示可以省略的
			select 查询列表
			from 表名
			[where  筛选条件]
			order by 排序列表 [asc | desc];
2.特点:
			1、asc代表的是升序,desc代表的是降序(如果不写,默认就是降序)
			2、order by子句中可以支持单个字段、多个字段、表达式、函数、别名
			3、order by子句一般是放在查询语句的最后面的,limit子句除外
*/
-- 案例1:查询员工信息,要求工资从高到低排序
SELECT
	* 
FROM
	employees 
ORDER BY
	salary DESC;

-- 案例2:查询员工信息,要求工资从低到高排序
SELECT
	* 
FROM
	employees 
ORDER BY
	salary;
	
-- 案例3:查询部门编号>=90的员工信息,按入职时间先后进行排序(升序)[添加筛选条件]
SELECT
	* 
FROM
	employees 
WHERE
	department_id >= 90 
ORDER BY
	hiredate ASC;
	
-- 案例4:按年薪的高低显示员工的信息和年薪[按表达式排序]	
SELECT
	*,
	salary * 12 * ( 1+IFNULL ( commission_pct, 0 ) ) 年薪 
FROM
	employees 
ORDER BY
	salary * 12 * ( 1+IFNULL ( commission_pct, 0 ) ) DESC;

-- 案例5:按年薪的高低显示员工的信息和年薪[按别名排序]	
SELECT
	*,
	salary * 12 * ( 1+IFNULL ( commission_pct, 0 ) ) 年薪 
FROM
	employees 
ORDER BY
	年薪 DESC;
	
-- 案例6:按姓名的长度显示员工的姓名和工资[按函数排序]
SELECT
	LENGTH( last_name ) 字节长度,
	last_name,
	salary 
FROM
	employees 
ORDER BY
	LENGTH( last_name ) DESC;
	
-- 案例7:查询员工信息,要求先按工资升序排序,再按员工编号降序排序[按多个字段排序]
SELECT
	* 
FROM
	employees 
ORDER BY
	salary ASC,
	employee_id DESC;

5.3.1 排序查询练习

-- 1. 查询员工的姓名和部门号和年薪,按年薪降序 按姓名升序
SELECT
	last_name,
	department_id,
	salary * 12 * ( 1+IFNULL ( commission_pct, 0 ) ) 年薪 
FROM
	employees 
ORDER BY
	年薪 DESC,
	last_name ASC;

-- 2. 选择工资不在 8000 到 17000 的员工的姓名和工资,按工资降序
SELECT
	last_name,
	salary 
FROM
	employees 
WHERE
	salary NOT BETWEEN 8000 
	AND 17000 
ORDER BY
	salary DESC;

-- 3. 查询邮箱中包含 e 的员工信息,并先按邮箱的字节数降序,再按部门号升序
SELECT
	*,
	LENGTH( email ) 邮箱字节数 
FROM
	employees 
WHERE
	email LIKE '%e%' 
ORDER BY
	邮箱字节数 DESC,
	department_id ASC;

5.4 常见函数

常见函数:
			字符函数:
			length 获取字节个数
			concat 拼接
			substr 截取子串
			instr 返回子串第一次出现的索引
			trim 去前后指定的空格和字符
			upper 转换成大写
			lower 转换成小写
			lpad 左填充
			rpad 右填充
			replace 替换
			
			数学函数:
			round 四舍五入
			ceil 向上取整
			floor 向下取整
			truncate 截断
			mod 取余
			rand 获取随机数,返回0-1之间的小数 [0,1)
			
			日期函数:
			now 当前日期+时间
			curdate 返回当前日期
			curtime 返回当前时间
			yearmonth 月
			monthname 以英文形式返回月
			dayhour 小时
			minute 分钟
			second 秒
      datediff 返回两个日期相差的天数
			str_to_date 将字符转换成日期
			date_format 将日期转换成字符     
			
			其他函数:
			version 当前数据库服务器的版本号
			database 当前打开的数据库
			user 当前用户
      password('字符') 返回该字符的密码形式(自动加密) 注意:8版本不支持此种加密方式
			md5('字符') 使用md5加密形式对该字符进行加密
                             
			流程控制函数:
			if(条件表达式,表达式1,表达式2) 如果条件表达式成立,返回表达式1,否则返回表达式2
			case(两种情况)
            case情况一:
                      case 变量或表达式或字段
                      when 常量1 then1
                      when 常量2 then2 
                        ...
                      else 值n
                      end
             case情况二:    
                      case 
                      when 条件1 then1
                      when 条件2 then2 
                        ...
                      else 值n
                      end
                             
        分组函数:
        max 最大值
        min 最小值
        sum 总和
        avg 平均值
        count 计算个数                                          
-- 进阶4:常见函数
/*
概念:类似于Java中的方法,将一组逻辑语句封装在方法体中,对外暴露方法名
好处:1、隐藏了实现细节  2、提高代码的重用性
调用:select 函数名(实参列表) [from 表名]
特点:
		①叫什么(函数名)
		②干什么(函数的功能)
分类:
		1、单行函数
		如:concat、length、ifnull等
		2、分组函数
		功能:做统计使用的,又称为统计函数、聚合函数、组函数
*/

-- 1.字符函数
-- 1.1 length 获取参数值的字节个数
SELECT
	LENGTH( 'john' ); #4
	
SELECT
	LENGTH( '张三丰haha' ); #13

-- 获取当前mysql的字符集
SHOW VARIABLES LIKE '%char%';

-- 1.2 concat 拼接字符串
SELECT
	CONCAT( last_name, '_', first_name ) 姓名 
FROM
	employees;

-- 1.3 upper:将指定字符变成大写的,lower:将指定字符变成小写的
SELECT
	UPPER( 'john' ); #JOHN

SELECT
	LOWER( 'joHn' ); #john
	
-- 案例:将姓变大写,名变小写,然后拼接
SELECT
	CONCAT( UPPER( last_name ), LOWER( first_name ) ) 姓名 
FROM
	employees;
	
-- 1.4 substr、substring  注意:索引都是从1开始的

-- 方法一:截取从指定索引处后面的所有字符
SELECT
	SUBSTR( '李莫愁爱上了陆展元', 7 ) out_put; # 陆展元
	
-- 方法二:截取从指定索引处指定字符长度的字符
SELECT
	SUBSTR( '李莫愁爱上了陆展元', 1, 3 ) out_put; # 李莫愁

-- 案例:姓名中首字符大写,其他字符小写然后用_拼接,显示出来
SELECT
	CONCAT(
		UPPER( SUBSTR( last_name, 1, 1 ) ),
		'_',
		LOWER( SUBSTR( last_name, 2 ) ) 
	) 姓名 
FROM
	employees;

-- 1.5 instr 返回指定字符在另一个指定字符中第一次出现的索引,如果找不到返回0
SELECT
	INSTR( '杨不殷六侠悔爱上了殷六侠', '殷八侠' ) AS out_put; # 0
	
SELECT
	INSTR( '杨不殷六侠悔爱上了殷六侠', '殷六侠' ) AS out_put; # 3
	
-- 1.6 trim 

-- 方法一:去掉前后的空格
SELECT
	LENGTH( TRIM( '    张翠山    ' ) ) AS out_put;  # TRIM( '    张翠山    ' )返回'张翠山'
	
-- 方法二:过滤掉前后指定的字符
SELECT
	TRIM( 'aa' FROM 'aaaaaaaaa张aaaaaaaaaaaa翠山aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' ) AS out_put;
# a张aaaaaaaaaaaa翠山a
	
-- 1.7 lpad 用指定的字符实现左填充指定长度
SELECT
	LPAD( '殷素素', 10, 'A' ) AS out_put; # AAAAAAA殷素素

SELECT
	LPAD( '殷素素', 2, 'A' ) AS out_put; # 殷素

-- 1.8 rpad 用指定的字符实现右填充指定长度
SELECT
	RPAD( '殷素素', 12, 'ab' ) AS out_put; # 殷素素ababababa,说明:因为殷素素已经占了3个字符,所以后面只能有9个字符
	
-- 1.9 replace 替换
SELECT REPLACE
	( '周芷若周芷若周芷若周芷若张无忌爱上了周芷若', '周芷若', '赵敏' ) AS out_put;
# 赵敏赵敏赵敏赵敏张无忌爱上了赵敏
-- 2.数学函数

-- 2.1 round 

-- 方法一:四舍五入
SELECT
	ROUND( - 1.55 );# -2

-- 方法二:小数点后保留几位小数
SELECT
	ROUND( 1.567, 2 );# 1.57

-- 2.2 ceil 向上取整,返回>=该参数的最小整数
SELECT CEIL(1.002); # 2
SELECT CEIL(-1.02); # -1

-- 2.3 floor 向下取整,返回<=该参数的最大整数
SELECT FLOOR(9.8); # 9
SELECT FLOOR(-9.99); # -10

-- 2.4 truncate 截断,小数点后保留几位小数
SELECT TRUNCATE(1.69999,1); # 1.6

-- 2.5 mod 取余,左边的数是正数,结果就是正的,反之则为负数
/*
mod(a,b) 相当于 a - a/b * b
mod(-10,-3) : -10 - (-10)/(-3) * (-3) = -1
mod(10,-3) : 10 - 10/(-3) * (-3) = 1
*/
SELECT MOD(10,3); # 1
SELECT MOD(10,-3); # 1
SELECT MOD(-10,3)# -1

-- 3.日期函数

-- 3.1 now 返回当前系统日期+时间
SELECT NOW(); # 2020-10-04 18:12:56

-- 3.2 curdate 返回当前系统日期,不包含时间
SELECT CURDATE(); # 2020-10-04

-- 3.3 curtime 返回当前时间,不包含日期
SELECT CURTIME(); # 18:16:18

-- 3.4 可以获取指定的部分,年、月、日、小时、分钟、秒
SELECT YEAR(NOW()); # 2020
SELECT YEAR('1999-04-28'); # 1999

SELECT YEAR(hiredate)FROM employees;

SELECT MONTH(NOW()); # 10
SELECT MONTHNAME(NOW()); # October

-- 3.5 str_to_date 将字符通过指定的格式转换成日期
SELECT STR_TO_DATE('1999-4-28','%Y-%c-%d') 日期; # 1999-04-28

-- 案例:查询入职日期为1992-4-3的员工信息
SELECT * FROM employees WHERE hiredate = '1992-4-3';
-- 在开发中有可能从前端返回的日期字符串是没有顺序的,这个时候就要用到我们的格式转换,才能查询出数据
SELECT * FROM employees WHERE hiredate = STR_TO_DATE('4-3 1992','%c-%d %Y');

-- 3.6 date_format 将日期转换成字符
SELECT DATE_FORMAT(NOW(),'%y年%m月%d日') AS 日期; # 20年10月04日

-- 案例:查询有奖金的员工名和入职日期,要求入职日期的格式是:(xx月/xx日 xx年)
SELECT
	last_name,
	DATE_FORMAT( hiredate, '%m月/%d日 %y年' ) 入职日期 
FROM
	employees 
WHERE
	salary IS NOT NULL;

-- 4.其他函数

-- 4.1 version 查看当前系统的mysql版本号
SELECT VERSION(); # 8.0.19

-- 4.2 database 查看当前所在数据库
SELECT DATABASE(); # myemployees

-- 4.3 user 查看当前用户
SELECT USER(); # root@localhost

-- 5.流程控制函数

-- 5.1 if函数 实现if-else的效果
SELECT IF(10>5,'大','小'); # 大

SELECT
	last_name,
	commission_pct,
IF
	( commission_pct IS NULL, '没奖金,呵呵', '有奖金,嘻嘻' ) 备注 
FROM
	employees;

-- 5.2 case函数

-- 5.2.1 case函数的使用一:实现switch-case的效果
/*
Java中
switch(表达式){
case 常量1:
			执行语句1;
			break;
case 常量2:
			执行语句2;
			break;
...
defaule:
			执行语句n;
			break;
			
mysql中
case 要判断的字段或表达式
when 常量1 then 要显示的值1或语句1
when 常量2 then 要显示的值2或语句2
...
else 要显示的值n或语句n
end
*/

/*
案例:查询员工的工资,要求
部门号=30,显示的工资为1.1倍
部门号=40,显示的工资为1.2倍
部门号=50,显示的工资为1.3倍
其他部门,显示的工资为原工资
*/
SELECT
	salary 原始工资,
	department_id,
CASE
		department_id 
		WHEN 30 THEN
		salary * 1.1 
		WHEN 40 THEN
		salary * 1.2 
		WHEN 50 THEN
		salary * 1.3 
		ELSE salary 
	END AS 新工资 
FROM
	employees;

-- 5.2.2 case函数的使用二:类似于 多重if的效果
/*
java中:
if(条件1){
	语句1;
}else if(条件2){
	语句2;
}
...
else{
	语句n;
}

mysql中:
case
when 条件1 then 要显示的值1或语句1
when 条件2 then 要显示的值2或语句2
...
else 要显示的值n或语句n
end
*/

/*
案例:查询员工的工资的情况
如果工资>20000,显示A级别
如果工资>15000,显示B级别
如果工资>10000,显示C级别
否则,显示D级别
*/
SELECT salary,
CASE 
	WHEN salary>20000 THEN 'A'
	WHEN salary>15000 THEN 'B'
	WHEN salary>10000 THEN 'C'
ELSE 'D'
END AS 工资级别
FROM employees;

5.4.1 常见函数练习

-- 1. 显示系统时间(注:日期+时间)
SELECT NOW();

-- 2. 查询员工号,姓名,工资,以及工资提高百分之 20%后的结果(new salary)
SELECT
	employee_id,
	last_name,
	salary,
	salary * ( 1+0.2 ) AS "new salary" 
FROM
	employees;

-- 3. 将员工的姓名按首字母排序,并写出姓名的长度(length)
SELECT
	last_name,
	LENGTH( last_name ) 姓名长度,
	SUBSTR( last_name, 1, 1 ) 首字母 
FROM
	employees 
ORDER BY
	首字母 ASC;

-- 4. 做一个查询,产生下面的结果
/*
<last_name> earns <salary> monthly but wants <salary*3>
									Dream Salary
			King earns 24000 monthly but wants 72000
*/
SELECT
	CONCAT(
		last_name,
		' earns ',
		salary,
		' monthly but wants ',
		salary * 3 
	) AS "Dream Salary" 
FROM
	employees 
WHERE
	salary = 24000;
	
-- 5. 使用 case-when,按照下面的条件: 
/*
										job 						grade 
									AD_PRES             A 
									ST_MAN              B
									IT_PROG             C
									SA_REP              D
									ST_CLERK            E
									
									产生下面的结果
last_name					job_id					Grade
  K_ing						AD_PRES	          A
	 
*/
SELECT last_name,job_id,
CASE job_id
	WHEN 'AD_PRES' THEN 'A'
	WHEN 'ST_MAN' THEN 'B'
	WHEN 'IT_PROG' THEN 'C'
	WHEN 'SA_REP' THEN 'D'
	WHEN 'ST_CLERK' THEN 'E'
END AS 'Grade' FROM employees WHERE job_id = 'AD_PRES';

5.5 分组函数

-- 进阶4:常见函数2

-- 分组函数
/*
功能:用作统计使用,又称为聚合函数或统计函数或组函数

分类:
sum 求和 、 avg 平均值 、 max 最大值 、 min 最小值 、 count 计算个数

特点:
1、sum、avg一般用于处理数值型
   max、min、count可以处理任何类型
2、以上分组函数都忽略null值

3、可以和distinct搭配实现去重的运算

4、count函数的单独介绍
count(字段):统计该字段非空值的个数
一般使用count(*)用作统计行数
另外 count(1)也是用来统计行数

5、和分组函数一同查询的字段要求是group by后的字段
*/

-- 1.简单的使用
SELECT SUM(salary) 工资总和 FROM employees;
SELECT AVG(salary) 工资平均值 FROM employees;
SELECT MIN(salary) 最小工资 FROM employees;
SELECT MAX(salary) 最大工资 FROM employees;
SELECT COUNT(salary) 总共多少份工资 FROM employees;

SELECT
	SUM( salary ) 工资总和,
	AVG( salary ) 工资平均值,
	MIN( salary ) 最小工资,
	MAX( salary ) 最大工资,
	COUNT( salary ) 总共多少份工资 
FROM
	employees;
	
SELECT
	SUM( salary ) 工资总和,
	ROUND( AVG( salary ), 2 ) "工资平均值(保留2位小数)",
	MIN( salary ) 最小工资,
	MAX( salary ) 最大工资,
	COUNT( salary ) 总共多少份工资 
FROM
	employees;
	
-- 2.参数支持哪些类型
SELECT SUM(last_name),AVG(last_name) FROM employees; # 不支持
SELECT SUM(hiredate) ,AVG(hiredate) FROM employees; # 没必要
SELECT MAX(last_name),MIN(last_name) FROM employees;
SELECT MAX(hiredate),MIN(hiredate) FROM employees;
SELECT COUNT(commission_pct) FROM employees; 
SELECT COUNT(last_name) FROM employees;

-- 3.是否忽略null:都忽略null值
SELECT
	SUM( commission_pct ),
	AVG( commission_pct ),
	SUM( commission_pct ) / 35,
	SUM( commission_pct ) / 107 
FROM
	employees; 
	
SELECT MAX(commission_pct) ,MIN(commission_pct) FROM employees;
SELECT COUNT(commission_pct) FROM employees;
SELECT commission_pct FROM employees;

-- 4.和distinct搭配
SELECT SUM(DISTINCT salary),SUM(salary) FROM employees;

SELECT COUNT(DISTINCT salary),COUNT(salary) FROM employees;


-- 5.count函数的详细介绍
/*
效率:
MYISAM存储引擎下  ,COUNT(*)的效率高
INNODB存储引擎下,COUNT(*)和COUNT(1)的效率差不多,比COUNT(字段)要高一些
*/
SELECT COUNT(salary) FROM employees;

SELECT COUNT(*) FROM employees; # 经常用来统计行数 107

SELECT COUNT(1) FROM employees; # 107

-- 6.和分组函数一同查询的字段有限制
SELECT AVG(salary),employee_id FROM employees; #报错

5.5.1 分组函数练习

-- 1. 查询公司员工工资的最大值,最小值,平均值(保留两位小数),总和,并按 job_id 升序
SELECT
	MAX( salary ) 最大值,
	MIN( salary ) 最小值,
	ROUND( AVG( salary ), 2 ) 平均值,
	SUM( salary ) 总和 
FROM
	employees 
ORDER BY
	job_id ASC;

-- 2. 查询员工最高工资和最低工资的差距(DIFFERENCE)
SELECT
	( MAX( salary ) - MIN( salary ) ) DIFFERENCE 
FROM
	employees;

-- 3. 查询各个管理者手下员工的最低工资,其中最低工资不能低于 6000,没有管理者的员工不计算在内
SELECT
	MIN( salary ),
	manager_id 
FROM
	employees 
WHERE
	manager_id IS NOT NULL 
GROUP BY
	manager_id 
HAVING
	MIN( salary ) >= 6000;

-- 4. 查询所有部门的编号,员工数量和工资平均值,并按平均工资降序
SELECT
	department_id,
	count( * ),
	AVG( Salary ) 
FROM
	employees 
GROUP BY
	department_id 
ORDER BY
	AVG( salary ) DESC;
	
-- 5. 选择具有各个 job_id 的员工人数
SELECT
	job_id,
	COUNT( * ) 
FROM
	employees 
GROUP BY
	job_id;

-- 6. 查询员工表中的最大入职时间和最小入职时间的相差天数(DIFFERENCE)
SELECT
	DATEDIFF( MAX( hiredate ), MIN( hiredate ) ) DIFFERENCE 
FROM
	employees;

-- 7. 查询某人从出生到现在的总天数
SELECT
	DATEDIFF( NOW( ), '1999-04-28' ) 总天数;

-- 8.查询部门编号为90的员工个数
SELECT
	COUNT( * ) 
FROM
	employees 个数
WHERE
	department_id = 90;

5.6 分组查询

-- 进阶5:分组查询
/*
		语法:  [ ]里面的内容表示可以省略的
		select 查询列表         		①
		from 表           					②
		[where 筛选条件]   					③
		group by 分组的字段 				  ④
		[having 分组后的筛选]        ⑤
		[order by 排序的字段];				⑥
		
		执行顺序:② -> ③ -> ④ - > ⑤ -> ① -> ⑥
注意:
		和分组函数一同查询的字段必须是group by后出现的字段
		
特点:
		1.分组查询中的筛选条件分为两类
										数据源 											位置									关键字
		分组前筛选		原始表(真实存在的表)					group by子句的前面					where
		分组后筛选     分组后的结果集							group by子句的后面					having
		
		① 分组函数做筛选条件,肯定是放在having子句中
		② 能用分组前筛选的,就优先考虑使用分组前筛选(性能问题)
		2.group by子句支持单个字段分组,多个字段分组(多个字段之间用逗号隔开并且没有顺序要求),表达式或函数(用得较少)
		3.也可以添加排序(排序放在整个分组查询的最后)
*/
-- 1.简单的分组查询

-- 案例1:查询每个工种的最高工资
SELECT
	MAX( salary ),
	job_id 
FROM
	employees 
GROUP BY
	job_id;

-- 案例2:查询每个位置上的部门个数
SELECT
	COUNT( * ),
	location_id 
FROM
	departments 
GROUP BY
	location_id;

-- 2.添加分组前的筛选条件

-- 案例1:查询邮箱中包含a字符的,每个部门的平均工资
SELECT
	AVG( salary ),
	department_id 
FROM
	employees 
WHERE
	email LIKE '%a%' 
GROUP BY
	department_id;

-- 案例2:查询有奖金的每个领导手下员工的最高工资
SELECT
	MAX( salary ),
	manager_id 
FROM
	employees 
WHERE
	commission_pct IS NOT NULL 
GROUP BY
	manager_id;

-- 3.添加分组后的筛选条件

-- 案例1:查询哪个部门的员工个数>2

-- ① 查询每个部门的员工个数
SELECT
	COUNT( * ),
	department_id 
FROM
	employees 
GROUP BY
	department_id;
	
-- ② 根据①的结果进行筛选,查询哪个部门的员工个数>2
SELECT
	COUNT( * ),
	department_id 
FROM
	employees 
GROUP BY
	department_id 
HAVING
	COUNT( * ) > 2;

-- 案例2:查询每个工种有奖金的员工的最高工资>12000的工种编号和最高工资

-- ① 查询每个工种有奖金的员工的最高工资
SELECT
	job_id,
	MAX( salary ) 
FROM
	employees 
WHERE
	commission_pct IS NOT NULL 
GROUP BY
	job_id;
	
-- ② 根据①的结果继续筛选,最高工资>12000
SELECT
	job_id,
	MAX( salary ) 
FROM
	employees 
WHERE
	commission_pct IS NOT NULL 
GROUP BY
	job_id 
HAVING
	MAX( salary ) > 12000;

-- 案例3:查询领导编号>102的每个领导手下的最低工资>5000的领导编号和最低工资
SELECT
	manager_id,
	MIN( salary ) 
FROM
	employees 
WHERE
	manager_id > 102 
GROUP BY
	manager_id 
HAVING
	MIN( salary ) > 5000;
	
-- 4.按表达式或函数分组

-- 案例1:按员工姓名的长度分组,查询每一组的员工个数,筛选员工个数>5的有哪些

-- ① 查询每个长度的员工个数
SELECT
	COUNT( * ),
	LENGTH( last_name ) 姓名长度 
FROM
	employees 
GROUP BY
	LENGTH( last_name );

-- ② 添加筛选条件
SELECT
	COUNT( * ) c,
	LENGTH( last_name ) 姓名长度 
FROM
	employees 
GROUP BY
	姓名长度 
HAVING
	c > 5;

-- 5.按多个字段进行分组

-- 案例1:查询每个部门每个工种的员工的平均工资
SELECT
	AVG( salary ),
	department_id,
	job_id 
FROM
	employees 
GROUP BY
	department_id,
	job_id;

-- 6.添加排序

-- 案例1:查询每个部门每个工种的员工的平均工资,并且按平均工资的高低显示,要求部门id不能为null,平均工资要大于10000
SELECT
	AVG( salary ),
	department_id,
	job_id 
FROM
	employees 
WHERE
	department_id IS NOT NULL 
GROUP BY
	department_id,
	job_id 
HAVING
	AVG( salary ) > 10000 
ORDER BY
	AVG( salary ) DESC;

5.6.1 分组查询练习

-- 1. 查询各 job_id 的员工工资的最大值,最小值,平均值,总和,并按 job_id 升序
SELECT
	job_id,
	MAX( salary ) 最大值,
	MIN( salary ) 最小值,
	ROUND( AVG( salary ), 2 ) 平均值,
	SUM( salary ) 总和 
FROM
	employees 
GROUP BY
	job_id 
ORDER BY
	job_id ASC;

-- 2. 查询员工最高工资和最低工资的差距(DIFFERENCE)
SELECT
	( MAX( salary ) - MIN( salary ) ) DIFFERENCE 
FROM
	employees;
	
-- 3. 查询各个管理者手下员工的最低工资,其中最低工资不能低于 6000,没有管理者的员工不计算在内
SELECT
	manager_id,
	MIN( salary ) 最低工资 
FROM
	employees 
WHERE
	manager_id IS NOT NULL 
GROUP BY
	manager_id 
HAVING
	MIN( salary ) > 6000;

-- 4. 查询所有部门的编号,员工数量和工资平均值,并按平均工资降序
SELECT
	department_id,
	COUNT( * ) 员工数量,
	AVG( salary ) 平均工资 
FROM
	employees 
GROUP BY
	department_id 
ORDER BY
	AVG( salary ) DESC;

-- 5. 查询各个 job_id 的员工人数
SELECT
	job_id,
	COUNT( * ) 员工人数 
FROM
	employees 
GROUP BY
	job_id;

5.7 连接查询

-- 进阶6:连接查询
/*
含义:又称多表查询,当查询的字段来自于多个表时,就会用到连接查询

笛卡尔乘积现象:表1 有m行,表2有n行,结果=m*n行
发生原因:没有有效的连接条件
如何避免:添加有效的连接条件

分类:
		按年代分类:
						sql92标准: 仅仅支持内连接
						sql99标准(推荐使用): 支持内连接+外连接(左外和右外)+交叉连接
						
		按功能分类:
						内连接:
									等值连接
									非等值连接
									自连接
									
						外连接:
									左外连接
									右外连接
									全外连接(mysql不支持)
									
						交叉连接
*/
USE girls;

SELECT * FROM beauty;
SELECT * FROM boys;

SELECT
	`name`,
	boyName 
FROM
	beauty,
	boys 
WHERE
	beauty.boyfriend_id = boys.id;

-- sql92标准

-- 1. 等值连接
/*
① 多表等值连接的结果为多表的交集部分
② n表连接,至少需要n-1个连接条件
③ 多表的顺序没有要求
④ 一般需要为表起别名
⑤ 可以搭配前面介绍的所有子句使用,比如排序、分组、筛选
*/

-- 案例1:查询女神名和对应的男神名
SELECT
	`name`,
	boyName 
FROM
	beauty,
	boys 
WHERE
	beauty.boyfriend_id = boys.id;

-- 案例2:查询员工名和对应的部门名
SELECT
	last_name,
	department_name 
FROM
	employees,
	departments 
WHERE
	departments.`department_id` = employees.`department_id`;

-- 1.2 为表起别名
/*
①提高语句的简洁度
②区分多个重名的字段

注意:如果为表起了别名,则查询的字段就不能使用原来的表名去限定
*/

-- 案例1:查询员工名、工种号、工种名
SELECT
	last_name,
	e.job_id,
	job_title 
FROM
	employees e,
	jobs j 
WHERE
	e.job_id = j.job_id;

-- 1.3 两个表的顺序可以调换

-- 案例1:查询员工名、工种号、工种名
SELECT
	last_name,
	e.job_id,
	job_title 
FROM
	jobs j,
	employees e 
WHERE
	e.`job_id` = j.`job_id`;
	
-- 1.4 加筛选

-- 案例1:查询有奖金的员工名、部门名
SELECT
	last_name,
	department_name,
	commission_pct 
FROM
	employees e,
	departments d 
WHERE
	e.`department_id` = d.`department_id` 
	AND e.`commission_pct` IS NOT NULL;

-- 案例2:查询城市名中第二个字符为o的部门名和城市名
SELECT
	department_name,
	city 
FROM
	departments d,
	locations l 
WHERE
	d.`location_id` = l.`location_id` 
	AND city LIKE '_o%';

-- 1.5 加分组

-- 案例1:查询每个城市的部门个数
SELECT
	city,
	COUNT( * ) 个数 
FROM
	departments d,
	locations l 
WHERE
	d.location_id = l.location_id 
GROUP BY
	city;
	
-- 案例2:查询有奖金的每个部门的部门名和部门的领导编号和该部门的最低工资
SELECT
	department_name 部门名,
	d.manager_id 部门领导编号,
	MIN( salary ) 最低工资 
FROM
	employees e,
	departments d 
WHERE
	e.department_id = d.department_id 
	AND commission_pct IS NOT NULL 
GROUP BY
	department_name,
	d.manager_id;
	
-- 1.6 加排序

-- 案例1:查询每个工种的工种名和员工的个数,并且按员工个数降序
SELECT
	job_title,
	COUNT( * ) 员工个数 
FROM
	employees e,
	jobs j 
WHERE
	e.job_id = j.job_id 
GROUP BY
	job_title 
ORDER BY
	COUNT( * ) DESC;

-- 1.7 实现三表连接

-- 1.案例:查询员工名、部门名和所在的城市
SELECT
	last_name,
	department_name,
	city 
FROM
	employees e,
	departments d,
	locations l 
WHERE
	e.department_id = d.department_id 
	AND d.location_id = l.location_id;

-- 2.非等值连接

-- 案例1:查询员工的工资和工资级别
SELECT
	salary,
	grade_level 
FROM
	employees e,
	job_grades g 
WHERE
	salary BETWEEN g.lowest_sal 
	AND g.highest_sal;

-- 3.自连接

-- 案例1:查询 员工名和上级的名称
SELECT
	e.employee_id,
	e.last_name,
	m.employee_id 领导id,
	m.last_name 领导名字
FROM
	employees e,
	employees m 
WHERE
	e.manager_id = m.employee_id;

5.7.1 连接查询练习

USE myemployees;

-- 1.显示所有员工的姓名,部门号和部门名称。
SELECT
	last_name,
	e.department_id,
	department_name 
FROM
	employees e,
	departments d 
WHERE
	e.department_id = d.department_id;

-- 2.查询90号部门员工的job_id和90号部门的location_id
SELECT
	e.department_id,
	job_id,
	location_id 
FROM
	employees e,
	departments d 
WHERE
	e.department_id = d.department_id 
	AND e.department_id = 90;

-- 3.	选择所有有奖金的员工的last_name , department_name , location_id , city
SELECT
	last_name,
	department_name,
	l.location_id,
	city 
FROM
	employees e,
	locations l,
	departments d 
WHERE
	e.department_id = d.department_id 
	AND d.location_id = l.location_id 
	AND e.commission_pct IS NOT NULL;

-- 4.选择city在Toronto工作的员工的last_name , job_id , department_id , department_name
SELECT
	last_name,
	job_id,
	d.department_id,
	department_name,
	city 
FROM
	employees e,
	locations l,
	departments d 
WHERE
	e.department_id = d.department_id 
	AND d.location_id = l.location_id 
	AND l.city = 'Toronto';

-- 5.查询每个工种、每个部门的部门名、工种名和最低工资
SELECT
	d.department_name 部门名,
	j.job_title 工种名,
	MIN( salary ) 最低工资 
FROM
	departments d,
	jobs j,
	employees e 
WHERE
	d.department_id = e.department_id 
	AND e.job_id = j.job_id 
GROUP BY
	d.department_name,
	j.job_title;

-- 6.查询每个国家下的部门个数大于2的国家编号
SELECT
	country_id,
	COUNT( * ) 部门个数 
FROM
	departments d,
	locations l 
WHERE
	d.location_id = l.location_id 
GROUP BY
	country_id 
HAVING
	COUNT( * ) > 2;

-- 7、选择指定员工的姓名,员工号,以及他的管理者的姓名和员工号,结果类似于下面的格式
/*
				employees	   Emp#    manager	    Mgr#
				kochhar		   101	    king	    100
*/

SELECT
	e.last_name employees,
	e.employee_id "Emp#",
	m.last_name manager,
	m.employee_id "Mgr#" 
FROM
	employees e,
	employees m 
WHERE
	e.manager_id = m.employee_id 
	AND e.last_name = 'kochhar';

5.8 sql99连接查询

-- sql99语法
/*
语法:
		select 查询列表
		from 表1 别名 [连接类型]
		join 表2 别名 
		on 连接条件
		[where 筛选条件]
		[group by 分组]
		[having 筛选条件]
		[order by 排序列表]

						内连接:(重点) inner
									等值连接
									非等值连接
									自连接
									
						外连接:
									左外连接 (重点) left [outer]
									右外连接 (重点) right [outer]
									全外连接 full [outer]
									
						交叉连接 cross
*/

-- 1. 内连接
/*
语法:
		select 查询列表
		from 表1 别名
		inner join 表2 别名
		on 连接条件;
		
分类:
		等值连接
		非等值连接
		自连接
		
特点:
①添加排序、分组、筛选
②inner可以省略
③筛选条件放在where后面,连接条件放在on后面,提高分离性,便于阅读
④inner join连接和sql92语法中的等值连接效果是一样的,都是查询多表的交集		
*/

-- 1.1 等值连接

-- 案例1:查询员工名、部门名
SELECT
	last_name,
	department_name 
FROM
	employees e
	INNER JOIN departments d ON e.department_id = d.department_id;

-- 案例2:查询名字中包含e的员工名和工种名(添加筛选)
SELECT
	last_name,
	job_title 
FROM
	employees e
	INNER JOIN jobs j ON e.job_id = j.job_id 
WHERE
	e.last_name LIKE '%e%';

-- 案例3:查询部门个数>3的城市名和部门个数,(添加分组+筛选)
SELECT
	city,
	COUNT( * ) 部门个数 
FROM
	departments d
	INNER JOIN locations l ON d.location_id = l.location_id 
GROUP BY
	city 
HAVING
	COUNT( * ) > 3;

-- 案例4:查询哪个部门的员工个数>3的部门名和员工个数,并按个数降序(添加排序)
SELECT
	department_name,
	COUNT( * ) 员工个数
FROM
	departments d
	INNER JOIN employees e ON d.department_id = e.department_id 
GROUP BY
	department_name 
HAVING
	COUNT( * ) > 3 
ORDER BY
	COUNT( * ) DESC;

-- 案例5:查询员工名、部门名、工种名,并按部门名降序(添加三表连接)
SELECT
	last_name,
	department_name,
	job_title 
FROM
	employees e
	INNER JOIN departments d ON e.department_id = d.department_id
	INNER JOIN jobs j ON e.job_id = j.job_id 
ORDER BY
	department_name DESC;

-- 1.2 非等值连接

-- 案例1:查询员工的工资级别
SELECT
	salary,
	grade_level 
FROM
	employees e
	JOIN job_grades j ON e.salary BETWEEN j.lowest_sal 
	AND j.highest_sal;

-- 案例2:查询工资级别>20的个数,并且按工资级别降序
SELECT
	grade_level,
	count( * ) 个数 
FROM
	employees e
	JOIN job_grades j ON e.salary BETWEEN j.lowest_sal 
	AND j.highest_sal 
GROUP BY
	grade_level 
HAVING
	COUNT( * ) > 20 
ORDER BY
	grade_level DESC;

-- 1.3 自连接

-- 案例1:查询员工的名字、上级的名字
SELECT
	e.last_name,
	m.last_name 
FROM
	employees e
	JOIN employees m ON e.manager_id = m.employee_id;

-- 案例2:查询姓名中包含字符k的员工的名字、上级的名字
SELECT
	e.last_name,
	m.last_name 
FROM
	employees e
	JOIN employees m ON e.manager_id = m.employee_id 
WHERE
	e.last_name LIKE '%k%';

-- 2.外连接
/*
应用场景:用于查询一个表中有,另一个表没有的记录
 
 特点:
 1、外连接的查询结果为主表中的所有记录
	如果从表中有和它匹配的,则显示匹配的值
	如果从表中没有和它匹配的,则显示null
	外连接查询结果=内连接结果+主表中有,而从表中没有的记录
 2、左外连接,left join左边的是主表
    右外连接,right join右边的是主表
 3、左外和右外交换两个表的顺序,可以实现同样的效果 
 4、全外连接=内连接的结果+表1中有但表2没有的+表2中有但表1没有的
*/

-- 案例1:查询男朋友 不在男神表的的女神名

-- 左外连接
SELECT
	b.NAME 
FROM
	beauty b
	LEFT JOIN boys bo ON b.boyfriend_id = bo.id 
WHERE
	bo.id IS NULL;

-- 案例2:查询哪个部门没有员工

-- 左外连接
SELECT
	d.* 
FROM
	departments d
	LEFT JOIN employees e ON d.department_id = e.department_id 
WHERE
	e.employee_id IS NULL;
	
-- 右外连接
SELECT
	d.* 
FROM
	employees e
	RIGHT JOIN departments d ON d.department_id = e.department_id 
WHERE
	e.employee_id IS NULL;

-- 全外连接,mysql不支持
USE girls;
SELECT
	b.*,
	bo.* 
FROM
	beauty b
	FULL JOIN boys bo ON b.boyfriend_id = bo.id;

-- 交叉连接,相当于笛卡尔乘积
SELECT b.*,bo.*
FROM beauty b
CROSS JOIN boys bo;

-- sql92和sql99的区别
/*
 功能:sql99支持的较多
 可读性:sql99实现连接条件和筛选条件的分离,可读性较高
*/

5.8.1 sql99连接查询练习

-- 1.查询编号>3 的女神的男朋友信息,如果有则列出详细,如果没有,用 null 填充
USE girls;
SELECT
	b.id,
	b.NAME,
	bo.* 
FROM
	beauty b
	LEFT JOIN boys bo ON b.boyfriend_id = bo.id 
WHERE
	b.id > 3;
	
-- 2.查询哪个城市没有部门
USE myemployees;
SELECT
	l.city 
FROM
	locations l
	LEFT JOIN departments d ON l.location_id = d.location_id 
WHERE
	d.department_id IS NULL;

-- 3.查询部门名为 SAL 或 IT 的员工信息
SELECT
	d.department_name,
	e.* 
FROM
	departments d
	LEFT JOIN employees e ON d.department_id = e.department_id 
WHERE
	d.department_name IN ( 'SAL', 'IT' );

5.9 子查询

-- 进阶7:子查询
/*
含义:
出现在其他语句中的select语句,称为子查询或内查询
外部的查询语句,称为主查询或外查询

分类:
按子查询出现的位置:
	select后面:
		仅仅支持标量子查询(结果集只有一行一列)
	
	from后面:
		支持表子查询
	where或having后面:★ (重点)
		标量子查询(结果集只有一行一列) √ (重点)
		列子查询  (结果集只有一列多行) √ (重点)
		行子查询(结果集有一行多列)
		
	exists后面(相关子查询)
		表子查询
按结果集的行列数不同:
	标量子查询(结果集只有一行一列)
	列子查询(结果集只有一列多行),也可以叫做多行子查询
	行子查询(结果集有一行多列)
	表子查询(结果集一般为多行多列)
*/

-- 1.where或having后面
/*
1、标量子查询(单行子查询):结果集为一行一列
2、列子查询(多行子查询) :结果集为多行一列
3、行子查询(多列多行) :结果集为一行多列

特点:
①子查询放在小括号内
②子查询一般放在条件的右侧
③标量子查询,一般搭配着单行操作符使用
		>   <   >=   <=   =   <>

列子查询,一般搭配着多行操作符使用
		in、any/some、all

④子查询的执行优先于主查询执行,主查询的条件用到了子查询的结果
*/

-- 1.1 标量子查询(单行子查询或一行一列)

-- 案例1:谁的工资比 Abel 高?
-- ①查询Abel的工资
SELECT
	salary 
FROM
	employees 
WHERE
	last_name = 'Abel';

-- ②查询员工的信息,满足 salary>①结果
SELECT
	* 
FROM
	employees 
WHERE
	salary > ( SELECT salary FROM employees WHERE last_name = 'Abel' );

-- 案例2:返回job_id与141号员工相同,salary比143号员工多的员工 姓名,job_id 和工资
-- ①查询141号员工的job_id
SELECT
	job_id 
FROM
	employees 
WHERE
	employee_id = 141
	
-- ②查询143号员工的salary
SELECT
	salary 
FROM
	employees 
WHERE
	employee_id = 143;
	
-- ③查询员工的姓名,job_id 和工资,要求job_id=①并且salary>②
SELECT
	last_name,
	job_id,
	salary 
FROM
	employees 
WHERE
	job_id = ( SELECT job_id FROM employees WHERE employee_id = 141 ) 
	AND salary > ( SELECT salary FROM employees WHERE employee_id = 143 );

-- 案例3:返回公司工资最少的员工的last_name,job_id和salary
-- ①查询公司的 最低工资
SELECT
	MIN( salary ) 
FROM
	employees;

-- ②查询last_name,job_id和salary,要求salary=①
SELECT
	last_name,
	job_id,
	salary 
FROM
	employees 
WHERE
	salary = ( SELECT MIN( salary ) FROM employees );

-- 案例4:查询最低工资大于50号部门最低工资的部门id和其最低工资
-- ①查询50号部门的最低工资
SELECT
	MIN( salary ) 
FROM
	employees 
WHERE
	department_id = 50;

-- ②查询每个部门的最低工资
SELECT
	department_id,
	MIN( salary ) 
FROM
	employees 
GROUP BY
	department_id;

-- ③ 在②基础上筛选,满足min(salary)>①
SELECT
	MIN( salary ),
	department_id 
FROM
	employees 
GROUP BY
	department_id 
HAVING
	MIN( salary ) > (
	SELECT
		MIN( salary ) 
	FROM
		employees 
	WHERE
		department_id = 50 
	);
	
-- 非法使用标量子查询
SELECT
	MIN( salary ),
	department_id 
FROM
	employees 
GROUP BY
	department_id 
HAVING
	MIN( salary ) > ( SELECT salary FROM employees WHERE department_id = 250 );

-- 1.2 列子查询(多行子查询)

-- 案例1:返回location_id是1400或1700的部门中的所有员工姓名
-- ①查询location_id是1400或1700的部门编号
SELECT DISTINCT
	department_id 
FROM
	departments 
WHERE
	location_id IN ( 1400, 1700 );

-- ②查询员工姓名,要求部门号是①列表中的某一个
SELECT
	last_name 
FROM
	employees 
WHERE
	department_id IN (
	SELECT DISTINCT
		department_id 
	FROM
		departments 
	WHERE
		location_id IN ( 1400, 1700 ) 
	);
	
-- 或
SELECT
	last_name 
FROM
	employees 
WHERE
	department_id = ANY (
	SELECT DISTINCT
		department_id 
	FROM
		departments 
	WHERE
		location_id IN ( 1400, 1700 ) 
	);
	
-- 案例2:返回其它工种中比job_id为‘IT_PROG’工种任一工资低的员工的员工号、姓名、job_id 以及salary

-- ①查询job_id为‘IT_PROG’部门任一工资
SELECT DISTINCT
	salary 
FROM
	employees 
WHERE
	job_id = 'IT_PROG';
	
-- ②查询员工号、姓名、job_id 以及salary,salary<(①)的任意一个
SELECT
	employee_id,
	last_name,
	job_id,
	salary 
FROM
	employees 
WHERE
	salary < ANY ( SELECT DISTINCT salary FROM employees WHERE job_id = 'IT_PROG' ) 
	AND job_id <> 'IT_PROG';

-- 或
/*
小于任意一个,应该是小于最大值
如果是小于所有的值,那就应该是小于最小值
*/
SELECT
	last_name,
	employee_id,
	job_id,
	salary 
FROM
	employees 
WHERE
	salary < (
	SELECT
		MAX( salary ) 
	FROM
		employees 
	WHERE
		job_id = 'IT_PROG' 
	) 
	AND job_id <> 'IT_PROG';
	
-- 案例3:返回其它部门中比job_id为‘IT_PROG’部门所有工资都低的员工   的员工号、姓名、job_id 以及salary
SELECT
	employee_id,
	last_name,
	job_id,
	salary 
FROM
	employees 
WHERE
	salary < ALL ( SELECT DISTINCT salary FROM employees WHERE job_id = 'IT_PROG' ) 
	AND job_id <> 'IT_PROG';
	
-- 或
SELECT
	last_name,
	employee_id,
	job_id,
	salary 
FROM
	employees 
WHERE
	salary < (
	SELECT
		MIN( salary ) 
	FROM
		employees 
	WHERE
		job_id = 'IT_PROG' 
	) 
	AND job_id <> 'IT_PROG';

-- 1.3 行子查询(一行多列或多行多列)

-- 案例1:查询员工编号最小并且工资最高的员工信息

-- 方法一:
SELECT
	* 
FROM
	employees 
WHERE
	( employee_id, salary ) = (
	SELECT
		MIN( employee_id ),
		MAX( salary ) 
	FROM
		employees 
	);

-- 方法二:

-- ①查询最小的员工编号
SELECT
	MIN( employee_id ) 
FROM
	employees;

-- ②查询最高工资
SELECT
	MAX( salary ) 
FROM
	employees;

-- ③查询员工信息
SELECT
	* 
FROM
	employees 
WHERE
	employee_id = ( SELECT MIN( employee_id ) FROM employees ) 
	AND salary = ( SELECT MAX( salary ) FROM employees );

-- 2.select后面
/*
仅仅支持标量子查询
*/

-- 案例1:查询每个部门的员工个数
SELECT
	d.*,
	(
	SELECT
		count( * ) 
	FROM
		employees e 
	WHERE
		e.department_id = d.department_id 
	) 员工数 
FROM
	departments d;

-- 案例2:查询员工号=102的部门名
SELECT
	(
	SELECT
		department_name 
	FROM
		departments d
		INNER JOIN employees e ON d.department_id = e.department_id 
	WHERE
		e.employee_id = 102 
	) 部门名;

-- 3.from后面
/*
将子查询结果充当一张表,要求必须起别名
*/
-- 案例1:查询每个部门的平均工资的工资等级

-- ①查询每个部门的平均工资
SELECT
	department_id,
	AVG( salary ) 
FROM
	employees 
GROUP BY
	department_id;

-- ② 连接①的结果集和job_grades表,筛选条件平均工资 between lowest_sal and highest_sal
SELECT
	j.grade_level,
	ag_dep.* 
FROM
	(
	SELECT
		department_id,
		AVG( salary ) ag 
	FROM
		employees 
	GROUP BY
		department_id 
	) ag_dep
	INNER JOIN job_grades j ON ag_dep.ag BETWEEN lowest_sal 
	AND highest_sal;

-- 4.exists后面(相关子查询)
/*
语法:
exists(完整的查询语句)
结果:
1或0
*/
SELECT EXISTS(SELECT employee_id FROM employees WHERE salary=300000);

-- 案例1:查询有员工的部门名
-- in
SELECT
	department_name 
FROM
	departments d 
WHERE
	d.department_id IN ( SELECT department_id FROM employees );

-- exists
SELECT
	department_name 
FROM
	departments d 
WHERE
	EXISTS (
	SELECT
		* 
	FROM
		employees e 
	WHERE
	d.department_id = e.department_id 
	);
	
-- 案例2:查询没有女朋友的男神信息
-- in
USE girls;
SELECT
	bo.* 
FROM
	boys bo 
WHERE
	bo.id NOT IN (
	SELECT
		b.boyfriend_id 
	FROM
		beauty b 
	WHERE
		bo.id = b.boyfriend_id 
	);

-- exists
USE girls;
SELECT
	bo.* 
FROM
	boys bo 
WHERE
	NOT EXISTS (
	SELECT
		* 
	FROM
		beauty b 
	WHERE
		b.boyfriend_id = bo.id 
	);

5.9.1 子查询练习

-- 1. 查询和 Zlotkey 相同部门的员工姓名和工资

-- ①查询Zlotkey的部门
SELECT
	department_id 
FROM
	employees 
WHERE
	last_name = 'Zlotkey';

-- ②查询部门号=①的姓名和工资(标量子查询)
SELECT
	last_name,
	salary 
FROM
	employees 
WHERE
	department_id = ( SELECT department_id FROM employees WHERE last_name = 'Zlotkey' );

-- 2. 查询工资比公司平均工资高的员工的员工号,姓名和工资。

-- ①查询平均工资
SELECT
	AVG( salary ) 
FROM
	employees;

-- ②查询工资>①的员工号,姓名和工资。
SELECT
	employee_id,
	last_name,
	salary 
FROM
	employees 
WHERE
	salary > ( SELECT AVG( salary ) FROM employees );

-- 3. 查询各部门中工资比本部门平均工资高的员工的员工号, 姓名和工资

-- ①查询各部门的平均工资
SELECT
	department_id,
	AVG( salary ) 
FROM
	employees 
GROUP BY
	department_id;

-- ②连接①的结果集和employees表,进行筛选
SELECT
	employee_id,
	last_name,
	salary,
	e.department_id 
FROM
	employees e
	INNER JOIN (
	SELECT
		department_id,
		AVG( salary ) s 
	FROM
		employees 
	GROUP BY
		department_id 
	) da ON da.department_id = e.department_id 
WHERE
	e.salary > da.s;

-- 4. 查询和姓名中包含字母 u 的员工在相同部门的员工的员工号和姓名

-- ①查询姓名中包含字母u的员工的部门(一列多行)
SELECT DISTINCT
	department_id 
FROM
	employees 
WHERE
	last_name LIKE '%u%';

-- ② 查询部门号=①中的任意一个的员工号和姓名
SELECT
	employee_id,
	last_name 
FROM
	employees 
WHERE
	department_id IN ( SELECT DISTINCT department_id FROM employees WHERE last_name LIKE '%u%' );

-- 5. 查询在部门的 location_id 为 1700 的部门工作的员工的员工号

-- ①查询location_id为1700的部门(一列多行)
SELECT
	department_id 
FROM
	departments d
	INNER JOIN locations l ON d.location_id = l.location_id 
WHERE
	d.location_id = 1700;

-- ②查询部门号=①中的任意一个的员工号(使用=ANY,等价于使用in)
SELECT
	employee_id 
FROM
	employees 
WHERE
	department_id = ANY (
	SELECT
		department_id 
	FROM
		departments d
		INNER JOIN locations l ON d.location_id = l.location_id 
	WHERE
		d.location_id = 1700 
	);

-- 6. 查询管理者是 K_ing 的员工姓名和工资

-- ①查询姓名为K_ing的员工编号(一列多行)
SELECT
	employee_id 
FROM
	employees 
WHERE
	last_name = 'K_ing';

-- ②查询哪个员工的manager_id = ①
SELECT
	last_name,
	salary 
FROM
	employees 
WHERE
	manager_id IN ( SELECT employee_id FROM employees WHERE last_name = 'K_ing');
	
-- 7. 查询工资最高的员工的姓名,要求 first_name 和 last_name 显示为一列,列名为 姓.

-- ①查询最高工资(标量子查询)
SELECT
	MAX( salary ) 
FROM
	employees;

-- ②查询工资=①的姓.名
SELECT
	CONCAT( first_name, ' ', last_name )FROM
	employees 
WHERE
	salary = ( SELECT MAX( salary ) FROM employees );
-- 1. 查询工资最低的员工信息: last_name, salary

-- ①查询最低的工资
SELECT
	MIN( salary ) 
FROM
	employees;

-- ②查询last_name,salary,要求salary=①
SELECT
	last_name,
	salary 
FROM
	employees 
WHERE
	salary = ( SELECT MIN( salary ) FROM employees );

-- 2. 查询平均工资最低的部门信息

-- 方式一:
-- ①查询各部门的平均工资
SELECT
	department_id,
	AVG( salary ) 
FROM
	employees 
GROUP BY
	department_id;
	
-- ② 查询①结果上的最低平均工资
SELECT
	MIN( s ) 
FROM
	(
	SELECT
		department_id,
		AVG( salary ) s 
	FROM
		employees 
	GROUP BY
		department_id 
	) da;

-- ③查询哪个部门的平均工资=②
SELECT
	department_id,
	AVG( salary ) 平均工资 
FROM
	employees 
GROUP BY
	department_id 
HAVING
	平均工资 = (
	SELECT
		MIN( s ) 
	FROM
		(
		SELECT
			department_id,
			AVG( salary ) s 
		FROM
			employees 
		GROUP BY
			department_id 
		) da 
	);
	
-- ④查询部门信息
SELECT
	d.* 
FROM
	departments d 
WHERE
	d.department_id = (
	SELECT
		department_id 
	FROM
		employees 
	GROUP BY
		department_id 
	HAVING
		AVG( salary ) = (
		SELECT
			MIN( da.s ) 
		FROM
			(
			SELECT
				department_id,
				AVG( salary ) s 
			FROM
				employees 
			GROUP BY
				department_id 
			) da 
		) 
	);

-- 方式二:
-- ①各部门的平均工资
SELECT
	department_id,
	AVG( salary ) 
FROM
	employees 
GROUP BY
	department_id;

-- ②求出最低平均工资的部门编号
SELECT
	department_id 
FROM
	employees 
GROUP BY
	department_id 
ORDER BY
	AVG( salary ) ASC 
	LIMIT 1;

-- ③查询部门信息
SELECT
	* 
FROM
	departments 
WHERE
	department_id = (
	SELECT
		department_id 
	FROM
		employees 
	GROUP BY
		department_id 
	ORDER BY
		AVG( salary ) ASC 
		LIMIT 1 
	);

-- 3. 查询平均工资最低的部门信息和该部门的平均工资
-- ①各部门的平均工资,再筛选工资最低的部门编号
SELECT
	department_id,
	AVG( salary ) 
FROM
	employees 
GROUP BY
	department_id 
ORDER BY
	AVG( salary ) ASC 
	LIMIT 1;

-- ② 根据①查询部门信息
SELECT
	d.*,
	ag 
FROM
	departments d
	INNER JOIN (
	SELECT
		department_id,
		AVG( salary ) ag 
	FROM
		employees 
	GROUP BY
		department_id 
	ORDER BY
		AVG( salary ) ASC 
		LIMIT 1 
	) da ON d.department_id = da.department_id

-- 4. 查询平均工资最高的 job 信息
-- ①查询最高的job的平均工资
SELECT
	AVG( salary ),
	job_id 
FROM
	employees 
GROUP BY
	job_id 
ORDER BY
	AVG( salary ) DESC 
	LIMIT 1;

-- ②查询job信息
SELECT
	* 
FROM
	jobs 
WHERE
	job_id = (
	SELECT
		job_id 
	FROM
		employees 
	GROUP BY
		job_id 
	ORDER BY
		AVG( salary ) DESC 
		LIMIT 1 
	);

-- 5. 查询平均工资高于公司平均工资的部门有哪些?

-- ①查询公司的平均工资
SELECT
	AVG( salary ) 
FROM
	employees;

-- ②查询每个部门的平均工资
SELECT
	AVG( salary ),
	department_id 
FROM
	employees 
GROUP BY
	department_id;

-- ③ 筛选②的结果集,满足平均工资>①
SELECT
	AVG( salary ),
	department_id 
FROM
	employees 
GROUP BY
	department_id 
HAVING
	AVG( salary ) > ( SELECT AVG( salary ) FROM employees );

-- 6. 查询出公司中所有 manager 的详细信息.

-- ①查询所有manager的员工编号
SELECT DISTINCT
	manager_id 
FROM
	employees;

-- ②查询详细信息,满足employee_id=①
SELECT
	* 
FROM
	employees 
WHERE
	employee_id IN ( SELECT DISTINCT manager_id FROM employees );

-- 7. 各个部门中 最高工资中最低的那个部门的 最低工资是多少

-- ①查询各部门的最高工资中最低的部门编号
SELECT
	department_id,
	MAX( salary ) 
FROM
	employees 
GROUP BY
	department_id 
ORDER BY
	MAX( salary ) ASC 
	LIMIT 1;

-- ②查询①结果的那个部门的最低工资
SELECT
	MIN( salary ) 
FROM
	employees e 
WHERE
	department_id = (
	SELECT
		department_id 
	FROM
		employees 
	GROUP BY
		department_id 
	ORDER BY
		MAX( salary ) ASC 
		LIMIT 1 
	);

-- 8. 查询平均工资最高的部门的 manager 的详细信息: last_name, department_id, email, salary

-- ①查询平均工资最高的部门编号
SELECT
	department_id 
FROM
	employees 
GROUP BY
	department_id 
ORDER BY
	AVG( salary ) DESC 
	LIMIT 1;

-- ②将employees和departments连接查询,筛选条件是①
SELECT
	last_name,
	e.department_id,
	email,
	salary 
FROM
	employees e
	INNER JOIN departments d ON e.employee_id = d.manager_id 
WHERE
	d.department_id = (
	SELECT
		department_id 
	FROM
		employees 
	GROUP BY
		department_id 
	ORDER BY
		AVG( salary ) DESC 
	LIMIT 1 
	);

5.10 分页查询

-- 进阶8:分页查询
/*
应用场景:当要显示的数据,一页显示不全,需要分页提交sql请求
语法:
	select 查询列表
	from 表
	[ join type join 表2
	on 连接条件
	where 筛选条件
	group by 分组字段
	having 分组后的筛选
	order by 排序的字段 ]
	limit [offset,] size;
	
	offset 要显示条目的起始索引(起始索引从0开始,默认也是从0开始)
	size 要显示的条目个数
特点:
	①limit语句放在查询语句的最后
	②公式
	要显示的页数 page,每页的条目数size
	
	select 查询列表
	from 表
	limit (page-1)*size,size;(一定要掌握)
*/
-- 案例1:查询前五条员工信息
SELECT * FROM employees LIMIT 0,5;

SELECT * FROM employees LIMIT 5;

-- 案例2:查询第11条——第25条(条数 = 25 - 11 + 1)
SELECT * FROM employees LIMIT 10,15;

-- 案例3:有奖金的员工信息,并且工资较高的前10名显示出来
SELECT
	* 
FROM
	employees 
WHERE
	commission_pct IS NOT NULL 
ORDER BY
	salary DESC 
	LIMIT 10;
-- 查询语句中涉及到的所有关键字,以及执行的先后顺序
select 查询列表         ⑦ 
from 表                ①
连接类型 join2on 连接条件             ③
where 筛选条件          ④
group by 分组列表       ⑤
having 分组后的筛选      ⑥
order by 排序列表       ⑧
limit 偏移,条目数       ⑨

5.11 联合查询

-- 进阶9:联合查询
/*
union 联合 合并:将多条查询语句的结果合并成一个结果

语法:
查询语句1
union
查询语句2
union
查询语句3
union
...

应用场景:
要查询的结果来自于多个表,且多个表没有直接的连接关系,但查询的信息一致时

特点:★
1、要求多条查询语句的查询列数是一致的!
2、要求多条查询语句查询的每一列的类型和顺序最好一致
3、union关键字默认去重,如果使用union all可以包含重复项
*/

-- 引入的案例:查询部门编号>90或邮箱包含a的员工信息
SELECT * FROM employees WHERE department_id > 90 OR email LIKE '%a%';

SELECT * FROM employees WHERE department_id > 90
UNION
SELECT * FROM employees WHERE email LIKE '%a%';

6. DML语言的学习

6.1 数据的增删改

-- DML语言
/*
数据操作语言:
					插入:insert
					修改:update
					删除:delete
*/

-- 1.插入语句
-- 方式一:
/*
语法:
insert into 表名(列名,...) values(值1,...);
*/
USE girls;
SELECT * FROM beauty;

-- 1.1 插入的值的类型要与列的类型一致或兼容
INSERT INTO beauty (
	id,
	`name`,
	sex,
	borndate,
	phone,
	photo,
	boyfriend_id 
)
VALUES
	(
		13,
		'唐艺昕',
		'女',
		'1990-04-23',
		'1898888888',
		NULL,
		2 
	);

-- 1.2 不可以为null的列必须插入值。可以为null的列如何插入值?

-- 方法一:字段的值用null表示,适合于该字段可以为空的
INSERT INTO beauty (
	id,
	`name`,
	sex,
	borndate,
	phone,
	photo,
	boyfriend_id 
)
VALUES
	(
		13,
		'唐艺昕',
		'女',
		'1990-4-23',
		'1898888888',
		NULL,
		2 
	);

-- 方法二:该字段省略
INSERT INTO beauty ( id, `name`, sex, phone )
VALUES
	( 15, '娜扎', '女', '1388888888' );

-- 1.3 列的顺序是否可以调换
INSERT INTO beauty ( `name`, sex, id, phone )
VALUES
	( '蒋欣', '女', 16, '110' );

-- 1.4 列数和值的个数必须一致
INSERT INTO beauty ( NAME, sex, id, phone )
VALUES
	( '关晓彤', '女', 17, '110' );

-- 1.5 可以省略列名,默认所有列,而且列的顺序和表中列的顺序一致,所以填充的值顺序也必须一致
INSERT INTO beauty
VALUES
	(
		18,
		'张飞',
		'男',
		NULL,
		'119',
		NULL,
	NULL 
	);

-- 方式二:
/*
语法:
insert into 表名
set 列名=值,列名=值,...
*/
INSERT INTO beauty 
SET id = 19,
`name` = '刘涛',
phone = '999';

/*
两种方式对比
*/
-- 1.6 方式一支持插入多行,方式二不支持
INSERT INTO beauty
VALUES
	(
		23,
		'唐艺昕1',
		'女',
		'1990-4-23',
		'1898888888',
		NULL,
		2 
	),
	(
		24,
		'唐艺昕2',
		'女',
		'1990-4-23',
		'1898888888',
		NULL,
		2 
	),
	(
		25,
		'唐艺昕3',
		'女',
		'1990-4-23',
		'1898888888',
		NULL,
		2 
	);

-- 1.7 方式一支持子查询,方式二不支持
INSERT INTO beauty ( id, `name`, phone ) SELECT
26,
'宋茜',
'11809866';

INSERT INTO beauty ( id, `name`, phone ) SELECT
id,
boyname,
'1234567' 
FROM
	boys 
WHERE
	id < 3;

-- 2.修改语句
/*
1.修改单表的记录[重点]
语法:
update 表名
set 列=新值,列=新值,...
where 筛选条件;

2.修改多表的记录[补充]

语法:
sql92语法:
update 表1 别名,表2 别名
set 列=值,列=值,...
where 连接条件
and 筛选条件;

sql99语法:
update 表1 别名
inner|left|right join 表2 别名
on 连接条件
set 列=值,...
where 筛选条件;
*/

-- 2.1 修改单表的记录

-- 案例1:修改beauty表中姓唐的女神的电话为13899888899
UPDATE beauty 
SET `phone` = '13899888899' 
WHERE
	`name` LIKE '唐%';

-- 案例2:修改boys表中id为2的名称为张飞,魅力值 10
UPDATE boys 
SET `boyname` = '张飞',
`usercp` = 10 
WHERE
	id = 2;

-- 2.2 修改多表的记录

-- 案例1:修改张无忌的女朋友的手机号为114,并且张无忌的魅力值为1000
UPDATE boys bo
INNER JOIN beauty b ON bo.`id` = b.`boyfriend_id` 
SET b.`phone` = '114',
bo.`userCP` = 1000 
WHERE
	bo.`boyName` = '张无忌';

-- 案例2:修改没有男朋友的女神的男朋友编号都为2号
UPDATE boys bo
RIGHT JOIN beauty b ON bo.`id` = b.`boyfriend_id` 
SET b.`boyfriend_id` = 2 
WHERE
	bo.id IS NULL;

-- 3.删除语句
/*
方式一:delete
语法:
1、单表的删除[重点]
delete from 表名 [where 筛选条件] [limit 条目数]

2、多表的删除[补充]

sql92语法:
delete 表1的别名,表2的别名
from 表1 别名,表2 别名
where 连接条件
and 筛选条件;

sql99语法:
delete 表1的别名,表2的别名
from 表1 别名
inner|left|right join 表2 别名 on 连接条件
where 筛选条件;

方式二:truncate
语法:
truncate table 表名;
*/

-- 方式一:delete
-- 3.1 单表的删除
-- 案例1:删除手机号以9结尾的女神信息
DELETE 
FROM
	beauty 
WHERE
	phone LIKE '%9';

-- 3.2 多表的删除
-- 案例1:删除张无忌的女朋友的信息
DELETE b 
FROM
	beauty b
	INNER JOIN boys bo ON b.boyfriend_id = bo.id 
WHERE
	bo.`boyName` = '张无忌';

-- 案例2:删除黄晓明的信息以及他女朋友的信息
DELETE b,
bo 
FROM
	boys bo
	INNER JOIN beauty b ON b.boyfriend_id = bo.id 
WHERE
	bo.boyName = '黄晓明';

-- 方式二:truncate语句:相当于清空表中的全部数据
-- 案例:将魅力值>100的男神信息删除(报错,不能加where语句)
TRUNCATE TABLE boys;

-- delete 与 truncate的区别?(重点面试题)
/*
1.delete 可以加where条件,truncate不能加
2.truncate删除,效率高一丢丢
3.假如要删除的表中有自增长列,如果用delete删除后,再插入数据,自增长列的值从断点开始,
							而truncate删除后,再插入数据,自增长列的值从1开始。
4.truncate删除没有返回值,delete删除有返回受影响的行数
5.truncate删除不能回滚,delete删除可以回滚
*/

DELETE FROM boys;
TRUNCATE TABLE boys;
INSERT INTO boys (boyname,usercp)
VALUES('张飞',100),('刘备',100),('关云长',100);

6.1.1 增删改练习

-- 对照test_lesson10 数据处理的练习

-- 1.
CREATE TABLE my_employees (
	Id INT ( 10 ),
	First_name VARCHAR ( 10 ),
	Last_name VARCHAR ( 10 ),
	Userid VARCHAR ( 10 ),
	Salary DOUBLE ( 10, 2 ) 
);

CREATE TABLE users (
	id INT,
	userid VARCHAR ( 10 ),
department_id INT 
)

-- 2.
DESC my_employees;

-- 3.
-- 方式一:
INSERT INTO my_employees
VALUES (1,'patel','Ralph','Rpatel',895),
(2,'Dancs','Betty','Bdancs',860),
(3,'Biri','Ben','Bbiri',1100),
(4,'Newman','Chad','Cnewman',750),
(5,'Ropeburn','Audrey','Aropebur',1550);

-- 方式二:
INSERT INTO my_employees
SELECT 1,'patel','Ralph','Rpatel',895 UNION
SELECT 2,'Dancs','Betty','Bdancs',860 UNION
SELECT 3,'Biri','Ben','Bbiri',1100 UNION
SELECT 4,'Newman','Chad','Cnewman',750 UNION
SELECT 5,'Ropeburn','Audrey','Aropebur',1550;

-- 4.
INSERT INTO users
VALUES
	( 1, 'Rpatel', 10 ),
	( 2, 'Bdancs', 10 ),
	( 3, 'Bbiri', 20 ),
	( 4, 'Cnewman', 30 ),
	( 5, 'Aropebur', 40 );

-- 5.
UPDATE my_employees 
SET Last_name = 'drelxer';

-- 6.
UPDATE my_employees 
SET Salary = 1000 
WHERE
	Salary < 900;
	
-- 7.
DELETE u,
m 
FROM
	users u
	INNER JOIN my_employees m ON u.userid = m.Userid 
WHERE
	u.userid = 'Bbiri';

-- 8.
DELETE FROM my_employees;
DELETE FROM users;

-- 9.
SELECT * FROM my_employees;
SELECT * FROM users;

-- 10.
TRUNCATE TABLE my_employees;
TRUNCATE TABLE users;

7. DDL语言的学习

7.1 库和表的管理

-- DDL语言(Data Definition Language)
/*
数据定义语言

库和表的管理

一、库的管理
创建、修改、删除
二、表的管理
创建、修改、删除

创建: create
修改: alter
删除: drop
*/

-- 1.库的管理

-- 1.1 库的创建
/*
语法:
create database [if not exists] 库名 [character set 字符集名];
*/

-- 案例:创建库Books
CREATE DATABASE
IF
	NOT EXISTS books;

-- 1.2 库的修改(该语法被废弃了)
RENAME DATABASE books TO 新库名;

-- 更改库的字符集
ALTER DATABASE books CHARACTER 
SET gbk;

-- 1.3 库的删除
/*
语法:drop database [if exists] 库名;
*/
DROP DATABASE
IF
	EXISTS books;

-- 2.表的管理

-- 2.1 表的创建
/*
语法:
create table 表名(
	列名 列的类型 [(长度) 约束],
	列名 列的类型 [(长度) 约束],
	列名 列的类型 [(长度) 约束],
	...
	列名 列的类型 [(长度) 约束]
)
*/
-- 案例:创建表Book
CREATE TABLE book(
	`id` INT,#编号
	`bName` VARCHAR(20),#图书名
	`price` DOUBLE,#价格
	`authorId`  INT,#作者编号
	`publishDate` DATETIME#出版日期
);
DESC book;

-- 案例:创建表author
CREATE TABLE IF NOT EXISTS author(
	id INT, #作者编号
	au_name VARCHAR(20), #作者姓名
	nation VARCHAR(10) #国籍
);
DESC author;

-- 2.2 表的修改
/*
语法
alter table 表名 add|drop|modify|change column 列名 [列类型 约束];
add:添加
注意:column可以省略
drop:删除
modify:修改列的数据类型
change:修改列名
*/

-- ①修改列名
ALTER TABLE book CHANGE COLUMN publishdate pubDate DATETIME;

-- ②修改列的类型或约束
/*
alter table 表名 modify column 列名 新类型 [新约束];
*/
ALTER TABLE book MODIFY COLUMN pubdate TIMESTAMP;

-- ③添加新列
/*
语法:alter table 表名 add column 列名 列类型 [first|after 字段名];
最后面的first表示把添加的列添加到表的第一列,或者哪一个列的后面,不加默认是添加到最后面
*/
ALTER TABLE author ADD COLUMN annual DOUBLE; 

-- ④删除列
/*
alter table 表名 drop column 列名
*/
ALTER TABLE author DROP COLUMN  annual;

-- ⑤修改表名
ALTER TABLE author RENAME TO book_author;

DESC book;

-- 2.3 表的删除
/*
drop table [if exists] 表名;
*/
DROP TABLE IF EXISTS book_author;

SHOW TABLES; #查看当前库的所有表

-- 通用的写法:
/*
DROP DATABASE IF EXISTS 旧库名;
CREATE DATABASE 新库名;

DROP TABLE IF EXISTS 旧表名;
CREATE TABLE  表名();
*/

-- 2.4 表的复制
INSERT INTO author VALUES
(1,'村上春树','日本'),
(2,'莫言','中国'),
(3,'冯唐','中国'),
(4,'金庸','中国');

SELECT * FROM Author;
SELECT * FROM copy;

-- ①仅仅复制表的结构
/*
create table 表名 like 旧表;
*/
CREATE TABLE copy LIKE author;

-- ②复制表的结构+数据
/*
create table 表名 select 查询列表 from 旧表 [where 筛选];
*/
CREATE TABLE copy2 
SELECT * FROM author;

-- ③只复制部分数据
CREATE TABLE copy3
SELECT id,au_name
FROM author 
WHERE nation='中国';

-- ④仅仅复制某些字段,不复制里面的数据
CREATE TABLE copy4 
SELECT id,au_name
FROM author
WHERE 0; #也可以写成1=2

7.1.1 库和表的管理练习

-- 基于test_lesson11的练习
-- 1.
#把该表建在test库里
USE test;
CREATE TABLE dept1(
			id INT(7),
			name VARCHAR(25)
);

-- 2.
CREATE TABLE dept2
SELECT department_id,department_name
FROM myemployees.departments;

-- 3.
CREATE TABLE emp5(
				id INT(7),
				First_name VARCHAR(25),
				Last_name VARCHAR(25),
				dept_id INT(7)
);

-- 4.
ALTER TABLE emp5 MODIFY COLUMN last_name VARCHAR(50);

-- 5.
CREATE TABLE employees2 LIKE myemployees.employees;

-- 6.
DROP TABLE IF EXISTS emp5;

-- 7.
ALTER TABLE employees2 RENAME TO emp5;

-- 8.
ALTER TABLE emp5 ADD COLUMN test_column INT;
ALTER TABLE dept ADD COLUMN test_column INT;

--9.
ALTER TABLE emp5 DROP COLUMN  dept_id;

7.2 常见数据类型

-- 常见的数据类型
/*
数值型:
	整型
	小数:
			定点数
			浮点数
字符型:
			较短的文本:char、varchar
			较长的文本:text、blob(较长的二进制数据)

日期型:
*/

-- 1.整型
/*
分类:
tinyint 、 smallint 、 mediumint 、 int/integer 、 bigint
	1	          2		       3	            4		         8      字节

特点:
① 如果不设置无符号还是有符号,默认是有符号,如果想设置无符号,需要添加unsigned关键字
② 如果插入的数值超出了整型的范围,会报out of range异常,并且插入临界值
③ 如果不设置长度,会有默认的长度
	长度代表了显示的最大宽度,如果不够会用0在左边填充,但需要搭配zerofill使用,并且默认变为无符号整型!
*/

-- 1.1 如何设置无符号和有符号
DROP TABLE IF EXISTS tab_int;
CREATE TABLE tab_int(
	t1 INT(7) ZEROFILL UNSIGNED,
	t2 INT(7) ZEROFILL 
);

INSERT INTO tab_int VALUES(-123456);
INSERT INTO tab_int VALUES(-123456,-123456);
INSERT INTO tab_int VALUES(2147483648,2147483648);

INSERT INTO tab_int VALUES(123,123);

-- 2.小数
/*
分类:
1.浮点型
float(M,D) 4个字节
double(M,D) 8个字节
2.定点型
dec(M,D)
decimal(M,D)

特点:
①
M:整数部位+小数部位的总个数
D:小数部位,相当于保留几位小数
如果超过范围,则显示临界值

②
M和D都可以省略
如果是decimal,则M默认为10,D默认为0
如果是float和double,则会根据插入的数值的精度来决定精度

③定点型的精确度较高,如果要求插入数值的精度较高,如:货币运算等可以考虑使用

原则:所选择的类型越简单越好,能保存数值的类型越小越好
*/

-- 测试M和D

DROP TABLE tab_float;
CREATE TABLE tab_float(
	f1 FLOAT,
	f2 DOUBLE,
	f3 DECIMAL
);
SELECT * FROM tab_float;
DESC tab_float;

INSERT INTO tab_float VALUES(123.4523,123.4523,123.4523);
INSERT INTO tab_float VALUES(123.456,123.456,123.456);
INSERT INTO tab_float VALUES(123.4,123.4,123.4);
INSERT INTO tab_float VALUES(1523.4,1523.4,1523.4);

-- 3.字符型
/*
较短的文本:

char
varchar

其他:

binary和varbinary用于保存较短的二进制
enum用于保存枚举
set用于保存集合

较长的文本:
text
blob(较大的二进制)

特点:

				写法							M的意思										特点				 空间的耗费	  效率
char	char(M)		最大长度不超过M,可以省略,默认为1		固定长度的字符		 		比较耗费	  	高

varchar varchar(M)	最大长度不超过M,不可以省略		  可变长度的字符		 		比较节省	   	低
*/

-- 3.1 枚举
CREATE TABLE tab_char(
	c1 ENUM('a','b','c')
);

INSERT INTO tab_char VALUES('a');
INSERT INTO tab_char VALUES('c');
INSERT INTO tab_char VALUES('b');
INSERT INTO tab_char VALUES('M'); #报错
INSERT INTO tab_char VALUES('A'); #报错

SELECT * FROM tab_char;

-- 3.2 set集合
CREATE TABLE tab_set(

	s1 SET('a','b','c','d')
);

INSERT INTO tab_set VALUES('a');
INSERT INTO tab_set VALUES('A,B'); #报错
INSERT INTO tab_set VALUES('a,c,d');

SELECT * FROM tab_set;

-- 4.日期型
/*

分类:
date只保存日期
time 只保存时间
year只保存年

datetime保存日期+时间
timestamp保存日期+时间

特点:

								字节					范围							时区等的影响
datetime	      8				1000——9999	            不受
timestamp				4	     		1970-2038	             受

*/
CREATE TABLE tab_date(
	t1 DATETIME,
	t2 TIMESTAMP
);

INSERT INTO tab_date VALUES(NOW(),NOW());

SELECT * FROM tab_date;

SHOW VARIABLES LIKE 'time_zone';

SET time_zone='+9:00';

7.3 常见约束

-- 常见约束

/*
含义:一种限制,用于限制表中的数据,为了保证表中的数据的准确和可靠性

分类:六大约束
	1.NOT NULL:非空,用于保证该字段的值不能为空
		比如姓名、学号等
	2.DEFAULT:默认,用于保证该字段有默认值
		比如性别
	3.PRIMARY KEY:主键,用于保证该字段的值具有唯一性(不可重复),并且非空
		比如学号、员工编号等
	4.UNIQUE:唯一,用于保证该字段的值具有唯一性(不可重复),可以为空
		比如座位号
	5.CHECK:检查约束 [mysql中不支持]
		比如年龄、性别
	6.FOREIGN KEY:外键,用于限制两个表的关系,用于保证该字段的值必须来自于主表的关联列的值
	在从表添加外键约束,用于引用主表中某列的值
		比如学生表的专业编号,员工表的部门编号,员工表的工种编号
	
添加约束的时机:
	1.创建表时
	2.修改表时
	
约束的添加分类:
	列级约束:
		六大约束语法上都支持,但外键约束没有效果
		
	表级约束:
		除了非空、默认外,其他的都支持
		
主键(PRIMARY KEY)和唯一(UNIQUE)的大对比:

		保证唯一性  	是否允许为空    一个表中可以有多少个   	是否允许组合
主键	   √		     	 ×		          至多有1个         √,但不推荐
唯一	   √		       √		          可以有多个        	√,但不推荐

外键:
	1、要求在从表设置外键关系
	2、从表的外键列的类型和主表的关联列的类型要求一致或兼容,名称无要求
	3、主表中的关联列必须是一个key(一般是主键或唯一键)
	4、插入数据时,先插入主表,再插入从表
	删除数据时,先删除从表,再删除主表
*/

CREATE TABLE 表名(
	字段名 字段类型 列级约束,
	字段名 字段类型,
	表级约束
)

CREATE DATABASE students;
use students;
-- 1.创建表时添加约束

-- 1.1 添加列级约束
/*
语法:
		直接在字段名和字段类型后面 追加约束类型即可
		只支持:默认、非空、主键、唯一
*/
CREATE TABLE stuinfo(
			id INT PRIMARY KEY, #主键约束
			stuName VARCHAR(20) NOT NULL, #非空约束
			gender CHAR(1) CHECK(gender = '男' OR gender = '女'), #检查约束
			seat INT UNIQUE, #唯一约束,可以为空
			age INT DEFAULT 18, #默认约束
			majorId INT REFERENCES major(id) #外键,引用major表的id字段
);

CREATE TABLE major(
			id INT PRIMARY KEY,
			majorName VARCHAR(20)
);
-- 查看表结构
DESC stuinfo;
-- 查看表中所有的索引,包括主键、外键、唯一
SHOW INDEX FROM stuinfo;

-- 1.2 添加表级约束
/*
语法:在各个字段的最下面
		[constraint 约束名] 约束类型(字段名) 
*/
-- 方式一:
DROP TABLE IF EXISTS stuinfo;
CREATE TABLE stuinfo(
				id INT,
				stuName VARCHAR(20),
				gender CHAR(1),
				seat INT,
				majorid INT,
				
				CONSTRAINT pk PRIMARY KEY(id), #主键
				CONSTRAINT uq UNIQUE(seat), #唯一键
				CONSTRAINT ck CHECK(gender = '男' OR gender = '女'), #检查约束
				CONSTRAINT fk_stuinfo_major FOREIGN KEY(majorid) REFERENCES major(id) #外键
);

-- 方式二:省略 CONSTRAINT 约束名
DROP TABLE IF EXISTS stuinfo;
CREATE TABLE stuinfo(
				id INT,
				stuName VARCHAR(20),
				gender CHAR(1),
				seat INT,
				majorid INT,
				
				PRIMARY KEY(id), #主键
				UNIQUE(seat), #唯一键
				CHECK(gender = '男' OR gender = '女'), #检查约束
				FOREIGN KEY(majorid) REFERENCES major(id) #外键
);

SHOW INDEX FROM stuinfo;

-- 通用的写法
CREATE TABLE IF NOT EXISTS stuinfo(
	id INT PRIMARY KEY, #主键
	stuname VARCHAR(20) NOT NULL, 
	sex CHAR(1),
	age INT DEFAULT 18, #默认18
	seat INT UNIQUE, #唯一键
	majorid INT,
	CONSTRAINT fk_stuinfo_major FOREIGN KEY(majorid) REFERENCES major(id) #外键
);

-- 2.修改表时添加约束
/*
1、添加列级约束
alter table 表名 modify column 字段名 字段类型 新约束;

2、添加表级约束
alter table 表名 add [constraint 约束名] 约束类型(字段名) [外键的引用];
*/
DROP TABLE IF EXISTS stuinfo;
CREATE TABLE stuinfo(
	id INT,
	stuname VARCHAR(20),
	gender CHAR(1),
	seat INT,
	age INT,
	majorid INT
);
DESC stuinfo;

-- 2.1 添加非空约束
ALTER TABLE stuinfo MODIFY COLUMN stuname VARCHAR(20) NOT NULL;

-- 2.2 添加默认约束
ALTER TABLE stuinfo MODIFY COLUMN age INT DEFAULT 18;

-- 2.3 添加主键
-- ①列级约束
ALTER TABLE stuinfo MODIFY COLUMN id INT PRIMARY KEY;
-- ②表级约束
ALTER TABLE stuinfo ADD PRIMARY KEY(id);

-- 2.4 添加唯一键
-- ①列级约束
ALTER TABLE stuinfo MODIFY COLUMN seat INT UNIQUE;
-- ②表级约束
ALTER TABLE stuinfo ADD UNIQUE(seat);

-- 2.5 添加外键(CONSTRAINT 键名,也可以省略)
ALTER TABLE stuinfo ADD CONSTRAINT fk_stuinfo_major FOREIGN KEY ( majorid ) REFERENCES major ( id );

-- 2.5.1 添加外键时,添加级联删除(ON DELETE CASCADE)
/*
添加级联删除后,删除主表的记录,与之关联的从表的记录也会被一起删除
*/
ALTER TABLE stuinfo ADD CONSTRAINT fk_stuinfo_major FOREIGN KEY ( majorid ) REFERENCES major ( id ) ON DELETE CASCADE;

-- 2.5.2 添加外键时,添加级联置空(ON DELETE SET NULL)
/*
添加级联置空后,删除主表的记录,与之关联的从表的字段会被置空
*/
ALTER TABLE stuinfo ADD CONSTRAINT fk_stuinfo_major FOREIGN KEY ( majorid ) REFERENCES major ( id ) ON DELETE SET NULL;

-- 3.修改表时删除约束

-- 3.1 删除非空约束
ALTER TABLE stuinfo MODIFY COLUMN stuname VARCHAR(20) [NULL];

-- 3.2 删除默认约束
ALTER TABLE stuinfo MODIFY COLUMN age INT ;

-- 3.3 删除主键
ALTER TABLE stuinfo DROP PRIMARY KEY;

-- 3.4 删除唯一
/*
alter table 表名 drop index 索引名;
*/
ALTER TABLE stuinfo DROP INDEX seat;

-- 3.5 删除外键
/*
alter table 表名 drop foreign key 约束名
*/
ALTER TABLE stuinfo DROP FOREIGN KEY fk_stuinfo_major;

SHOW INDEX FROM stuinfo;

7.3.1 常见约束练习

-- 1. 向表 emp2 的 id 列中添加 PRIMARY KEY 约束(my_emp_id_pk)
ALTER TABLE emp2 MODIFY COLUMN id INT PRIMARY KEY;
ALTER TABLE emp2 ADD CONSTRAINT my_emp_id_pk PRIMARY key(id);

-- 2. 向表 dept2 的 id 列中添加 PRIMARY KEY 约束(my_dept_id_pk)
ALTER TABLE dept2 MODIFY COLUMN id INT PRIMARY KEY;
ALTER TABLE dept2 ADD CONSTRAINT my_dept_id_pk PRIMARY KEY(id);

-- 3. 向表 emp2 中添加列 dept_id,并在其中定义 FOREIGN KEY 约束,与之相关联的列是 dept2 表中的 id 列。
ALTER TABLE emp2 ADD COLUMN dept_id INT;
ALTER TABLE emp2 ADD CONSTRAINT fk_emp2_dept2 FOREIGN KEY(dept_id) REFERENCES dept2(id);

/*
						位置								支持的约束类型          是否可以起约束名
列级约束:  列的后面          语法都支持,但外键没效果         不可以
表级约束:  所有列的下面      默认和非空不支持,其他支持    可以(但是对主键没效果,主键键名默认就是PRIMARY KEY)   
*/

7.4 标识列

-- 标识列
/*
又称为自增长列
含义:可以不用手动的插入值,系统提供默认的序列值

特点:
1、标识列必须和主键搭配吗?不一定,但要求是一个key
2、一个表可以有几个标识列?至多一个!
3、标识列的类型只能是数值型
4、标识列可以通过 SET auto_increment_increment=3;设置步长
5、可以通过首次手动插入值,设置起始值
*/
-- 1.创建表时设置标识列

DROP TABLE IF EXISTS tab_identity;
CREATE TABLE tab_identity(
	id INT PRIMARY KEY,
	NAME FLOAT UNIQUE AUTO_INCREMENT, 
	seat INT 
);

TRUNCATE TABLE tab_identity;

INSERT INTO tab_identity(id,NAME) VALUES(NULL,'john');
INSERT INTO tab_identity(NAME) VALUES('lucy');
SELECT * FROM tab_identity;

SHOW VARIABLES LIKE '%auto_increment%';

-- 设置每次自动增长的步长
SET auto_increment_increment=3;

-- 2.修改表时设置标识列
/*
alter table 表名 modify column 字段名 字段类型 约束 auto_increment;
*/
ALTER TABLE tab_identity MODIFY COLUMN id INT PRIMARY KEY AUTO_INCREMENT;

-- 3.修改表时删除标识列
-- 删除表字段id的主键,因为一张表只能有一个主键,所以主键可以默认不写
ALTER TABLE tab_identity MODIFY COLUMN id int; 

8. TCL语言的学习

8.1 事务

-- 事务
/*
Transaction Control Language 事务控制语言

事务:
一个或一组sql语句组成一个执行单元,这个执行单元要么全部执行,要么全部不执行。

案例:转账

张三丰   1000
郭襄	    1000

update 表 set 张三丰的余额=500 where name='张三丰'
意外
update 表 set 郭襄的余额=1500 where name='郭襄'

事务的特性:
ACID
原子性(Atomicity):一个事务是不可再分割的,要么都执行要么都不执行
一致性(Consistency):一个事务执行会使数据从一个一致状态切换到另外一个一致状态
隔离性(Isolation):一个事务的执行不受其他事务的干扰,多个事务互相隔离
持久性(Durability):一个事务一旦提交,则会永久的持久化到本地

事务的创建
1.隐式事务:事务没有明显的开启和结束的标记
比如insert、update、delete语句

delete from 表 where id =1;

2.显式事务:事务具有明显的开启和结束的标记
前提:必须先设置自动提交功能为禁用

-- 关闭当前事务的自动提交
set autocommit=0;

具体步骤:
步骤1:开启事务
set autocommit=0;
start transaction; 可以省略的
步骤2:编写事务中的sql语句(select insert update delete)
语句1;
语句2;
...

步骤3:结束事务
commit; 提交事务
rollback; 回滚事务

savepoint 节点名; 设置保存点
一般搭配rollback to 节点名;

事务的隔离级别:
															脏读					不可重复读					幻读
read uncommitted(读未提交):	  	√							√								√
read committed(读已提交):  			×							√								√
repeatable read(可重复读): 			×							×								√
serializable(可串行化):	  			×             ×               ×

脏读:一个事务读取了其他事务还没有提交的数据,读到的是其他事物"更新"的数据
不可重复读:一个事务多次读取数据,结果都不一样
幻读:一个事务读取了其他事务还没有提交的数据,读到的是其他事物"插入"的数据

mysql中默认 第三个隔离级别 repeatable read
oracle中默认第二个隔离级别 read committed
查看隔离级别(mysql8.0+)
select @@transaction_isolation;
查看隔离级别(mysql 5.7)
select @@tx_isolation;
设置隔离级别
set session transaction isolation level 隔离级别;

开启事务的语句;
update 表 set 张三丰的余额=500 where name='张三丰'

update 表 set 郭襄的余额=1500 where name='郭襄' 
结束事务的语句;
*/

-- 查看mysql的存储引擎
SHOW ENGINES;

-- 查看自动提交的状态
SHOW VARIABLES LIKE 'autocommit';

/*
创建表和插入数据
USE test;

DROP TABLE IF EXISTS account;

CREATE TABLE account(
				id INT PRIMARY KEY AUTO_INCREMENT,
				username VARCHAR(20),
				balance DOUBLE
);

INSERT INTO account(username,balance)
VALUES ('张无忌',1000),('赵敏',1000);
*/
-- 1.演示事务的使用步骤

-- 1.1 开启事务
SET autocommit = 0;
START TRANSACTION;
-- 1.2 编写一组事务的语句
UPDATE account SET balance = 1000 WHERE username = '张无忌';
UPDATE account SET balance = 1000 WHERE username = '赵敏';
-- 1.3 结束事务
ROLLBACK; #回滚事务
COMMIT; #提交事务

SELECT * FROM account;

-- 2.演示事务对于delete和truncate的处理区别

-- 2.1 演示delete
SET autocommit=0;
START TRANSACTION;
DELETE FROM account;
ROLLBACK; #delete支持回滚,回滚后数据还存在

-- 2.2 演示truncate
SET autocommit=0;
START TRANSACTION;
TRUNCATE TABLE account;
ROLLBACK; #truncate不支持回滚,回滚后数据也是删除了

-- 3.演示savepoint的使用
SET autocommit=0;
START TRANSACTION;
DELETE FROM account WHERE id = 1;
SAVEPOINT a; #设置保存点
DELETE FROM account WHERE id = 2;

ROLLBACK TO a; #回滚到保存点处,也就是说id=1的信息会被删除,而id=2的信息会被保存下来

SELECT * FROM account;

9.视图

-- 视图
/*
含义:虚拟表,和普通表一样使用
mysql5.1版本出现的新特性,本身是一个虚拟表,它的数据来自于表,通过执行时动态生成
好处:
1、简化sql语句
2、提高了sql的重用性
3、保护基表的数据,提高了安全性

比如:类似于java中的方法

			创建语法的关键字		   是否实际占用物理空间									使用

视图		create view					只是保存了sql逻辑								一般用于查询

表			create table					保存了数据											增删改查
*/

-- 案例:查询姓张的学生名和专业名
SELECT stuname,majorname FROM stuinfo s
INNER JOIN major m ON s.majorid = m.id
WHERE s.stuname LIKE '张%';

CREATE VIEW v1
AS
SELECT stuname,majorname FROM stuinfo s
INNER JOIN major m ON s.majorid = m.id;

SELECT * FROM v1 WHERE stuname LIKE '张%';

-- 1.创建视图
/*
语法:
		create view 视图名
		as
		查询语句;
*/

-- 案例1:查询姓名中包含a字符的员工名、部门名和工种信息
-- ①创建视图
CREATE VIEW myv1
AS
SELECT last_name,department_name,job_title
FROM employees e
INNER JOIN departments d ON e.department_id = d.department_id
INNER JOIN jobs j ON j.job_id = e.job_id;

-- ②使用视图
SELECT * FROM myv1 WHERE Last_name LIKE '%a%';

-- 案例2:查询各部门的平均工资级别
-- ①创建视图,查看每个部门的平均工资
CREATE VIEW myv2
AS
SELECT AVG(salary) ag,department_id
FROM employees
GROUP BY department_id;

-- ②使用视图
SELECT myv2.ag,j.grade_level
FROM myv2 
INNER JOIN job_grades j
ON myv2.ag BETWEEN j.lowest_sal AND j.highest_sal;

-- 案例3:查询平均工资最低的部门信息
SELECT * FROM myv2 ORDER BY ag ASC LIMIT 1;

-- 案例4:查询平均工资最低的部门名和工资
CREATE VIEW myv3
AS
SELECT * FROM myv2 ORDER BY ag ASC LIMIT 1;

SELECT d.* FROM myv3 m
INNER JOIN departments d
ON m.department_id = d.department_id;

-- 2.视图的修改

-- 2.1 方式一:
/*
create or replace view  视图名
as
查询语句;
*/
SELECT * FROM myv3;

CREATE OR REPLACE VIEW myv3
AS
SELECT AVG(salary),job_id
FROM employees
GROUP BY job_id;

-- 2.2 方式二:
/*
语法:
alter view 视图名
as 
查询语句;
*/
ALTER VIEW myv3
AS
SELECT * FROM employees;

-- 3.删除视图
/*
语法:drop view 视图名,视图名,...;
*/
DROP VIEW emp_v1,emp_v2,myv3;

-- 4.查看视图
DESC myv3;

SHOW CREATE VIEW myv3;

-- 5.视图的更新
CREATE OR REPLACE VIEW myv1
AS
SELECT last_name,email,salary*12*(1+IFNULL(commission_pct,0)) "annual salary"
FROM employees;

CREATE OR REPLACE VIEW myv1
AS
SELECT last_name,email
FROM employees;

SELECT * FROM myv1
SELECT * FROM employees;

-- 5.1 插入数据
INSERT INTO myv1 VALUES('张飞','zf@qq.com');

-- 5.2 修改数据
UPDATE myv1 SET last_name = '张无忌' WHERE last_name='张飞';

-- 5.3 删除数据
DELETE FROM myv1 WHERE last_name = '张无忌';

-- 5.4 具备以下特点的视图不允许更新

-- 5.4.1 包含以下关键字的sql语句:分组函数、distinct、group by、having、union或者union all

-- 案例:group by
CREATE OR REPLACE VIEW myv1
AS
SELECT MAX(salary) m,department_id
FROM employees
GROUP BY department_id;

SELECT * FROM myv1;

-- 更新
UPDATE myv1 SET m=9000 WHERE department_id=10; #报错,视图包含了group by

-- 5.4.2 常量视图
CREATE OR REPLACE VIEW myv2
AS
SELECT 'john' NAME;

SELECT * FROM myv2;

-- 下面的sql语句也可以算是常量视图
SELECT (SELECT MAX(salary) FROM employees) 最高工资;

-- 更新
UPDATE myv2 SET NAME='lucy'; #报错,视图里面包含常量

-- 5.4.3 select中包含子查询
CREATE OR REPLACE VIEW myv3
AS
SELECT department_id,(SELECT MAX(salary) FROM employees) 最高工资
FROM departments;

SELECT * FROM myv3;

-- 更新
UPDATE myv3 SET 最高工资=100000; #报错,视图包含子查询

-- 5.4.4 join关键字
CREATE OR REPLACE VIEW myv4
AS
SELECT last_name,department_name
FROM employees e
JOIN departments d
ON e.department_id  = d.department_id;

SELECT * FROM myv4;

-- 更新
UPDATE myv4 SET last_name  = '张飞' WHERE last_name='Whalen'; #可以更新
INSERT INTO myv4 VALUES('陈真','xxxx'); #报错,语句包含join

-- 5.4.5 from后面加一个不能更新的视图
CREATE OR REPLACE VIEW myv5
AS
SELECT * FROM myv3;

SELECT * FROM myv5;

-- 更新
UPDATE myv5 SET 最高工资=10000 WHERE department_id=60; #报错

-- 5.4.6 where子句的子查询引用了from子句中的表
CREATE OR REPLACE VIEW myv6
AS
SELECT last_name,email,salary
FROM employees
WHERE employee_id IN(
	SELECT manager_id
	FROM employees
	WHERE manager_id IS NOT NULL
);

SELECT * FROM myv6;

-- 更新
UPDATE myv6 SET salary=10000 WHERE last_name = 'k_ing'; #报错

10.变量

-- 变量
/*
系统变量:
			全局变量
			会话变量

自定义变量:
			用户变量
			局部变量
*/

-- 1.系统变量
/*
说明:变量由系统定义,不是用户定义,属于服务器层面,必须拥有super权限才能为系统变量赋值,作用域为整个服务器
注意:全局变量需要添加global关键字,会话变量需要添加session关键字,如果不写,默认会话级别
使用步骤:
1、查看所有系统变量
show global|[session] variables;
2、查看满足条件的部分系统变量
show global|[session] variables like '%char%';
3、查看指定的系统变量的值
select @@global|[session] 系统变量名;
4、为某个系统变量赋值
方式一:
set global|[session] 系统变量名=值;
方式二:
set @@global|[session] 系统变量名=值;
*/

-- 1.1 全局变量
/*
服务器每次启动都将为所有的全局变量初始化值
作用域:针对于所有会话(连接)有效,但不能跨重启
*/
-- ①查看所有全局变量
SHOW GLOBAL VARIABLES;
-- ②查看满足条件的部分系统变量
SHOW GLOBAL VARIABLES LIKE '%char%';
-- ③查看指定的系统变量的值
SELECT @@global.autocommit;
select @@transaction_isolation; #查看隔离级别
-- ④为某个系统变量赋值
SET @@global.autocommit=0;
SET GLOBAL autocommit=0;

-- 1.2 会话变量
/*
服务器为每一个连接的客户端都提供了系统变量
作用域:针对于当前会话(连接)有效
*/
-- ①查看所有会话变量
SHOW SESSION VARIABLES;
-- ②查看满足条件的部分会话变量
SHOW SESSION VARIABLES LIKE '%char%';
-- ③查看指定的会话变量的值
SELECT @@autocommit;
SELECT @@session.transaction_isolation;
SELECT @@transaction_isolation;
-- ④为某个会话变量赋值
SET @@session.transaction_isolation='read-uncommitted';
SET SESSION transaction_isolation='read-committed';

-- 2.自定义变量
/*
说明:变量由用户自定义,而不是系统提供的
使用步骤:
		1、声明
		2、赋值
		3、使用(查看、比较、运算等)
*/

-- 2.1 用户变量
/*
作用域:针对于当前会话(连接)有效,作用域和会话变量一样
*/
-- 赋值操作符:=或:=
-- ①声明并初始化
SET @变量名=;
SET @变量名:=;
SELECT @变量名:=;

-- ②赋值(更新变量的值)
-- 方式一:
	SET @变量名=;
	SET @变量名:=;
	SELECT @变量名:=;
-- 方式二:
	SELECT 字段 INTO @变量名
	FROM;
-- ③使用(查看变量的值)
SELECT @用户变量名;

-- 案例:
/*
set @count = 1;
*/
SELECT COUNT(*) INTO @count 
FROM employees;

SELECT @count; #107

-- 2.2 局部变量
/*
作用域:仅仅在定义它的begin end块中有效
应用在 begin end中的第一句话
*/
-- 步骤:
-- ①声明
DECLARE 变量名 类型;
DECLARE 变量名 类型 [DEFAULT];
-- ②赋值(更新变量的值)
-- 方式一:
	SET 局部变量名=;
	SET 局部变量名:=;
	SELECT @局部变量名:=;
-- 方式二:
	SELECT 字段 INTO 局部变量名
	FROM;
-- ③使用(查看变量的值)
SELECT 局部变量名;

-- 案例:声明两个变量,求和并打印

-- 用户变量
SET @m=1;
SET @n=1;
SET @sum=@m+@n;
SELECT @sum;

-- 局部变量,报错
DECLARE m INT DEFAULT 1;
DECLARE n INT DEFAULT 1;
DECLARE SUM INT;
SET SUM=m+n;
SELECT SUM;

/*
用户变量和局部变量的对比

						作用域										定义位置										语法
用户变量			当前会话							会话的任何地方							加@符号,不用指定类型
局部变量		定义它的BEGIN END中 	 BEGIN END的第一句话				一般不用加@,需要指定类型
*/

11.存储过程

-- 存储过程和函数
/*
存储过程和函数:类似于java中的方法
好处:
1、提高代码的重用性
2、简化操作
*/

-- 存储过程
/*
含义:一组预先编译好的SQL语句的集合,理解成批处理语句
1、提高代码的重用性
2、简化操作
3、减少了编译次数并且减少了和数据库服务器的连接次数,提高了效率
*/

-- 1.创建语法
/*
CREATE PROCEDURE 存储过程名(参数列表)
BEGIN
			存储过程体(一组合法的SQL语句)
END
*/
-- 注意:
/*
1、参数列表包含三部分
参数模式  参数名  参数类型
举例:
in stuname varchar(20)

参数模式:
in:该参数可以作为输入,也就是该参数需要调用方传入值
out:该参数可以作为输出,也就是该参数可以作为返回值
inout:该参数既可以作为输入又可以作为输出,也就是该参数既需要传入值,又可以返回值

2、如果存储过程体仅仅只有一句话,begin end可以省略
存储过程体中的每条sql语句的结尾要求必须加分号。
存储过程的结尾可以使用 delimiter 重新设置
语法:
delimiter 结束标记
案例:
delimiter $
*/

-- 2.调用语法
CALL 存储过程名(实参列表);

-- 3.案例演示
-- 3.1 空参列表
-- 案例:插入到admin表中五条记录
SELECT * FROM admin;

delimiter $ #自定义结束标记符
CREATE PROCEDURE myp1()
BEGIN
INSERT INTO admin(username,`password`) 
VALUE ('john1','0000'),('lily','0000'),('rose','0000'),('jack','0000'),('tom','0000');
END $

-- 调用
CALL myp1()$

-- 3.2 创建带in模式参数的存储过程
-- 案例1:创建存储过程实现 根据女神名,查询对应的男神信息

delimiter $ #自定义结束标记符
CREATE PROCEDURE myp2(IN beautyName VARCHAR(20))
BEGIN
SELECT b.*,bo.* FROM boys bo
RIGHT JOIN beauty b ON bo.id = b.boyfriend_id
WHERE b.`name` = beautyName;
END $

-- 调用
CALL myp2('柳岩')$
CALL myp2('小昭')$

-- 案例2:创建存储过程实现,用户是否登录成功
delimiter $ #自定义结束标记符
CREATE PROCEDURE myp3(IN `username` VARCHAR(20),IN `password` VARCHAR(20))
BEGIN
DECLARE result INT DEFAULT 0; #声明并初始化变量

SELECT COUNT(*) INTO result #变量赋值
FROM admin
WHERE admin.username = username
AND admin.password = password;

SELECT IF(result>0,'成功','失败'); #使用变量
END $

-- 调用
CALL myp3('张飞','8888')$

-- 3.3 创建带out模式参数的存储过程
-- 案例1:根据输入的女神名,返回对应的男神名
delimiter $ #自定义结束标记符
CREATE PROCEDURE myp5(IN `beautyName` VARCHAR(20),OUT `boyName` VARCHAR(20))
BEGIN
SELECT bo.boyName INTO `boyName`
FROM boys bo
INNER JOIN beauty b ON bo.id = b.boyfriend_id
WHERE b.`name` = `beautyName`;
END $

-- 调用
SET @bName$ #自定义用户变量
CALL myp5('小昭',@bName)$
SELECT @bName$ #使用变量

-- 案例2:根据输入的女神名,返回对应的男神名和魅力值
delimiter $ #自定义结束标记符
CREATE PROCEDURE myp6(IN `beautyName` VARCHAR(20),OUT `boyName` VARCHAR(20),OUT `userCp` INT)
BEGIN
SELECT bo.boyName,bo.userCP INTO `boyName`,`userCp`
FROM boys bo
INNER JOIN beauty b ON bo.id = b.boyfriend_id
WHERE b.`name` = `beautyName`;
END $

-- 调用
CALL myp6('小昭',@bName,@usercp)$
SELECT @bName,@usercp$  #使用变量

-- 3.4 创建带inout模式参数的存储过程
-- 案例1:传入a和b两个值,最终a和b都翻倍并返回
delimiter $ #自定义结束标记符
CREATE PROCEDURE myp7(INOUT a INT,INOUT b INT)
BEGIN
SET a = a * 2; #局部变量赋值
SET b = b * 2;
END $

-- 调用
SET @m=10$ #定义用户变量
SET @n=20$
CALL myp7(@m,@n)$
SELECT @m,@n$ #使用变量

-- 4.删除存储过程
/*
语法:drop procedure 存储过程名
*/
DROP PROCEDURE test_pro3;
DROP PROCEDURE p2,p3;  #报错,不能一次删除多个

-- 5.查看存储过程的信息
DESC myp2; #报错
SHOW CREATE PROCEDURE myp2;

11.1 存储过程练习

-- 1.创建存储过程实现传入用户名和密码,插入到admin表中
delimiter $ #自定义结束标记符
CREATE PROCEDURE test_pro1(IN `username` VARCHAR(20),IN `loginPwd` VARCHAR(20))
BEGIN
INSERT INTO admin(admin.username,admin.`password`)
VALUES(username,loginPwd);
END $

-- 调用
CALL test_pro1('xiexu','283619')$

-- 2.创建存储过程实现传入女神编号,返回女神名称和女神电话
delimiter $ #自定义结束标记符
CREATE PROCEDURE test_pro2(IN `bid` INT,OUT `uname` VARCHAR(20),OUT `uphone` VARCHAR(20))
BEGIN
SELECT b.name,b.phone INTO `uname`,`uphone`
FROM beauty b 
WHERE b.id = `bid`;
END $

-- 调用
CALL test_pro2(2,@uname,@uphone)$
SELECT @uname,@uphone$

-- 3.创建存储过程或函数实现传入两个女神生日,返回大小
delimiter $ #自定义结束标记符
CREATE PROCEDURE test_pro3(IN `birth1` DATETIME,IN `birth2` DATETIME,OUT `result` INT)
BEGIN
SELECT DATEDIFF(`birth1`,`birth2`) INTO `result`;
END $

-- 调用
CALL test_pro3('1987-12-30',now(),@m)$
SELECT @m$

-- 4.创建存储过程或函数实现传入一个日期,格式化成xx年xx月xx日并返回
delimiter $ #自定义结束标记符
CREATE PROCEDURE test_pro4(IN `mydate` DATETIME,OUT `strDate` VARCHAR(10))
BEGIN
SELECT DATE_FORMAT(`mydate`,'%y年%m月%d日') INTO `strDate`;
END $

-- 调用
CALL test_pro4(now(),@str)$
SELECT @str$

-- 5.创建存储过程或函数实现传入女神名称,返回:女神 and 男神 格式的字符串
/*
如 传入 :小昭
返回: 小昭 AND 张无忌
*/
delimiter $ #自定义结束标记符
CREATE PROCEDURE test_pro5(IN `beautyName` VARCHAR(20),OUT `str` VARCHAR(50))
BEGIN
SELECT CONCAT(`beautyName`,' and ',IFNULL(bo.boyName,'NULL')) INTO `str`
FROM boys bo
RIGHT JOIN beauty b ON b.boyfriend_id = bo.id
WHERE b.name = `beautyName`;
END $

-- 调用
CALL test_pro5('柳岩',@str)$
SELECT @str $

-- 6.创建存储过程或函数,根据传入的条目数和起始索引,查询beauty表的记录
delimiter $ #自定义结束标记符
CREATE PROCEDURE test_pro6(IN `startIndex` INT,IN `size` INT)
BEGIN
SELECT * FROM beauty LIMIT `startIndex`,`size`;
END $

-- 调用
CALL test_pro6(3,5)$

12.函数

-- 函数
/*
含义:一组预先编译好的SQL语句的集合,理解成批处理语句
1、提高代码的重用性
2、简化操作
3、减少了编译次数并且减少了和数据库服务器的连接次数,提高了效率

区别:
存储过程:可以有0个返回,也可以有多个返回,适合做批量插入、批量更新
函数:有且仅有1个返回,适合做处理数据后返回一个结果
*/

-- 1.创建语法
/*
CREATE FUNCTION 函数名(参数列表) RETURNS 返回类型
BEGIN
	函数体
END

注意:
1.参数列表 包含两部分:
									参数名 参数类型

2.函数体:肯定会有return语句,如果没有会报错
如果return语句没有放在函数体的最后也不报错,但不建议

return 值;
3.函数体中仅有一句话,则可以省略begin end
4.使用delimiter语句设置结束标记
*/

-- 2.调用语法
/*
SELECT 函数名(参数列表)
*/

-- 3.案例演示

-- 3.1 无参有返回
-- 案例:返回公司的员工个数
delimiter $ #自定义结束标记符
CREATE FUNCTION myf1() RETURNS INT
BEGIN
DECLARE c INT DEFAULT 0; #定义局部变量,并且加上默认值0

SELECT count(*) INTO c #为局部变量赋值
FROM employees;
RETURN c;
END $

-- 调用
SELECT myf1()$

-- 3.2 有参有返回
-- 案例1:根据员工名,返回它的工资
delimiter $ #自定义结束标记符
CREATE FUNCTION myf2(`empName` VARCHAR(20)) RETURNS DOUBLE
BEGIN
SET @sal=0; #定义用户变量
SELECT salary INTO @sal #为用户变量赋值
FROM employees
WHERE last_name = `empName`;

RETURN @sal;
END $

-- 调用
SELECT myf2('Kochhar')$

-- 案例2:根据部门名,返回该部门的平均工资
delimiter $ #自定义结束标记符
CREATE FUNCTION myf3(`deptName` VARCHAR(20)) RETURNS DOUBLE
BEGIN
DECLARE av DOUBLE; #定义局部变量

SELECT AVG(salary) INTO av #为局部变量赋值
FROM employees e
INNER JOIN departments d ON e.department_id = d.department_id
WHERE d.department_name = `deptName`;

RETURN av;
END $

-- 调用
SELECT myf3('IT')$

-- 4.查看函数
SHOW CREATE FUNCTION myf3;

-- 5.删除函数
DROP FUNCTION myf3;

-- 案例:创建函数,实现传入两个float,返回二者之和
delimiter $ #自定义结束标记符
CREATE FUNCTION test_fun1(`num1` FLOAT,`num2` FLOAT) RETURNS FLOAT
BEGIN
	DECLARE SUM FLOAT DEFAULT 0; #定义局部变量,并且有默认值0
	
	SET SUM=num1+num2;
	RETURN SUM;
END $

SELECT test_fun1(1,2)$

13.流程控制结构

-- 流程控制结构
/*
顺序结构:程序从上往下依次执行
分支结构:程序从两条或多条路径中选择一条去执行
循环结构:程序在满足一定条件的基础上,重复执行一段代码
*/

-- 1.分支结构

-- 1.1 case结构
/*
情况一:类似于switch,一般用于实现等值判断
语法:
case 变量或表达式
when 值1 then 语句1;
when 值2 then 语句2;
...
else 语句n;
end case;

情况2:类似于多重if语句,一般用于实现区间判断。
语法:
case 
when 条件1 then 语句1;
when 条件2 then 语句2;
...
else 语句n;
end case;

特点:
①可以作为表达式,嵌套在其他语句中使用。可以放在任何地方,BEGIN END 中或BEGIN END 的外面
②可以作为独立的语句去使用,只能放在BEGIN END中如果when中的值满足或条件成立,
则执行对应的then后面的语句,并且结束CASE如果都不满足,则执行E1SE中的语句或值。

③ELSE可以省略,如果ELSE省略了,并且所有WHEN条件都不满足,则返回NULL。

位置:
可以放在任何位置,
如果放在begin end 外面,作为表达式结合着其他语句使用
如果放在begin end 里面,一般作为独立的语句使用
*/

-- 案例:创建存储过程,根据传入的成绩,来显示等级,比如传入的成绩:90-100, 显示A,80-90,显示B,60-80,显示c,否则,显示D
delimiter $ #自定义结束标记符
CREATE PROCEDURE test_case(IN score INT)
BEGIN
CASE 
WHEN score>=90 AND score<=100 THEN SELECT 'A';
WHEN score>=80 THEN SELECT 'B';
WHEN score>=60 THEN SELECT 'C';
ELSE SELECT 'D';
END CASE;

END $

-- 调用
CALL test_case(55)$

-- 1.2 if结构
/*
语法:
if 条件1 then 语句1;
elseif 条件2 then 语句2;
....
[else 语句n;]
end if;

功能:类似于多重if
位置:只能应用在begin end中
*/

-- 案例:创建函数,实现传入成绩,如果成绩>90,返回A,如果成绩>80,返回B,如果成绩>60,返回C,否则返回D
delimiter $ #自定义结束标记符
CREATE FUNCTION test_if(score INT) RETURNS CHAR
BEGIN
if score>=90 AND score<=100 THEN RETURN 'A';
ELSEIF score>=80 THEN RETURN 'B';
ELSEIF score>=60 THEN RETURN 'C';
ELSE RETURN 'D';
END IF;

END $

-- 调用
SELECT test_if(86)$

-- 2.循环结构
/*
分类:
while、loop、repeat

循环控制:
iterate类似于continue,继续,结束本次循环,继续下一次
leave 类似于break,跳出,结束当前所在的循环
*/

-- 2.1 while
/*
语法:
[标签:]while 循环条件 do
				循环体;
end while [标签];

联想:
while(循环条件){
	循环体;
}
*/

-- 没有添加循环控制语句
-- 案例1:批量插入,根据次数插入到admin表中多条记录
delimiter $ #自定义结束标记符
CREATE PROCEDURE pro_while1(IN `insertCount` INT)
BEGIN
DECLARE i INT DEFAULT 1; #定义局部变量,并且有默认值1

WHILE i<=`insertCount` DO
INSERT INTO admin(username,`password`) VALUES (CONCAT('Rose',i),'666');
SET i=i+1;
END WHILE;

END $

-- 调用
CALL pro_while1(100)$

SELECT * FROM admin$

-- 添加leave语句
-- 案例2:批量插入,根据次数插入到admin表中多条记录,如果次数大于20则停止
TRUNCATE TABLE admin$ #删除admin表中的全部数据

delimiter $ #自定义结束标记符
CREATE PROCEDURE pro_while2(IN `insertCount` INT)
BEGIN
DECLARE i INT DEFAULT 1; #定义局部变量,并且有默认值1

a:WHILE i<=`insertCount` DO
INSERT INTO admin(username,`password`) VALUES (CONCAT('xiaohua',i),'666');
IF i>=20 THEN LEAVE a; #如果i>=20,就结束当前所在的循环,类似于break
END IF;
SET i=i+1;
END WHILE a;

END $

-- 调用
CALL pro_while2(100)$

-- 添加iterate语句
-- 案例:批量插入,根据次数插入到admin表中多条记录,只插入偶数次
TRUNCATE TABLE admin$ #删除admin表中的全部数据

delimiter $ #自定义结束标记符
CREATE PROCEDURE pro_while3(IN `insertCount` INT)
BEGIN
DECLARE i INT DEFAULT 0; #定义局部变量,并且有默认值1

a:WHILE i<=`insertCount` DO
SET i=i+1;
IF MOD(i,2)!=0 THEN ITERATE a; #如果i%2!=0,就结束此次循环,继续执行下一次循环,类似于continue
END IF;

INSERT INTO admin(username,`password`) VALUES (CONCAT('xiaohua',i),'000');

END WHILE a;

END $

-- 调用
CALL pro_while3(100)$

-- 2.2 loop
/*
语法:
[标签:]loop
	循环体;
end loop [标签];

可以用来模拟简单的死循环
*/

-- 2.3 repeat
/*
语法:
[标签:]repeat
	循环体;
until 结束循环的条件
end repeat [标签];
*/

13.1 流程控制结构练习

/*
已知表stringcontent
其中字段:
id 自增长
content varchar(20)

向该表插入指定个数的,随机的字符串
*/

DROP TABLE IF EXISTS stringcontent;

-- 创建表stringcontent
CREATE TABLE stringcontent(
				id INT PRIMARY KEY AUTO_INCREMENT,
				content VARCHAR(20)
);

-- 操作
delimiter $ #自定义结束标记符
CREATE PROCEDURE test_randstr_insert(IN `insertCount` INT)
BEGIN
#定义一个循环变量i,表示插入次数
DECLARE i INT DEFAULT 1; 
#定义一个具有26个字母的字符串
DECLARE str VARCHAR(26) DEFAULT 'abcdefghijklmnopqrstuvwxyz';
#代表初始索引
DECLARE startIndex INT;
#代表截取的字符长度
DECLARE len INT;

WHILE i<=`insertCount` DO
#floor表示向下取整,rand()*26+1表示产生[1,27)之间的随机数
SET startIndex=FLOOR(RAND()*26+1); #代表初始索引,随机范围1-26
#因为content定义varchar(20),所以最多只能截取20个
#代表截取长度,随机范围 1 - (20-startIndex+1)
SET len=FLOOR(RAND()*(20-startIndex+1)+1);

INSERT INTO stringcontent(content) VALUES(SUBSTR(str,startIndex,len));

SET i=i+1; #循环变量更新
END WHILE;

END $

-- 调用
CALL test_randstr_insert(10)$
  • 4
    点赞
  • 25
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

猿小羽

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值