mysql学习笔记

#############################【mysql初级篇学习笔记】#######################################

#############################学校作业二:#####################################

#2.创建数据库[学生选课数据库](不需要代码,按照上次上机中所创建数据库步骤创建);
CREATE TABLE 课程表(课程号 CHAR(4) PRIMARY KEY,课程名 CHAR(40) NOT NULL,先行课程号 CHAR(4),学分 SMALLINT NOT NULL);

#3.创建[课程表]代码;课程表(课程号 char(4),课程名 char(40),先行课程号 char(4),学分 smallint) 
#  要求设置:主键(课程号)、非空(课程名, 学分)
CREATE TABLE 学生表(学号 CHAR(9) PRIMARY KEY,姓名 CHAR(20),性别 CHAR(2),年龄 SMALLINT,专业 CHAR(20) NOT NULL);

#4.创建[学生表]代码;学生表(学号 char(9),姓名char(20),性别char(2),年龄smallint,专业char(20))  
#  要求设置:主键(学号)、非空(专业)
CREATE TABLE 选课表(学号 CHAR(9),课程号 CHAR(4),分数 SMALLINT,
FOREIGN KEY(学号) REFERENCES 学生表(学号),
FOREIGN KEY(课程号) REFERENCES 课程表(课程号));

#5.修改[课程表],将“学分”列的数据类型改为int;
ALTER TABLE 课程表 MODIFY 学分 INT;
#ALTER TABLE 课程表 alter column 学分 char(2);   ==>错误的

#6.修改[学生表],在表中增加一列,属性名为“备注”,数据类型为char(20);
ALTER TABLE 学生表 ADD 备注 CHAR(20);

#7.修改[学生表],对“姓名”列增加唯一约束条件;
ALTER TABLE 学生表 ADD UNIQUE(姓名);

#8.修改[学生表],删除表中新增的“备注”列;
ALTER TABLE 学生表 DROP 备注;

#9.删除[选课表]。
DROP TABLE 选课表;#伪删除



#############################################################################################


#having不可以单独使用,只能和group by一起使用;where 和 having后写过滤条件,建议方式一where,因为执行效率高
#结论:当过滤条件中有聚合函数时,必须写在having;若没有,则having和where均可,但建议使用where(必须)。
SELECT department_id,MAX(salary) FROM employees WHERE department_id IN (10,20,30,40,50) GROUP BY department_id HAVING MAX(salary)>10000; 
SELECT department_id,MAX(salary) FROM employees GROUP BY department_id HAVING MAX(salary)>10000 AND department_id IN (10,30,40,50);

#SQL底层执行原理
SELECT ...,...,...  包含聚合函数
FROM ... (LEFT / RIGHT)JOIN ... ON 多表连接条件
(LEFT / RIGHT)JOIN ... ON ...
WHERE 多表的连接条件 AND 不包含聚合函数的过滤条件
GROUP BY ...,...
HAVING 包含聚合函数的过滤条件
ORDER BY ...,... (ASC,DESC)
LIMIT ...,...

#执行过程
FROM ...,...->ON->(LEFT/RIGHT JOIN)->WHERE->GROUP BY->HAVING->SELECT->DISTINCT->ORDER BY->LIMIT


#1.where子句可否使用组函数进行过滤?
不可以

#2.查询公司员工工资的最大值,最小值,平均值,总和
SELECT MAX(salary) max_sal,MIN(salary) min_sal,AVG(salary) avg_sal,SUM(salary) sum_sal  FROM employees;

#3.查询各job_id的员工工资的最大值,最小值,平均值,总和
SELECT job_id,MIN(salary),MAX(salary),AVG(salary),SUM(salary) FROM employees GROUP BY job_id;

#4.选择具有各个job_id的员工人数
SELECT job_id,COUNT(*) FROM employees GROUP BY job_id;

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

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

# 7.查询所有部门的名字,location_id,员工数量和平均工资,并按平均工资降序
SELECT d.department_name,d.location_id,COUNT(*),AVG(e.salary) 
FROM employees e RIGHT JOIN departments d  
ON e.department_id = d.department_id
GROUP BY d.`department_name`,d.`location_id`
HAVING AVG(salary)IS NOT NULL
ORDER BY AVG(salary);

# 8.查询每个工种、每个部门的部门名、工种名和最低工资
SELECT department_name,job_id,MIN(salary)
FROM departments d JOIN employees e
ON d.`department_id` = e.`department_id`
GROUP BY department_name,job_id;


#子查询
#需求:i准的工资比Abel的高?
#方式一:自连接
SELECT e2.last_name,e2.salary 
FROM employees e1,employees e2
WHERE e2.`salary` > e1.`salary`#多表的连接条件
AND e1.last_name = 'Abel';
#方式二:子查询
SELECT last_name,salary 
FROM employees
WHERE salary > (SELECT salary 
		FROM employees 
		WHERE last_name = 'Abel');
		
#2.称谓的规范:外查询(或者主查询),内查询(或内查询)
#注意事项:a.子查询要包含在括号内;b.将子查询放在比较条件的右侧;c.单行操作符对应单行子查询,多行操作符对应多行子查询
/*子查询的分类:
1.单行子查询【结果一行】 VS 多行子查询【多行结果】;  
2.内查询是否被执行多次:相关子查询  VS 不相关子查询。
*/

#单行子查询    单行操作符:<,>,=,<=,>=,<>不等于符号

/*题目:显式员工的employee_id,last_name和location.
其中,若员工department id与location_id为1800的department_id相同,
则location为' Canada',其余则为'UsA’。*/
SELECT employee_id,last_name,(CASE department_id WHEN (SELECT department_id FROM departments WHERE location_id=1800)
						 THEN 'Canada' 
						 ELSE 'USA' 
						 END) "location"
FROM employees;

#多行子查询    多行操作符:in,【any,all,some(同any)  与单行操作符混合使用】
SELECT employee_id,last_name
FROM employees
WHERE salary IN (SELECT MIN(salary)FROM employees GROUP BY department_id);


#any和all的区别
/*题目:返回其它job_id中比job_id为'IT_PROG’部门任一工资低的员工的员工号、
       姓名、job_id以及salary
*/
SELECT employee_id,last_name,job_id,salary 
FROM employees 
WHERE salary < ANY(SELECT salary FROM employees WHERE job_id='IT_PROG')
AND job_id <> 'IT_PROG';


SELECT employee_id,last_name,job_id,salary 
FROM employees 
WHERE salary < ALL(SELECT salary FROM employees WHERE job_id='IT_PROG')
AND job_id <> 'IT_PROG';

#【难】题目:查询平均工资最低的部门id【注意:mysql中聚合函数不可以嵌套,其他的也许可以,比如oracle】
SELECT MIN(avg_sal)
FROM (SELECT AVG(salary) avg_sal FROM employees GROUP BY department_id)t_dept_avg_sal

SELECT department_id
FROM employees
GROUP BY department_id
HAVING AVG(salary)=(SELECT MIN(avg_sal)FROM (SELECT AVG(salary) avg_sal FROM employees GROUP BY department_id)t_dept_avg_sal);


#相关子查询
#题目:查询员工中工资大于本部门平均工资的员工的last_name,salary和其department_id
SELECT last_name,salary,department_id
FROM employees e1
WHERE salary > (SELECT AVG(salary)FROM employees e2 WHERE e2.`department_id`=e1.`department_id`);

#题目:查询员工的id,salary,按照department_name排序【难】
SELECT employee_id,salary
FROM employees e
ORDER BY (
	SELECT department_name
	FROM departments d
	WHERE e.`department_id`=d.`department_id`
	) ASC;
	

#结论:在select中,除了group by和limit之外,其他位置都可以声明子查询

/*【重要】
题目:若employees表中employee_id与job_history表中employee_id相同的数目不小于2,
     输出这些相同id的员工的employee_id,last_name和其job_id
*/	
SELECT employee_id,last_name,job_id 
FROM employees e
WHERE 2<=(SELECT COUNT(*) FROM job_history j WHERE e.`employee_id` = j.`employee_id`);

#exists和not exists关键字

#题目:查询公司管理者的employee_id,last_name,job_id,department_id信息
#方式一:自连接
SELECT DISTINCT mgr.employee_id,mgr.last_name,mgr.job_id,mgr.department_id
FROM employees emp JOIN employees mgr
ON emp.manager_id = mgr.employee_id;
#方式二:子查询
SELECT employee_id,last_name,job_id,department_id
FROM employees
WHERE employee_id IN(SELECT DISTINCT manager_id FROM employees);
#方式三:使用exists
SELECT employee_id,last_name,job_id,department_id
FROM employees e1
WHERE EXISTS(SELECT * FROM employees e2 WHERE e1.`employee_id`=e2.`manager_id`);


#1.查询和Zlotkey相同部门的员工姓名和工资
SELECT last_name,salary,department_id
FROM employees e1
WHERE e1.`department_id` = (SELECT e2.department_id FROM employees e2 WHERE e2.`last_name`="Zlotkey")
AND e1.`last_name` <> "Zlotkey"

#2.查询工资比公司平均工资高的员工的员工号,姓名和工资。
SELECT employee_id,last_name,salary 
FROM employees
WHERE salary > (SELECT AVG(salary) FROM employees);

#3.选择工资大于所有JOB_ID = 'SA_MAN'的员工的工资的员工的last_name, job_id, salary
SELECT last_name,job_id,salary
FROM employees
WHERE salary > ALL(SELECT salary FROM employees WHERE job_id="SA_MAN");

#4.查询和姓名中包含字母u的员工在相同部门的员工的员工号和姓名
SELECT employee_id,last_name
FROM employees
WHERE department_id = ANY (SELECT department_id FROM employees WHERE last_name LIKE "%u%");

SELECT employee_id,last_name
FROM employees
WHERE department_id IN (SELECT department_id FROM employees WHERE last_name LIKE "%u%");

#5.查询在部门的location_id为1700的部门工作的员工的员工号
SELECT employee_id
FROM employees
WHERE department_id = SOME (SELECT department_id FROM departments WHERE location_id = 1700);

#6.查询管理者是King的员工姓名和工资
SELECT e1.last_name,e1.salary,e1.`manager_id`
FROM employees e1,employees e2
WHERE e1.`manager_id` = e2.`employee_id`
AND e2.`last_name` = "King";

SELECT last_name,salary
FROM employees
WHERE manager_id IN (SELECT employee_id FROM employees WHERE last_name = "King");

#7.查询工资最低的员工信息: last_name, salary
SELECT last_name,salary
FROM employees
WHERE salary =(SELECT MIN(salary) FROM employees);

#8.查询平均工资最低的部门信息
SELECT * 
FROM departments
WHERE department_id = (SELECT department_id
		       FROM employees
		       GROUP BY department_id
		       HAVING AVG(salary)=(SELECT MIN(dept_avgsal) 
					   FROM (SELECT AVG(salary)dept_avgsal 
						 FROM employees 
						 GROUP BY department_id
						 )
						 avg_sal
					   )
			);
#【牛掰】
SELECT d.*
FROM departments d,(SELECT department_id,AVG(salary) avg_sal
		    FROM employees
		    GROUP BY department_id
		    ORDER BY avg_sal ASC
		    LIMIT 0,1
		   )t_dept_avg_sal
WHERE d.`department_id`=t_dept_avg_sal.`department_id`  

#9.查询平均工资最低的部门信息和该部门的平均工资(相关子查询)
SELECT d.*,t_dept_avg_sal.`avg_sal`
FROM departments d,(SELECT department_id,AVG(salary) avg_sal
		    FROM employees
		    GROUP BY department_id
		    ORDER BY avg_sal ASC
		    LIMIT 0,1
		   )t_dept_avg_sal
WHERE d.`department_id`=t_dept_avg_sal.`department_id`;

#10.查询平均工资最高的 job 信息
#【方法一】排序取最大
SELECT j.*
FROM jobs j,(SELECT job_id,AVG(salary) avg_sal
	     FROM employees
	     GROUP BY job_id
	     ORDER BY avg_sal DESC
	     LIMIT 0,1
	    )t_max_avg_sal
WHERE j.`job_id`=t_max_avg_sal.`job_id`;
#【方法二】一个一个比较找最大
SELECT *
FROM jobs
WHERE job_id=(
		SELECT job_id
		FROM employees
		GROUP BY job_id
		HAVING AVG(salary)=(SELECT MAX(avg_sal)
				    FROM (SELECT AVG(salary) avg_sal
					  FROM employees
					  GROUP BY job_id
					  )t_job_avg_sal
				   )
	     );
#【方法三】>=all
SELECT *
FROM jobs
WHERE job_id=(
		SELECT job_id
		FROM employees
		GROUP BY job_id
		HAVING AVG(salary)>= ALL(SELECT AVG(salary) avg_sal
					  FROM employees
					  GROUP BY job_id
					)
	     );


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

SELECT department_id
FROM (SELECT department_id,AVG(salary)avg_sal FROM employees WHERE department_id IS NOT NULL GROUP BY department_id)t_dep_avg_sal
WHERE t_dep_avg_sal.`avg_sal` > (SELECT AVG(salary)FROM employees);


SELECT department_id
FROM employees
WHERE department_id IS NOT NULL
GROUP BY department_id
HAVING AVG(salary) > (
			SELECT AVG(salary)
			FROM employees
		     );

#12. 查询出公司中所有 manager 的详细信息
SELECT DISTINCT e2.employee_id, e2.last_name, e2.salary
FROM employees e1,employees e2
WHERE e1.`manager_id` = e2.`employee_id`;

SELECT employee_id,last_name,salary
FROM employees
WHERE employee_id IN (SELECT  DISTINCT manager_id FROM employees WHERE manager_id IS NOT NULL);

SELECT employee_id, last_name, salary
FROM employees e1
WHERE EXISTS ( SELECT *
		FROM employees e2
		WHERE e2.manager_id = e1.employee_id);
		
		
#13. 各个部门中,最高工资中最低的那个部门的最低工资是多少?
SELECT MIN(salary)
FROM employees
WHERE department_id=(
			SELECT department_id
			FROM employees
			GROUP BY department_id
			HAVING MAX(salary)=(
						SELECT MIN(max_sal)
						FROM (
							SELECT MAX(salary) max_sal
							FROM employees
							GROUP BY department_id
							)t_dept_max_sal
						)
			);

SELECT MIN(salary)
FROM employees
WHERE department_id = (
			SELECT department_id
			FROM employees
			GROUP BY department_id
			HAVING MAX(salary) <= ALL(
						SELECT MAX(salary) max_sal
						FROM employees
						GROUP BY department_id
						)
			);

#14. 查询平均工资最高的部门的 manager 的详细信息: last_name,department_id, email, salary
SELECT last_name,department_id,email,salary
FROM employees
WHERE employee_id = (
			SELECT manager_id
			FROM departments d,(
						SELECT department_id,AVG(salary) avg_sal
						FROM employees
						GROUP BY department_id
						ORDER BY avg_sal DESC
						LIMIT 0,1
					      ) t_emp_avg_sal
			WHERE d.`department_id` = t_emp_avg_sal.`department_id`
		   );


#15. 查询部门的部门号,其中不包括job_id是"ST_CLERK"的部门号
SELECT department_id
FROM departments
WHERE department_id NOT IN (
				SELECT DISTINCT department_id
				FROM employees
				WHERE job_id = 'ST_CLERK'
			   );
#方法二:
SELECT department_id
FROM departments d
WHERE NOT EXISTS (
			SELECT *
			FROM employees e
			WHERE d.`department_id` = e.`department_id`
			AND job_id = 'ST_CLERK'
		);

#16. 选择所有没有管理者的员工的last_name
SELECT last_name,manager_id
FROM employees
WHERE manager_id IS NULL;

SELECT last_name
FROM employees e1
WHERE NOT EXISTS (
		SELECT *
		FROM employees e2
		WHERE e1.manager_id = e2.employee_id
		);

#17.查询员工号、姓名、雇用时间、工资,其中员工的管理者为 'De Haan'
SELECT employee_id,last_name,hire_date,salary
FROM employees
WHERE manager_id = (
			SELECT employee_id
			FROM employees
			WHERE last_name = "De Haan"
		    );

SELECT employee_id,last_name,hire_date,salary
FROM employees
GROUP BY manager_id
HAVING manager_id = (
			SELECT employee_id
			FROM employees
			WHERE last_name = "De Haan"
		    );
		    
#18.查询各部门中工资比本部门平均工资高的员工的员工号, 姓名和工资(难)




#19.查询每个部门下的部门人数大于 5 的部门名称
SELECT department_name,department_id
FROM departments d
WHERE 5 < (
		SELECT COUNT(*)
		FROM employees e
		WHERE d.`department_id` = e.`department_id`
	   )


#20.查询每个国家下的部门个数大于 2 的国家编号
SELECT country_id
FROM locations l
WHERE 2<(
	SELECT COUNT(*)
	FROM departments d
	WHERE l.`location_id`=d.`location_id`
	);


#####################################1.数据库的操作#################################

#1.数据库的操作

#1.1创建数据库
#方式一:使用的是默认的字符集
CREATE DATABASE dbtest4;

#方式二:显示的指明了要创建的数据库的字符集
CREATE DATABASE dbtest3 CHARACTER SET 'gbk';
SHOW CREATE DATABASE dbtest3;

#方式三
CREATE DATABASE IF NOT EXISTS dbtest5 CHARACTER SET 'utf8';


#1.2 管理数据库
#查看所有数据库
SHOW DATABASES;	
#切换数据库
USE atguigudb;
#查看当前数据库中的表
SHOW TABLES;
#查看当前使用的数据库
SELECT DATABASE() FROM DUAL;
#查看指定数据库下的表
SHOW TABLES FROM atguigudb;

#1.3修改数据库【不要修改】
SHOW CREATE DATABASE dbtest4;
#修改字符集
ALTER DATABASE dbtest4 CHARACTER SET 'gbk';
#【注意】DATABASE 不能改名。一些可视化工具可以改名,它是建新库,
#把所有表复制到新库,再删旧库。

#1.4删除数据库
#方式一
DROP DATABASE dbtest4;
#方式二:安全  [建议]
DROP DATABASE IF EXISTS dbtest4;

#####################################2.创建表#################################

#2.创建数据表
USE atguigudb;
SHOW CREATE DATABASE atguigudb;
#方式一:白手起家
CREATE TABLE IF NOT EXISTS myemp1(#需要用户具备创建表的权限
	id INT,
	emp_name VARCHAR(15),
	hire_date DATE
);
#查看表结构
DESC myemp1;
#查看创建表语句的结构
SHOW CREATE TABLE myemp1;#如果创建表时没有指定使用的字符集,则默认使用表所在的数据库字符集
SELECT * FROM myemp1;
#方式二:基于现有的表创建新表(属性+数据 全部copy)
CREATE TABLE myemp2 AS (SELECT employee_id,last_name,salary FROM employees);
DESC myemp2;
SHOW CREATE TABLE myemp2;
SELECT * FROM myemp2;
#说明1:查询语句中字段的别名,可以作为新创建的表的字段的名称。
#说明2:此时的查询语句可以结构比较丰富,使用前面章节讲过的各种SELECT。


#练习:创建一个表employees_blank,实现对employees表的复制,不包括表数据
CREATE TABLE employees_blank
AS
SELECT * 
FROM employees 
ORDER BY employee_id
LIMIT 0;

CREATE TABLE IF NOT EXISTS employees_blank
AS
SELECT * 
FROM employees
WHERE FALSE;

SELECT * FROM employees_blank;

USE atguigudb;

#####################################3.修改表#################################
#3.修改表  -> alter table
DESC myemp1;


#3.1 添加一个字段   默认添加到表中的最后一个字段的位置
#add first after
ALTER TABLE myemp1
ADD salary DOUBLE(10,2);#共10位,小数点后2位

ALTER TABLE myemp1
ADD phone_number VARCHAR(20) FIRST;#添加到第一个位置

ALTER TABLE myemp1
ADD email VARCHAR(45) AFTER emp_name;#指定位置

DESC myemp1;

#3.2修改一个字段:数据类型,长度,默认值  modify  default
ALTER TABLE myemp1
MODIFY emp_name VARCHAR(25);

ALTER TABLE myemp1
MODIFY emp_name VARCHAR(30) DEFAULT 'aaa';#默认值   前面必须有 varchar

#3.3重命名一个字段  change 
ALTER TABLE myemp1
CHANGE salary monthly_salary DOUBLE(10,2);

ALTER TABLE myemp1
CHANGE email my_email INT;#修改名字的同时,也可以修改数据类型、长度

#3.4删除一个字段
ALTER TABLE myemp1
DROP COLUMN my_email;

DESC myemp1;

#####################################4.重命名表#################################
#方式一:rename...to【建议】
RENAME TABLE myemp1
TO myemp11;
#方式二:alter...rename to
ALTER TABLE myemp2
RENAME TO myemp1;

#####################################5.删除表#################################
#将表结构删除掉,同时表中的数据也删除表,释放表空间
CREATE TABLE myemp2(id CHAR(10),my_name VARCHAR(20));
DROP TABLE IF EXISTS myemp2;

#####################################6.清除表#################################
#清空表,表示清空表中的所有数据,但是表结构保留。truncate
TRUNCATE TABLE employees_copy;

CREATE TABLE employees_copy AS SELECT * FROM employees;

SELECT * FROM employees_copy;

DESC employees_copy;


#############################7.DCL中的commit和rollback#################################
#COMMIT:提交数据。一旦执行CONMIT,则数据就被永久的保存在了数据库中,意味着数据不可以回滚(撤销)。
#ROLLBACK:回滚数据。一旦执行ROLLBACK,则可以实现数据的回滚。回滚到最近的一次COMMIT之后。

#8.对比TRUNCATE TABLE和DELETE FROM
#相同点:都可以实现对表中所有数据的删除,同时保留表结构。
#不同点:
#	TRUNCATE TABLE:一旦执行此操作,表数据全部清除。同时,数据是不可以回滚的。
#	DELETE FROM:一旦执行此操作”表数据可以全部清除。同时,数据是可以回滚的


#9.DDL和DML的说明
#	①DDL的操作一旦执行,就不可回滚。指令SET autocommit = FALs山x寸DDL操作失效。
#	②DML的操作默认情况,一旦执行,也是不可回滚的。但是,如果在执行DNL之前,执行了
#		SET autocommit = FALSE,则执行的DML操作就可以实现回滚。


COMMIT;
SELECT * FROM myemp2;
SET autocommit = FALSE;
DELETE FROM myemp2;
SELECT * FROM myemp2;
ROLLBACK;
SELECT * FROM myemp2;

###################################创建和管理表【练习题一】####################################
#1. 创建数据库test01_office,指明字符集为utf8。并在此数据库下执行下述操作
CREATE DATABASE IF NOT EXISTS test01_office CHARACTER SET 'utf8';
USE test01_office;

#2. 创建表dept01
CREATE TABLE dept01(
	id INT(7),
	NAME VARCHAR(25)
);

#3. 将表departments中的数据插入新表dept02中
CREATE TABLE dept02
AS
SELECT * FROM atguigudb.departments;

#4. 创建表emp01
CREATE TABLE emp01(
	id INT(7),
	first_name VARCHAR(25),
	last_name VARCHAR(25),
	dept_id INT(7)
);

#5. 将列last_name的长度增加到50
DESC emp01;
ALTER TABLE emp01
MODIFY last_name VARCHAR(50);

#6. 根据表employees创建emp02
CREATE TABLE emp02
AS
SELECT * FROM atguigudb.employees;

#7. 删除表emp01
DROP TABLE IF EXISTS emp01;

#8. 将表emp02重命名为emp01
ALTER TABLE emp02
RENAME TO emp01;

RENAME TABLE emp01 TO emp02;

#9. 在表dept02和emp01中添加新列test_column,并检查所作的操作
ALTER TABLE dept02 ADD test_column VARCHAR(10);
DESC dept02;

ALTER TABLE emp01 ADD test_column VARCHAR(10);
DESC emp01;

#10.直接删除表emp01中的列 department_id
ALTER TABLE emp01
DROP COLUMN department_id;


###################################创建和管理表【练习题二】####################################

# 11、创建数据库 test02_market
CREATE DATABASE test02_market;
USE test02_market;

# 12、创建数据表 customers
CREATE TABLE customers(
	c_num INT,
	c_name VARCHAR(50),
	c_contact VARCHAR(50),
	c_city VARCHAR(50),
	c_birth DATE
);

# 13、将 c_contact 字段移动到 c_birth 字段后面【注意此题】
ALTER TABLE customers MODIFY c_contact VARCHAR(50) AFTER c_birth;#必须加上VARCHAR(50) 

# 14、将c_name字段数据类型改为 varchar(70)
ALTER TABLE customers MODIFY c_name VARCHAR(70);

# 15、将c_contact字段改名为c_phone【注意此题】
ALTER TABLE customers CHANGE c_contact c_phone VARCHAR(50);

# 16、增加c_gender字段到c_name后面,数据类型为char(1)
ALTER TABLE customers ADD c_gender CHAR(1) AFTER c_name;

# 17、将表名改为customers_info
RENAME TABLE customers TO customers_info;

# 18、删除字段c_city
ALTER TABLE customers_info DROP COLUMN c_city;

###################################创建和管理表【练习题三】####################################
# 1、创建数据库test03_company
CREATE DATABASE test03_company;
USE test03_company;

# 2、创建表offices
CREATE TABLE offices(
	officeCode INT,
	city VARCHAR(30),
	address VARCHAR(50),
	country VARCHAR(50),
	postalCode VARCHAR(25)
);
DESC offices;

# 3、创建表employees
CREATE TABLE employees(
	empNum INT,
	lastName VARCHAR(50),
	firstName VARCHAR(50),
	mobile VARCHAR(25),
	CODE INT,
	jobTitle VARCHAR(50),
	birth DATE,
	note VARCHAR(255),
	sex VARCHAR(5)
);
DESC employees;

# 4、将表employees的mobile字段修改到code字段后面
ALTER TABLE employees MODIFY mobile VARCHAR(25) AFTER CODE;

# 5、将表employees的birth字段改名为birthday   【重要:change改名】
ALTER TABLE employees CHANGE birth birthday DATE;

# 6、修改sex字段,数据类型为char(1)
ALTER TABLE employees MODIFY sex CHAR(1);

# 7、删除字段note
ALTER TABLE employees DROP COLUMN note;

# 8、增加字段名favoriate_activity,数据类型为varchar(100)
ALTER TABLE employees ADD favoriate_activity VARCHAR(100);
DESC employees;

# 9、将表employees的名称修改为 employees_info
RENAME TABLE employees TO employees_info;


###################################数据处理之增删改###########################################
#0.筹备工作
USE atguigudb;
CREATE TABLE IF NOT EXISTS emp1(
	id INT,
	`name` VARCHAR(15),
	hire_date DATE,
	salary DOUBLE(10,2)
);
DESC emp1;
SELECT * FROM emp1;

#1.插入数据 insert into ... values (),()...
#方式一:一条一条的
#①没有指明添加的字段,必须严格按照声明的字段的先后顺序
INSERT INTO emp1 VALUES(1,'Tom','2002-12-21',3400);#value也可以
INSERT INTO emp1 VALUES(2,'Jerry','2001-10-01',3500);

#②指明要添加的字段【推荐】
INSERT INTO emp1(id,hire_date,salary,`NAME`) VALUES(3,'1999-02-17',800,'Jack');

#没有进行赋值的值为NULL
INSERT INTO emp1(id,salary,`NAME`) VALUES(4,800,'ZMJ');#可以不写全,NULL

#③同时插入多条记录,逗号隔开【推荐】
INSERT INTO emp1(id,hire_date,salary,`NAME`)
VALUES (5,'2001-09-19',900,'李玟琪'),(6,'2003-10-20',1002,'张梦姣');


#方式二:将查询结果插入表中
SELECT * FROM emp1;
INSERT INTO emp1(id,NAME,salary,hire_date)
SELECT employee_id,last_name,salary,hire_date FROM employees WHERE department_id IN (60,70);
SELECT * FROM emp1;
#说明: empl表中要添加数据的字段的长度不能低于employees表中查询的字段的长度。



#2.更新数据(修改数据)
#update ... set ... where ...  可以实现批量修改
UPDATE emp1 
SET hire_date=CURDATE()
WHERE id=4;#没有where,则全部修改
SELECT * FROM emp1;

#同时修改一条数据的多个字段:逗号隔开即可
UPDATE emp1
SET hire_date=CURDATE(),salary=10000
WHERE id=4;


#3.删除数据 delete from ... where ... 
DELETE FROM emp1
WHERE id=1;


#小结:DML操作默认情况下,执行完以后都会自动提交数据。
#如果希望执行完以后不自动提交数据,则需要使用SET autocommit = FALSE.


#4.MySQL8新特性:计算列
#计算列:简单来说就是某一列的值是通过别的列计算得来的。
CREATE TABLE test1(
	a INT,
	b INT,
	c INT GENERATED ALWAYS AS (a+b)VIRTUAL
);

INSERT INTO test1(a,b)#必须指定a b
VALUES (1,2),(10,20),(100,200);

UPDATE test1
SET a=1000,b=2000
WHERE a=1 AND b=2;

SELECT * FROM test1;


###################################增删改【练习题一】###################################
#1. 创建数据库dbtest11
CREATE DATABASE IF NOT EXISTS dbtest11 CHARACTER SET 'utf8';

#2. 运行以下脚本创建表my_employees
USE dbtest11;
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
);
#3. 显示表my_employees的结构
DESC my_employees;
SELECT * FROM my_employees;
#4. 向my_employees表中插入下列数据
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);
	
SELECT * FROM my_employees;

#5. 向users表中插入数据
INSERT INTO users 
VALUES  (1,'Rpatel',10),
	(2,'Bdancs',10),
	(3,'Bbiri',20),
	(4,'Cnewman',30),
	(5,'Aropebur',40);

SELECT * FROM users;

#6. 将3号员工的last_name修改为“drelxer”
UPDATE my_employees
SET last_name='drelxer'
WHERE id=3;

#7. 将所有工资少于900的员工的工资修改为1000
UPDATE my_employees
SET salary=1000
WHERE salary<900;

#8. 将userid为Bbiri的user表和my_employees表的记录全部删除【注意此题】
DELETE u,e
FROM users u JOIN my_employees e
ON u.`userid` = e.`userid`
WHERE u.`userid` = 'Bbiri';

#9. 删除my_employees、users表所有数据
DELETE FROM users;
DELETE FROM my_employees;

#10. 检查所作的修正
SELECT * FROM users;
SELECT * FROM my_employees;

#11. 清空表my_employees
TRUNCATE TABLE my_employees;

###################################增删改【练习题二】###################################
# 1. 使用现有数据库dbtest11
USE dbtest11;

# 2. 创建表格pet
CREATE TABLE pet(
	`name` VARCHAR(20),
	`owner` VARCHAR(20),
	species VARCHAR(20),
	sex CHAR(1),
	birth YEAR,
	death YEAR
);

# 3. 添加记录
INSERT INTO pet VALUES('Fluffy','harold','Cat','f','2013','2010');
INSERT INTO pet(`name`,`owner`,species,sex,Birth)
VALUES('Claws','gwen','Cat','m','2014');
INSERT INTO pet(`name`,species,sex,Birth) VALUES('Buffy','Dog','f','2009');
INSERT INTO pet(`name`,`owner`,species,sex,Birth)
VALUES('Fang','benny','Dog','m','2000');
INSERT INTO pet VALUES('bowser','diane','Dog','m','2003','2009');
INSERT INTO pet(`name`,species,sex,birth) VALUES('Chirpy','Bird','f','2008');

SELECT * FROM pet;

# 4. 添加字段:主人的生日owner_birth DATE类型
ALTER TABLE pet 
ADD COLUMN owner_birth DATE;

# 5. 将名称为Claws的猫的主人改为kevin
UPDATE pet
SET `owner`='kevin'
WHERE NAME='Claws'
AND species='Cat';

# 6. 将没有死的狗的主人改为duck
UPDATE pet
SET `owner`='duck'
WHERE death IS NULL AND species = 'Dog';

# 7. 查询没有主人的宠物的名字
SELECT `name`
FROM pet
WHERE `owner` IS NULL;

# 8. 查询已经死了的cat的姓名,主人,以及去世时间
SELECT `name`,`owner`,death
FROM pet
WHERE species='Cat' AND death IS NOT NULL;

# 9. 删除已经死亡的狗
DELETE FROM pet
WHERE species = 'Dog' AND death IS NOT NULL;

# 10. 查询所有宠物信息
SELECT * FROM pet;


###################################增删改【练习题三】###################################
# 1. 使用已有的数据库dbtest11
USE dbtest11;

# 2. 创建表employee,并添加记录
CREATE TABLE employee(
		id INT,
		`name` VARCHAR(20),
		sex VARCHAR(2),
		tel VARCHAR(20),
		addr VARCHAR(50),
		salary DOUBLE
		);
		
INSERT INTO employee(id,`name`,sex,tel,addr,salary)
VALUES
	(10001,'张一一','男','13456789000','山东青岛',1001.58),
	(10002,'刘小红','女','13454319000','河北保定',1201.21),
	(10003,'李四','男','0751-1234567','广东佛山',1004.11),
	(10004,'刘小强','男','0755-5555555','广东深圳',1501.23),
	(10005,'王艳','男','020-1232133','广东广州',1405.16);
	
# 3. 查询出薪资在1200~1300之间的员工信息`dbtest11`
SELECT * FROM employee WHERE salary>=1200 AND salary<=1300;

# 4. 查询出姓“刘”的员工的工号,姓名,家庭住址
SELECT id,`name`,addr
FROM employee
WHERE `name` LIKE '刘%';

# 5. 将“李四”的家庭住址改为“广东韶关”
UPDATE employee
SET addr='广东韶关'
WHERE `name`='李四';

# 6. 查询出名字中带“小”的员工
SELECT `name`
FROM employee
WHERE `name` LIKE '%小%';


######################################数据类型精讲#####################################
#关于属性:character set name
#【1】创建数据库时指明字符集类型
CREATE DATABASE IF NOT EXISTS dbtest12 CHARACTER SET 'utf8';
#【2】创建表时候,指定
CREATE TABLE temp(
	id INT,
	NAME VARCHAR(10)
)CHARACTER SET 'utf8';
#【3】创建表时,指表中的字段时,可以指定
CREATE TABLE temp1(
	id INT,
	NAME VARCHAR(10) CHARACTER SET 'gbk'
);
SHOW CREATE TABLE temp1;


#【一、整数类型】tinyint 1字节、smallint 2字节、mediumint 3字节、int (integer) 4字节和bigint 8字节.
USE dbtest12;

CREATE TABLE test_int1(
	f1 TINYINT,
	f2 SMALLINT,
	f3 MEDIUMINT,
	f4 INT,
	f5 BIGINT
);
DESC test_int1;

INSERT INTO test_int1(f1)
VALUES(127),(-128),(0),(1000);#超出范围报错

SELECT * FROM test_int1;

#指定显示宽度 配合zerofill使用
CREATE TABLE test_int2(
	f1 INT,
	f2 INT(5),#括号中的数字表示显示宽度,不改变本身数值,只是显示
	f3 INT(5) ZEROFILL#不足5位,用0向前填充
);
INSERT INTO test_int2(f1,f2)
VALUES (123,123),(123456,123456);

SELECT * FROM test_int2;

INSERT INTO test_int2(f3)
VALUES (123),(123456);

#unsigned
CREATE TABLE test_int3(
	f1 INT UNSIGNED
);
DESC test_int3;
INSERT INTO test_int3(f1)
VALUES (21),(100);
SELECT * FROM test_int3;

/*【整数类型使用场景】
TINYINT:一般用于枚举数据,比如系统设定取值范围很小且固定的场景。
SMALLINT:可以用于较小范围的统计数据,比如统计工厂的固定资产库存数量等。
MEDIUMINT:用于较大整数的计算,比如车站每日的客流量等。
INT、INTEGER︰取值范围足够大,一般情况下不用考虑超限问题,用得最多。比如商品编号。
BIGINT:只有当你处理特别巨大的整数时才会用到。比如双十一的交易量、大型门户网站点击量、主E一生产品持仓等。
*/


#【二、浮点类型】
#float 4字节,double 8字节,real(real默认是double类型)
#小数部分超出,则四舍五入; 整数部分超出,报错
CREATE TABLE test_double1(
	f1 FLOAT,
	f2 FLOAT(5,2),
	f3 DOUBLE,
	f4 DOUBLE(5,2)
);
DESC test_double1;
SELECT * FROM test_double1;
INSERT INTO test_double1(f1,f2)
VALUES(123.45,123.45);
INSERT INTO test_double1(f2)
VALUES(133.456);#整数部分超出,报错


INSERT INTO test_double1(f3,f4)
VALUES(123.45,123.456);#小数部分超出,则四舍五入
INSERT INTO test_double1(f4)
VALUES(1233.456);#整数部分超出,报错


#【三、定点数类型】
#decimal(M,D) ==>存储空间M+2字节,   dec   numeric
#定点数在MySQL内部是以字符串的形式进行存储,这就决定了它一定是精准的。
#默认decimal(10,0)


#【四、位类型bit】
#存储形式:二进制  bit(M)长度M,约为(M+7)/8字节
CREATE TABLE test_bit1(
	f1 BIT,
	f2 BIT(3),
	f3 BIT(64)
);
DESC test_bit1;
SELECT * FROM test_bit1;
INSERT INTO test_bit1(f1,f2,f3)
VALUES (1,5,255);#超出指定位数表示的数据范围,报错

#指定进制显示 oct bin hex
SELECT BIN(f1),BIN(f2),BIN(f3),HEX(f3),OCT(f3)
FROM test_bit1;

#显示十进制  +0即可
SELECT f1+0,f2+0
FROM test_bit1;

#【五、日期与时间类型】
#year 年 1个字节,time 时间 3,date 日期 3,datetime 日期时间 8,timestamp 日期时间4.
#year YYYY或YY;    time HH:MM:SS;    date YYYY-MM-DD

#【5.1 year类型】默认就是year(4),不推荐使用year(2)   可以字符串【建议】,也可以数字
CREATE TABLE test_year(
	f1 YEAR,
	f2 YEAR(4)
);
INSERT INTO test_year(f1,f2)
VALUES(1921,'2078'),('1999',2018);

SELECT * FROM test_year;

#【5.2 date类型】 显示格式:"YYYY-MM-DD",插入格式:"YYYY-MM-DD"或者"YYYYMMDD"   current_date()或now()返回当前系统时间
CREATE TABLE test_date(
	f1 DATE
);
INSERT INTO test_date(f1)
VALUES ("2020-07-11"),(20200711),("20200711");#2020-07-11插入错误
SELECT * FROM test_date;

INSERT INTO test_date(f1)
VALUES (CURRENT_DATE());

#【5.3 time类型】 3个字节    标准格式:"HH:MM:SS"或者"HHMMSS"或HHMMSS
#格式:"D HH:MM:SS" "HH:MM:SS"  "HH:MM"  "D HH:MM"  "D HH"  "SS" ==>D表示天,转换成小时D*24+HH
CREATE TABLE test_time(
	f1 TIME
);
SELECT * FROM test_time;

INSERT INTO test_time(f1)
VALUES ("12:20:19"),("122019");

INSERT INTO test_time(f1)
VALUES ("1 12:20:00"),("1 12:20"),("20");#最后一个是秒数,前两个一样

INSERT INTO test_time(f1)
VALUES (CURRENT_TIME());

INSERT INTO test_time(f1)
VALUES (NOW());#截取时间部分

SELECT CURRENT_DATE(),CURRENT_TIME(),NOW() FROM DUAL;

#【5.4 datetime类型】   ==>  【常用】
#"YYYY-MM-DD HH:MM:SS"  "YYYYMMDDHHMMSS"
CREATE TABLE test_datatime(
	f1 DATETIME
);
SELECT * FROM test_datatime;

INSERT INTO test_datatime(f1)
VALUES ("2020-10-21 12:12:12"),("20201021121212");#一模一样

INSERT INTO test_datatime(f1)
VALUES (CURRENT_TIMESTAMP()),(NOW()),(SYSDATE());


#【5.5 timestamp类型】
#格式:"YYYY-MM-DD HH:MM:SS"或者"YYYY@MM@DD@HH@MM@SS"
#UTC表示世界统一时间,也叫作世界标准时间。
/*存储数据的时候需要对当前时间所在的时区进行转换,查询数据的时候再将时间转换回当前的时区
因此,使用TIMESTAMP存储的同一个时间值,在不同的时区查询时会显示不同的时间。*/

CREATE TABLE test_timestamp(
	f1 TIMESTAMP
);
SELECT * FROM test_timestamp;

INSERT INTO test_timestamp(f1)
VALUES ("2020-10-21 12:12:12"),("2020@10@21@12@12@12");

INSERT INTO test_timestamp(f1)
VALUES (NOW()),(CURRENT_TIMESTAMP());



#【六、文本字符串类型】
#char(M)  varchar(M)  tinytext  text  mediumtext   longtext   enum   set

#【6.1 char或char(M)】: 不指定时,只能存储一位字符或一个汉字
#如果保存时,数据的实际长度比CHAR类型声明的长度小,则会在右侧填充空格以达到指定的长度。
#检索CHAR类型的数据时,CHAR类型的字段会去除尾部的空格。

CREATE TABLE test_char(
	f1 CHAR,
	f2 CHAR(5)
);
SELECT * FROM test_char;

INSERT INTO test_char(f1,f2)
VALUES ('a','你好帅呀朋'),('好','asfgg');

INSERT INTO test_char(f2)
VALUES ('abc  '),('abcde');

SELECT CONCAT(f2,f2) FROM test_char;#自动去掉末尾的空格

#【6.2 varchar(M)】必须指定M

#CHAR(M)     固定长度 浪费存储空间 效率高 存储不大,速度要求高
#VARCHAR(D)  可变长度 节省存储空间 效率低 非CHAR的情况


#【6.3 text类型】存多少就是多少,不像char会自动删去空格
CREATE TABLE test_text(f1 TEXT);
SELECT * FROM test_text;
INSERT INTO test_text(f1)VALUES('abc'),('abc   ');
SELECT CONCAT(f1,f1) FROM test_text;#不删除末尾空格,是什么就是什么


#【6.4 enmu类型】单选   只能从枚举值中选一个,忽略大小写
#索引从1开始,添加时,可以数字,也可以字符
#没有限制非空时候,可以添加null,但不可以0
CREATE TABLE test_enum(
	season ENUM('春','夏','秋','冬','unknown')
);
SELECT * FROM test_enum;

INSERT INTO test_enum(season) VALUES('春'),('秋'),('unknown');

INSERT INTO test_enum(season) VALUES('UNKNOWN'),('UnKnown');#忽略大小写

INSERT INTO test_enum(season) VALUES(1),('1');#可以使用索引

INSERT INTO test_enum(season) VALUES(NULL);#可以null

#【6.5 set类型】多选
CREATE TABLE test_set(
	s SET('A','B','C')
);	
SELECT * FROM test_set;
INSERT INTO test_set(s)VALUES('A'),('A,B');
INSERT INTO test_set(s)VALUES('A,B,A');#写重复的不报错,自动过滤掉
INSERT INTO test_set(s)VALUES('A,D');#写不在范围内的,报错

CREATE TABLE temp_mul(
	gender ENUM('男','女'),
	hobby SET('吃','喝','玩','乐')
);
SELECT * FROM temp_mul;
INSERT INTO temp_mul(gender,hobby)VALUES('男','吃,喝');


#【七、二进制字符串类型】
#binary(M) varbinary(M) tinyblob blob mediumblob longblob


#【7.1 binary和varbinary类型】
#binary和varbinary类似于char和varchar,只是它们存储的是二进制字符串。
#binary(M)表示最多存储M个字节,而char是字符;未指定M,表示1个
#varbinary(M):必须指定M


#【7.2 blob类型】
#BLOB类型包括TINYBLOB、BLOB、MEDIUMBLOB和LONGBLOB4种类型,
#BLOB是一个二进制大对象,可以容纳可变数量的数据,比如图片,音频,视频
/*【注意】在实际工作中,往往不会在MySQL数据库中使用BLOB类型存储大对象数据,通常会将图片、音频
和视频文件存储到服务器的磁盘上,并将图片、音频和视频的访问路径存储到MySQL中。*/
CREATE TABLE test_blob1(
	id INT,
	img MEDIUMBLOB
);
SELECT * FROM test_blob1;
INSERT INTO test_blob1(id)VALUES(1001);


#【八、json类型】
CREATE TABLE test_json(
	js JSON
);
SELECT * FROM test_json;
INSERT INTO test_json(js)
VALUES('{"name":"zmj","age":18,"address":{"province":"河南","city":"郑州"}}');
#提取字段
SELECT js -> '$.name' AS NAME,js -> '$.age' AS age,js -> '$.address.province'AS province
FROM test_json;


#【九、空间类型】略 用到再学习



#【总结】
/*在定义数据类型时,如果确定是整数,就用INT;如果是小数,一定用定点数类型DECIMAL(N,D);
如果是日期与时间,就用DATETIME。*/
/*【经验】
任何字段如果为非负数,必须是UNSIGNED
【强制】小数类型为DECIMAL,禁止使用FLOAT和DOUBLE。
	说明:在存储的时候,FLOAT和DOUBLE都存在精度损失的问题,很可能在比较值的时候,得到不正确的结果。
	如果存储的数据范围超过DECIMAL的范围,建议将数据拆成整数和小数并分开存储。
【强制】如果存储的字符串长度几乎相等,使用CHAR定长字符串类型。
【强制】VARCHAR是可变长字符串,不预先分配存储空间,长度不要超过50o0。
	如果存储长度大于此值,定义字段类型为TEXT,独立出来一张表,用主键来对应,避免影响其它字段索引效率。

*/
I


################################################【约束】#################################################
#约束:constraint:列级约束,表级约束
CREATE DATABASE dbtest13;
USE dbtest13;

#【1】查看表中的约束
SELECT * FROM information_schema.table_constraints
WHERE TABLE_NAME='employees';

#【2】非空约束not null
#①默认,所有的类型的值都可以是NULL,包括INT、FLOAT等数据类型
#②非空约束只能出现在表对象的列上,只能某个列单独限定非空,不能组合非空,也就是只能列级约束
#③空字符串不等于NULL,0也不等于NULL
#2.1 在create table时添加约束
CREATE TABLE test1(
	id INT NOT NULL,
	last_name VARCHAR(15) NOT NULL,
	email VARCHAR(25),
	salary DECIMAL(10,2)
);

DESC test1;
#2.2 在alter table时添加约束
ALTER TABLE test1
MODIFY email VARCHAR(25) NOT NULL;

#2.3 在alter table时删除约束
ALTER TABLE test1
MODIFY email VARCHAR(25);


#【3】unique唯一性约束
#可以向声明为unique的字段上添加null值,可以重复,即使unique
#3.1 在create table时添加
CREATE TABLE test2(
	id INT UNIQUE,#列级约束,在创建唯一约束的时候,如果不给唯一约束命名,就默认和列名相同.
	last_name VARCHAR(15),
	email VARCHAR(25)UNIQUE,
	salary DECIMAL(10,2),#注意此处有逗号
	
	#表级约束
	CONSTRAINT uk_test2_email UNIQUE(email)#起名字CONSTRAINT uk_test2_email
);
DESC test2;
SELECT * FROM information_schema.table_constraints
WHERE TABLE_NAME='test2';

#3.2 在alter table时添加
#方式一
ALTER TABLE test2 ADD CONSTRAINT uk_test2_sal UNIQUE(salary);#起名字:CONSTRAINT uk_test2_sal
#方式二
ALTER TABLE test2 MODIFY last_name VARCHAR(15) UNIQUE;

#3.3 复合的唯一性约束
CREATE TABLE USER(
	id INT,
	`name` VARCHAR(15),
	`password` VARCHAR(25),
	
	#表级约束
	CONSTRAINT uk_user_name_pwd UNIQUE(`name`,`password`)#两个在一起建立的约束,两个不全一样就可以
);
DESC USER;
INSERT INTO USER 
VALUES(1,'Tom','abc'),(1,'Tom1','abc');#可以通过



#【4】主键约束primary key
#①一个表中最多只能有一个主键约束。
#②主键约束特征:非空且唯一,用于唯一的标识表中的一条记录。
#③如果删除主键约束了,主键约束对应的索引就自动删除了。
#④MySQL的主键名总是PRIMARY,就算自己命名了主键约束名也没用。
#⑤如果是多列组合的复合主键约束,那么这些列都不允许为空值,并且组合的值不允许重复


#4.1 在create table时添加
CREATE TABLE test3(
	id INT PRIMARY KEY,#列级约束
	last_name VARCHAR(15),
	email VARCHAR(25),
	salary DECIMAL(10,2)
);
DESC test3;

CREATE TABLE test4(
	id INT,
	last_name VARCHAR(15),
	email VARCHAR(25),
	salary DECIMAL(10,2),
	
	#表级约束
	CONSTRAINT pk_test4_id PRIMARY KEY(id)#没有必要起名字
);
DESC test4;
SELECT * FROM information_schema.table_constraints
WHERE TABLE_NAME='test4';

CREATE TABLE USER1(
	id INT,
	`name` VARCHAR(15),
	`password` VARCHAR(25),
	
	PRIMARY KEY(`name`,`password`)#俩一起构成主键
);

#4.2 在alter table时添加
CREATE TABLE test6(
	id INT,
	last_name VARCHAR(15),
	email VARCHAR(25),
	salary DECIMAL(10,2)
);
DESC test6;

ALTER TABLE test6
ADD PRIMARY KEY(id);

#4.3 删除主键(在实际开发中,并不会删除的)
ALTER TABLE test6
DROP PRIMARY KEY;


#【5】自增列:auto_increment
#①一个表最多只能有一个自增长列I
#②当需要产生唯一标识符或顺序值时,可设置自增长
#③自增长列约束的列必须是键列(主键列,唯一键列)
#④自增约束的列的数据类型必须是整数类型
#⑥如果自增列指定了0和null,会在当前最大值的基础上自增;如果自增列手动指定了具体值,直接赋值为具体值。

#5.1 在create table时候添加
CREATE TABLE test7(
	id INT PRIMARY KEY AUTO_INCREMENT,
	last_name VARCHAR(15)
);
SELECT * FROM test7;
INSERT INTO test7(last_name)
VALUES('zmj'),('lwq'),('zym'),('zyn');#自动递增
INSERT INTO test7(id,last_name)
VALUES(-10,'tom');#自动设置id也可以,负数也可以,且按照id从小到大排序了

#5.2 在alter table时候添加
CREATE TABLE test8(
	id INT PRIMARY KEY,
	last_name VARCHAR(15)
);
DESC test8;
ALTER TABLE test8
MODIFY id INT AUTO_INCREMENT;

#5.3 在alter table时候删除
ALTER TABLE test8
MODIFY id INT;

#【6】外键约束foreign key
#①主表(父表)︰被引用的表,被参考的表从表(子表):引用别人的表,参考别人的表
#关联:主键或者唯一性约束的
#②在创建外键约束时,如果不给外键约束命名,默认名不是列名,而是自动产生一个外键名
#③删表时,先删从表(或先删除外键约束),再删除主表
#④在“从表”中指定外键约束,并且一个表可以建立多个外键约束
#⑤当创建外键约束时,系统默认会在所在的列上建立对应的普通索引。但是索引名是列名,不是外键的约束名(根据外键查询效率很高)
#⑥删除外键约束后,必须手动删除对应的索引

#6.1 在CREATETABLE时添加
#先创建主表
CREATE TABLE dept1(
	dept_id INT PRIMARY KEY,
	dept_name VARCHAR(15)
);
DESC dept1;
#再创建从表
CREATE TABLE emp1(
	emp_id INT PRIMARY KEY AUTO_INCREMENT,
	emp_name VARCHAR(15),
	department_id INT,
	
	#表级约束
	CONSTRAINT fk_emp1_dept_id FOREIGN KEY(department_id) REFERENCES dept1(dept_id)
);
DESC emp1;
SELECT * FROM information_schema.table_constraints WHERE TABLE_NAME='emp1'; 

#6.2 在alter table 添加外键约束
CREATE TABLE dept2(
	dept_id INT PRIMARY KEY,
	dept_name VARCHAR(15)
);
CREATE TABLE emp2(
	emp_id INT PRIMARY KEY AUTO_INCREMENT,
	emp_name VARCHAR(15),
	department_id INT
);
ALTER TABLE emp2
ADD CONSTRAINT kf_emp2_dept2_id FOREIGN KEY(department_id)REFERENCES dept2(dept_id);


#6.3 约束等级
#Cascade方式:在父表上update/delete记录时,同步update/delete掉子表的匹配记录
#Set null方式∶在父表上update/delete记录时,将子表上匹配记录的列设为null,但是要注意子表的外键列不能为not null
#No action方式∶如果子表中有匹配的记录,则不允许对父表对应候选键进行update/delete操作
#Restrict方式:同no action,都是立即检查外键约束
#Set default方式(在可视化工具sQLyog中可能显示空白)︰父表有变更时,子表将外键列设置成一个默认的值,但Innodb不能识别
#如果没有指定等级,就相当于Restrict方式。
#【结论】对于外键约束,最好是采用: ON UPDATE CASCADE ON DELETE RESTRICT的方式。


#6.4 删除外键约束
ALTER TABLE emp2 DROP FOREIGN KEY kf_emp2_dept2_id;
#再手动删除外键约束对应的普通索引
SHOW INDEX FROM emp2;
ALTER TABLE emp2 DROP INDEX kf_emp2_dept2_id;#外键约束名

#6.5 开发场景
#①建和不建外键约束和查询没有关系。
#②建外键约束,你的操作(创建表、删除表、添加、修改、删除)会受到限制,从语法层面受到限制。
#③在MySQL里,外键约束是有成本的,需要消耗系统资源。对于大并发的sQL操作,有可能会不适



#【七、check约束】
#作用:检查某个字段的值是否符号xx要求,一般指的是值的范围
#7.1 在create table添加
CREATE TABLE test10(
	id INT,
	last_name VARCHAR(10),
	salary DECIMAL(10,2) CHECK(salary>2000)
);


#【八、default约束】
#9.1 在create table时候添加
CREATE TABLE test11(
	id INT,
	last_name VARCHAR(19),
	salary DECIMAL(10,2) DEFAULT 2000
);

#9.2 在alter table时添加约束
ALTER TABLE test11
MODIFY last_name VARCHAR(19) DEFAULT 'zmj';

DESC test11;
#9.3 在alter table时删除约束
ALTER TABLE test11
MODIFY last_name VARCHAR(19);

#【十、面试题】
#①面试1:为什么建表时,加not null default "或default 0?  ==》不想让表中出现null值。
#②面试2:为什么不想要null的值?==》不好比较; 效率不高。
#③面试3:带AUTO_INCREMENT约束的字段值是从1开始的吗?==》在mysql时,是的。还可以指定。
#④面试4:并不是每个表都可以任意选择存储引擎? ==》外键约束不能跨引擎使用。


###########################################【约束练习题一】################################################
#已经存在数据库test04_emp,两张表emp2和dept2
CREATE DATABASE test04_emp;
USE test04_emp;
CREATE TABLE emp2(
	id INT,
	emp_name VARCHAR(15)
);
CREATE TABLE dept2(
	id INT,
	dept_name VARCHAR(15)
);
#1.向表emp2的id列中添加PRIMARY KEY约束
ALTER TABLE emp2 MODIFY id INT PRIMARY KEY;
ALTER TABLE emp2 ADD PRIMARY KEY(id);
DESC emp2;
#2. 向表dept2的id列中添加PRIMARY KEY约束
ALTER TABLE dept2 ADD PRIMARY KEY(id);
DESC dept2;
#3. 向表emp2中添加列dept_id,并在其中定义FOREIGN KEY约束,与之相关联的列是dept2表中的id列。
ALTER TABLE emp2 ADD dept_id INT;
ALTER TABLE emp2 ADD CONSTRAINT fk_emp2_dep2_id FOREIGN KEY(dept_id) REFERENCES dept2(id);


################################################【视图view】#################################################
#【1.常见的数据库对象】
#表,数据字典,约束,视图,索引,存储过程,存储函数,触发器


#【2.视图概述】
#[作用]可以帮我们使用表的一部分数据而不是所有的表;也可以针对不同的用户制定不同的查询视图。
#[优点]简化查询;减少数据冗余;控制数据的访问;使用灵活多变的需求;能够分解复杂的查询逻辑
#[缺点]如果视图过多,会导致数据库维护成本的问题;及时维护,维护成本大。
#①视图是一种虚拟表,本身是不具有数据的,占用很少的内存空间。
#②视图建立在已有表的基础上,视图赖以建立的这些表称为基表。
#③视图的创建和删除只影响视图本身,不影响对应的基表。
# 但是当对视图中的数据进行增加、删除和修改操作时,数据表中的数据会相应地发生变化,反之亦然。
#④视图,是向用户提供基表数据的另一种表现形式。通常情况下,小型项目的数据库可以不使用视图。


#【3.创建视图】
CREATE DATABASE dbtest14;
USE dbtest14;
CREATE TABLE emps AS SELECT * FROM atguigudb.employees;#数据复制过来了,但是约束没有
CREATE TABLE depts AS SELECT * FROM atguigudb.departments;

#3.1 创建单表视图
#情况一:视图中的字段在基表中有对应的字段
CREATE VIEW vu_emp1 AS SELECT employee_id,last_name,salary FROM emps;
SELECT * FROM vu_emp1;

CREATE VIEW vu_emp2
AS
SELECT employee_id 'emp_id',last_name 'l_name',salary 'sal'#查询语句中字段的别名会出现在视图的字段中
FROM emps
WHERE salary >8000;

SELECT * FROM vu_emp3;

CREATE VIEW vu_emp3(emp_id,`name`,sal)#一次匹配,字段名字
AS 
SELECT employee_id,last_name,salary
FROM emps
WHERE salary >8000;

#情况二:视图中的字段在基表中可能设有对应的字段
CREATE VIEW vu_emp_sal(dep_id,avg_sal)#不在这里写,也可以
AS
SELECT department_id,AVG(salary) avg_sal
FROM emps 
WHERE department_id IS NOT NULL
GROUP BY department_id;

SELECT * FROM vu_emp_sal;

#3.2 创建多表的视图
#其实没啥区别   多表查询而已


#利用视图对数据进行格式化
#其实也就是将查询数据固定下来,存为view而已


#3.3 基于视图创建视图
#其实和表一模一样啦


#【4.查看视图】==>和表一样
#语法一:查看数据库的表对象、视图对象
SHOW TABLES;#表和视图全部展示出来

#语法二:查看视图的结构
DESC vu_emp1;
DESCRIBE vu_emp1;#同上
DESCRIBE depts;

#语法三:查看视图的属性信息
SHOW TABLE STATUS LIKE 'vu_emp1';#命令行竖着显示,末尾加上\G
SHOW TABLE STATUS LIKE 'depts';

#语法四:查看视图的详细定义信息
SHOW CREATE VIEW vu_emp1;


#【5.更新视图数据】更新《==》增删改
#①更改视图,会同时更改原表
#②同理,更细表中数据,会导致视图中的数据修改
#③在定义视图的SELECT语句中使用了JOIN联合查询,视图将不支持更新操作。
#④虽然可以更新视图数居,但总的来说,视图作为虚拟表,主要用于方便查询,不建议更新视图的数据。
#⑤对视图数据的更改,都是通过对实际数据表里数据的操作来完成的。

SELECT * FROM vu_emp1;
SELECT  employee_id,last_name,salary FROM emps;

UPDATE vu_emp1
SET salary =20000
WHERE employee_id=101;#更改视图,会同时更改原表

#【6.修改视图】
DESC vu_emp1;
#方式1
CREATE OR REPLACE VIEW vu_emp1#不存在就创建,存在就替换更新
AS 
SELECT employee_id,last_name,salary,email
FROM emps
WHERE salary >7000;
#方式2
ALTER VIEW vu_emp1
AS 
SELECT employee_id,last_name,salary,email
FROM emps;

#【7.删除视图】
DROP VIEW vu_emp3;
DROP VIEW IF EXISTS vu_emp3,vu_emp4;#可同时删除多个
SHOW TABLES;

########################################【视图练习题一】##########################################
#1. 使用表employees创建视图employee_vu,其中包括姓名,员工号,部门号
CREATE VIEW emplouee_vu
AS
SELECT last_name,employee_id,department_id
FROM atguigudb.employees;
#2. 显示视图的结构
DESC emplouee_vu;
#3. 查询视图中的全部内容
SELECT * FROM emplouee_vu;
#4. 将视图中的数据限定在部门号是80的范围内
CREATE OR REPLACE VIEW employee_vu AS
SELECT last_name,employee_id,department_id FROM atguigudb.employees WHERE department_id=80;

########################################【视图练习题二】##########################################
CREATE TABLE IF NOT EXISTS emps AS SELECT * FROM atguigudb.employees;
SELECT * FROM emps;
#1. 创建视图emp_v1,要求查询电话号码以‘011’开头的员工姓名和工资、邮箱
CREATE OR REPLACE VIEW emp_v1 AS
SELECT last_name,salary,email FROM emps WHERE phone_number LIKE '011%';
SELECT * FROM emp_v1;
#2. 要求将视图 emp_v1 修改为查询电话号码以‘011’开头的并且邮箱中包含 e 字符的员工姓名和邮箱、电话号码
CREATE OR REPLACE VIEW emp_v1 AS
SELECT last_name,email,phone_number FROM emps
WHERE phone_number LIKE '011%' AND email LIKE '%e%';
#3. 向 emp_v1 插入一条记录,是否可以?==》不可以
DESC emps;
DESC emp_v1;
INSERT INTO emp_v1(last_name,salary,email,phone_number)
VALUES('Tom',2300,'[email protected]','1322321312');#错误
#4. 修改emp_v1中员工的工资,每人涨薪1000
UPDATE emp_v1
SET salary=salary+1000;
#5. 删除emp_v1中姓名为Olsen的员工
DELETE FROM emp_v1
WHERE last_name='Olsen'
#6. 创建视图emp_v2,要求查询部门的最高工资高于 12000 的部门id和其最高工资
CREATE OR REPLACE VIEW emp_v2 AS
SELECT department_id,MAX(salary) max_sal FROM emps GROUP BY department_id HAVING MAX(salary)>12000;
SELECT * FROM emp_v2;
#7. 向 emp_v2 中插入一条记录,是否可以?==》不可以
INSERT INTO emp_v2 VALUES(400,18000);
#8. 删除刚才的emp_v2 和 emp_v1
DROP VIEW IF EXISTS emp_v2,emp_v1;


###########################################【存储过程】############################################
#【1.存储过程】procedure
#[定义]就是一组经过预先编译的SQL语句的封装。
#[优点]提高重用性;减少网络传输量;减少sql语句暴露在网络上的风险,提高安全性。
#[存储过程、视图对比]视图是虚拟表,一般不操控原表;存储过程是程序化的SQL可以直接操作底层数据表.
#[存储过程、函数对比]存储过程没有返回值,函数有返回值。
/*[分类]存储过程的参数类型是in,out,inout。参数类型可以是mysql中的任意类型。
	1.没有参数(无参数无返回)
	2.仅仅带in/out类型(有参数无返回/无参数有返回)
	3.既带in又带out(有参数有返回)
	4.带inout(有参数有返回)
    注意:IN、OUT、INOUT都可以在一个存储过程中带多个。
*/
/*characteristics表示创建存储过程时指定的对存储过程的约束条件,
	

*/


#【2.创建存储过程】procedure
DELIMITER $#定义结束符$,也可以其他符号,自定义delimiter
CREATE PROCEDURE select_all_date()
BEGIN 
	...

END $
DELIMITER ;

CREATE DATABASE dbtest15;
USE dbtest15;
CREATE TABLE employees AS SELECT * FROM atguigudb.employees;
CREATE TABLE departments AS SELECT * FROM atguigudb.departments;

#类型一:无参数无返回值
#2.1 创建
DELIMITER $
CREATE PROCEDURE select_all_data()
BEGIN 
	SELECT *FROM employees;
END $

DELIMITER ;

#2.2 调用
CALL select_all_data();#存储过程的调用用call,函数是select

DELIMITER //
CREATE PROCEDURE avg_employee_salary()
BEGIN
	SELECT AVG(salary) FROM employees;
END //
DELIMITER ;
CALL avg_employee_salary();


#类型二:out
DELIMITER $
CREATE PROCEDURE show_min_sal(OUT ms DOUBLE)#先写名字ms,再写类型double
BEGIN
	SELECT MIN(salary) INTO ms FROM employees;
END $
DELIMITER ;
CALL show_min_sal(@ms);#变量ms
SELECT @ms;#查看变量值select即可


#类型三:in
DELIMITER $
CREATE PROCEDURE show_someone_salary(IN empname VARCHAR(20))
BEGIN 
	SELECT salary FROM employees WHERE last_name=empname;
END $
DELIMITER ;
#调用方式1:直接传入
CALL show_someone_salary('Abel');
#调用方式2:变量方式
SET @empname='Abel';#定义变量,'='或者':='均为赋值符号
CALL show_someone_salary(@empname);#传入


#类型四:in out
DELIMITER $
CREATE PROCEDURE show_someone_sal2(IN empname VARCHAR(20),OUT empsalary DECIMAL(10,2))
BEGIN
	SELECT salary INTO empsalary
	FROM employees
	WHERE last_name=empname;
END $
DELIMITER ;
SET @empname='Abel';
CALL show_someone_sal2(@empname,@empsal);
SELECT @empsal;


#类型五:inout
DELIMITER $
CREATE PROCEDURE show_mgr_name(INOUT empname VARCHAR(25))#查询某个员工领导的姓名
BEGIN
	SELECT last_name INTO empname
	FROM employees
	WHERE employee_id=(
				SELECT manager_id
				FROM employees
				WHERE last_name=empname
				);
END $
DELIMITER ;
SET @empname='Abel';
CALL show_mgr_name(@empname);
SELECT @empname;


#调试:逐步调试,很恶心

########################################【存储函数】##############################################
#[语法格式]
DELIMITER $
CREATE FUNCTION 函数名(参数名 参数类型,...)
RETURNS 返回值类型
[characteristics...]
BEGIN 
	函数体#函数体中肯定有return语句	
END $
DELIMITER ;
#[参数列表]指定参数为IN、OUT或INOUT只对procedure是合法的,function中总是默认为IN参数
#[调用格式]
SELECT 函数名(实参列表);

#情况一:参数为空
DELIMITER $
CREATE FUNCTION email_by_name()
RETURNS VARCHAR(25)
	DETERMINISTIC #解决报错的方式一
	CONTAINS SQL
	READS SQL DATA
BEGIN 
	RETURN(SELECT email FROM employees WHERE last_name='Abel'); 
END $
DELIMITER ;
#解决报错的方式二:创建函数前执行此语句,保证函数的创建会成功
SET GLOBAL log_bin_trust_function_creators=1;
#调用
SELECT email_by_name();


#情况二:传参
DELIMITER $
CREATE FUNCTION email_by_id(emp_id INT)
RETURNS VARCHAR(25)
BEGIN
	RETURN(SELECT email FROM employees WHERE employee_id=emp_id);
END $
DELIMITER ;
SELECT email_by_id(101);

/*存储过程和函数对比
   存储过程:PROCEDURE   CALL存储过程()  返回值理解为有0个或多个  一般用于更新
   存储函数:FUNCTION    SELECT函数()    返回值只能是一个         一般用于查询结果为一个值并返回时
   
   存储函数可以放在查询语句中使用,存储过程不行。
   存储过程的功能更加强大,包括能够执行对表的操作(比如创建表,删除表等)和事务操作,
   这些功能是存储函数不具备的
*/

#####################################【存储过程和函数的查看、修改、删除】####################################
#【查看】
#创建完之后,怎么知道我们创建的存储过程、存储函数是否成功了呢?
#方式一:查看存储过程和函数的创建信息
SHOW CREATE PROCEDURE show_mgr_name;
SHOW CREATE FUNCTION email_by_id;
#方式二:查看存储过程和函数的状态信息
SHOW PROCEDURE STATUS;
SHOW PROCEDURE STATUS LIKE 'show_mgr_name';
#方式三:从information_schema.Routines表中查看存储过程和函数的信息
SELECT * FROM information_schema.Routines
WHERE ROUTINE_NAME LIKE 'email_by_id'
AND ROUTINE_TYPE='FUNCTION';#这一行不写也可以


#【修改】
#修改存储过程或函数,不影响存储过程或函数功能,只是修改相关特性。使用ALTER语句实现。
ALTER PROCEDURE show_min_sal
SQL SECURITY INVOKER
COMMENT '查询最低工资';

#【删除】
DROP PROCEDURE IF EXISTS show_min_sal;


#【关于存储过程使用的争议】
/*[优点]
	存储过程可以一次编译多次使用。
	可以减少开发工作量。
	存储过程的安全性强。
	可以减少网络传输量。
	良好的封装性。
*/
/*[缺点]
	可移植性差。
	调试困难。
	存储过程的版本管理很困难。
	不适合高并发的场景。
*/

#########################################【存储过程、函数练习题】#############################################
#0.准备工作
CREATE DATABASE test15_pro_func;
USE test15_pro_func;
#1. 创建存储过程insert_user(),实现传入用户名和密码,插入到admin表中
CREATE TABLE IF NOT EXISTS ADMIN(
				id INT PRIMARY KEY AUTO_INCREMENT,
				user_name VARCHAR(15) NOT NULL,
				pwd VARCHAR(25)NOT NULL
				);
DELIMITER //
CREATE PROCEDURE insert_user(IN username VARCHAR(20),IN loginPwd VARCHAR(20))
BEGIN
	INSERT INTO `admin`(user_name,pwd)
	VALUES(username,loginpwd);
END //
DELIMITER ;

CALL insert_user('zmj','abc123');
SHOW TABLES;
SELECT * FROM `admin`;
DROP TABLE ADMIN;

#2. 创建存储过程get_phone(),实现传入女神编号,返回女神姓名和女神电话
CREATE TABLE beauty(
	id INT PRIMARY KEY AUTO_INCREMENT,
	NAME VARCHAR(15) NOT NULL,
	phone VARCHAR(15) UNIQUE,
	birth DATE
);

INSERT INTO beauty(NAME,phone,birth)
VALUES
	('朱茵','13201233453','1982-02-12'),
	('孙燕姿','13501233653','1980-12-09'),
	('田馥甄','13651238755','1983-08-21'),
	('邓紫棋','17843283452','1991-11-12'),
	('刘若英','18635575464','1989-05-18'),
	('杨超越','13761238755','1994-05-11');
SELECT * FROM beauty;

DELIMITER $
CREATE PROCEDURE get_phone(IN id_ INT,OUT name_ VARCHAR(15),OUT phone_ VARCHAR(15))
BEGIN
	SELECT `name`,phone INTO name_,phone_
	FROM beauty
	WHERE id=id_;
END $
DELIMITER ;

#调用
CALL get_phone(2,@name_,@phone_);
SELECT @name_,@phone_;


#3. 创建存储过程date_diff(),实现传入两个女神生日,返回日期间隔大小
DELIMITER $
CREATE PROCEDURE date_diff(IN b1 DATE,IN b2 DATE,OUT diff_day INT)
BEGIN
	SELECT DATEDIFF(b1,b2) INTO diff_day;
END $
DELIMITER ;

SET @b1='1999-05-26';
SET @b2='1999-06-26';
CALL date_diff(@b1,@b2,@diff_day);
SELECT @diff_day;

#4. 创建存储过程format_date(),实现传入一个日期,格式化成xx年xx月xx日并返回
DELIMITER $
CREATE PROCEDURE format_date(IN d DATE,OUT strdate VARCHAR(50))
BEGIN
	SELECT DATE_FORMAT(d,'%y年%m月%d日')INTO strdate;
END $
DELIMITER ;

CALL format_date('20221024',@res);
SELECT @res;

#5. 创建存储过程beauty_limit(),根据传入的起始索引和条目数,查询女神表的记录
DELIMITER $
CREATE PROCEDURE beauty_limit(IN start_index INT,IN size INT)
BEGIN
	SELECT * FROM beauty LIMIT start_index,size;
END $
DELIMITER ;

CALL beauty_limit(2,2);

#6. 传入a和b两个值,最终a和b都翻倍并返回
DELIMITER //
CREATE PROCEDURE add_double(INOUT a INT,INOUT b INT)
BEGIN
	SET a=a+a;
	SET b=b+b;
END //
DELIMITER ;
SET @a=3,@b=4;
CALL add_double(@a,@b);
SELECT @a,@b;


#存储函数练习
USE test15_pro_func;
CREATE TABLE employees AS SELECT * FROM atguigudb.`employees`;
CREATE TABLE departments AS SELECT * FROM atguigudb.`departments`;
#1. 创建函数get_count(),返回公司的员工个数
DELIMITER //
CREATE FUNCTION get_count()
RETURNS INT
BEGIN
	RETURN(SELECT COUNT(*)FROM employees);
END //
DELIMITER ;
SELECT get_count();

#2. 创建函数ename_salary(),根据员工姓名,返回它的工资
DELIMITER //
CREATE FUNCTION ename_salary(n VARCHAR(25))
RETURNS DOUBLE
BEGIN 
	RETURN(SELECT salary FROM employees WHERE last_name=n);
END //
DELIMITER ;

SELECT ename_salary('De Haan');#不能返回多行的

DESC employees;
DESC departments;

#3. 创建函数dept_sal() ,根据部门名,返回该部门的平均工资
DELIMITER //
CREATE FUNCTION dept_sal(dept_name VARCHAR(30))
RETURNS DOUBLE
BEGIN
	RETURN(SELECT AVG(salary) 
		FROM employees 
		GROUP BY department_id 
		HAVING department_id=(
					SELECT department_id
					FROM departments
					WHERE department_name=dept_name
					)
		);
END //
DELIMITER ;

SELECT dept_sal('IT');

#4. 创建函数add_float(),实现传入两个float,返回二者之和
DELIMITER //
CREATE FUNCTION add_float(f1 FLOAT,f2 FLOAT)
RETURNS DOUBLE
BEGIN
	RETURN(SELECT f1+f2);
END //
DELIMITER ;

SELECT add_float(2,3);

#########################################【变量、流程控制与游标】############################################
#【一、变量】
#系统变量(全局系统变量、会话系统变量),用户自定义变量

#1.1 系统变量
/*
   ①变量由系统定义,不是用户定义,属于服务器层面。
   ②系统变量分为:全局系统变量(需要添加global),会话系统变量(local变量)(需要添加session关键字)
   ③全局系统变量针对于所有会话(连接)有效,但不能跨重启
   ④会话系统变量仅针对于当前会话(连接)有效。
     会话期间,当前会话对某个会话系统变量值的修改不会影响其他会话同一个会话系统变量的值。
   ⑤会话1对某个全局系统变量值的修改会导致会话2中同一个全局系统变量值的修改。
   ⑥ 有些系统变量只能是全局的,例如max_connections 用于限制服务器的最大连接数;
     有些系统变量作用域既可以是全局又可以是会话,例如character_set_client用于设置客户端的字符集;
     有些系统变量的作用域只能是当前会话,例如pseudo_thread_id 用于标记当前会话的MySQL连接ID。	
*/

#1.1.1 查看系统变量
#【查看所有或部分系统变量】
#查看所有全局变量
SHOW GLOBAL VARIABLES;
#查看所有会话变量
SHOW SESSION VARIABLES;
SHOW VARIABLES;#默认的是会话系统变量
#查看满足条件的部分系统变量
SHOW GLOBAL VARIABLES LIKE 'admin_%';
#查看满足条件的部分会话变量
SHOW SESSION VARIABLES LIKE 'admin_%';

#【查看指定系统变量】
/*
   MysQL中的系统变量以“@@”开头,其中“@@global”仅用于标记全局系统变
量, @@session"仅用于标记会话系统变量。“@@"首先标记会话系统变量,如果
会话系统变量不存在,则标记全局系统变量。
*/
#查看指定的系统变量的值
SELECT @@global.admin_ssl_ca;
#查看指定的会话系统变量的值
#或者
SELECT @@session.character_set_client;
SELECT @@max_connections;#现在会话系统变量中找,再到全局中找
#【修改系统变量的值】
/*
   方式1:修改MySQL配置文件,继而修改MySQL系统变量的值(该方法需要重启MySQL服务) my.ini
   方式2:在MySQL服务运行期间,使用“set”命令重新设置系统变量的值
*/
#[为某个系统变量赋值]
#全局系统变量:针对于当前的数据库实例是有效的,一旦重启mysql服务,就失效了。
#方式1
SET @@global.max_connections=161;
SELECT @@global.max_connections;#查看值
#方式2
SET GLOBAL max_connections=171;

#[为某个会话变量赋值]
#针对于当前会话是有效的,一旦结束会话,重新建立起新的会话,就失效了。
#方式1
SET @@session.character_set_client='gbk';
SELECT @@character_set_client;
SELECT @@global.character_set_client;#修改会话的,并不影响全局的
#方式2
SET SESSION character_set_client='gbk';



#【1.2 用户变量】
#用户变量:会话用户变量和局部变量
/*  MySQL中的会话用户变量以一个“@”开头,局部变量都懒得用了。
    会话用户变量:作用域和会话变量一样,只对当前连接会话有效。
    局部变量:只在BEGIN和END语句块中有效。局部变量只能在存储过程和函数中使用。
*/
#【会话用户变量】
USE dbtest15;
#变量的定义
#方式1:'='或者':='
SET @m1=1;
SET @m2:=2;
SET @sum:=@m1+@m2;
SELECT @sum;#查看变量值
#方式2
SELECT @count :=COUNT(*) FROM employees;#注意是':='
SELECT @count,@avg_sal;
SELECT AVG(salary) INTO @avg_sal FROM employees;

#【局部变量】
/*
  ①声明和使用只能在begin end之间,即存储过程和函数之中。
  ②使用declare声明。
  ③declare的方式声明必须放在begin中首行的位置。
  ④赋值还是set,select ... into这一类。
  
声明格式:declare 变量名 类型 [default 值];#如果没有default,初始值为null
#注意:其他变量都没有类型,而这个局部变量声明时候有类型
*/
DELIMITER //
CREATE PROCEDURE test_var()
BEGIN
	#声明局部变量
	DECLARE a INT DEFAULT 0;
	DECLARE b INT;
	#如果类型、默认值都一样的话也可以一起声明
	DECLARE c,d INT DEFAULT 100;
	
	DECLARE emp_name VARCHAR(25);
	
	#赋值
	SET a = 1;
	SET b := 2;
	SELECT last_name INTO emp_name FROM employees WHERE employee_id=101;
	
	#使用
	SELECT a,b,emp_name;
END //
DELIMITER ;

CALL test_var();


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

*/



#【二、定义条件与处理程序】
/*
定义条件:事先定义程序执行道程中可能遇到的问题,
处理程序:定义了在遇到问题时应当采取的处理方式,并且保证存储过程或函数在遇到警告或错误时能继续执行。
         这样可以增强存储程序处理问题的能力,避免程序异常停止运行。
说明:定义条件和处理程序在存储过程、存储函数中都是支持的。
*/

#【2.1 定义条件】
/*
  定义条件就是给MysQL中的错误码命名,这有助于存储的程序代码更清晰。
  它将一个错误名字和指定的错误条件关联起来。
  这个名字可以随后被用在定义处理程序的DECLARE HANDLER语句中。
语法格式:
   declare 错误名称 condition for 错误码(或错误条件);
   
错误码说明:
  MySQL_error_code和sqlstate_value都可以表示MySQL的错误。
	MySQL_error_code:是数值类型错误代码。
	sqlstate_value:是长度为5的字符串类型错误代码。
*/

#举例1:定义"Field_Not_Be_NULL"错误名与MysQL中违反非空约束的错误类型
#是"ERROR 1048 (23000)”对应。
#方式一:使用MySQL_error_code
DECLARE Field_Not_Be_NULL CONDITION FOR 1048;

#方式二:使用qlstate_value
DECLARE Field_Not_Be_NULL2 CONDITION FOR SQLSTATE '23000';

#举例2:定义"ERROR 1148 (42000)"错误,名称为command_not_allowed。
DECLARE command_not_allowed CONDITION FOR 1148;
DECLARE command_not_allowed CONDITION FOR SQLSTATE '42000';

#【2.2 定义处理程序】
/*
语法格式:declare 处理方式 handler for 错误类型 处理语句;
   处理方式:
	CONTINUE:表示遇到错误不处理,继续执行。
	EXIT:表示遇到错误马上退出。
	UNDO:表示遇到错误后撤回之前的操作。MySsQL中暂时不支持这样的操作。
   错误类型(即条件)可以有如下取值:
	SQLSTATE '字符串错误码':表示长度为5的sqlstate_value类型的错误代码;
	MySQL_error_code :匹配数值类型错误代码;
	错误名称:表示DECLARE ... CONDITION定义的错误条件名称。
	SQLWARNING:匹配所有以o1开头的SQLSTATE错误代码;
	NOT FOUND:匹配所有以o2开头的SQLSTATE错误代码;
	SQLEXCEPTION:匹配所有没有被SQLWARNING或NOT FOUND捕获的SQLSTATE错误代码;
   处理语句:
	如果出现上述条件之一,则采用对应的处理方式,并执行指定的处理语句。
	语句可以是像“ SET变量=值”这样的简单语句,也可以是使用BEGIN ... END编写的复合语句。
*/
#举例
#1.捕获sqlstate_value
DECLARE CONTINUE HANDLER FOR SQLSTATE '42s02' SET @info='NO_SUCH_TABLE';
#2.捕获mysql_error_value
DECLARE CONTINUE HANDLER FOR 1146 SET @info='NO_SUCH_TABLE';
#3.先定义条件,再调用
DECLARE no_such_table CONTINUE FOR 1146;
DECLARE CONTINUE HANDLER FOR no_such_table SET @info='NO_SUCH_TABLE';
#4.使用SQLWARING
DECLARE EXIT HANDLER FOR SQLWARNING SET @info='ERROR';
#5.使用NOT FOUND
DECLARE EXIT HANDLER FOR NOT FOUND SET @info='NO_SUCH_TABLE';
#6.使用SQLEXCEPTION
DECLARE EXIT HANDLER FOR SQLEXCEPTION SET @info='ERROR';

#案例解决
/*
  在存储过程中,定义处理程序,捕获sqlstate_value值,当遇到MySQL_error_code值为1048时,
执行CONTINUE操作,并且将@proc_value的值设置为-1。
*/
DELIMITER //
CREATE PROCEDURE UpdateDataNoCondition()
BEGIN
	#定义处理程序
	DECLARE CONTINUE HANDLER FOR 1048 SET @proc_value=-1;
	
	SET @x=1;
	UPDATE employees SET email NULL WHERE last_name='Abel';
	
	SET @x=2;
	UPDATE employees SET email='aabbel' WHERE last_name='Abel';
	SET @x=3;
END //
DELIMITER ;

#删除此存储过程
DROP PROCEDURE UpdateDataNoCondition;


#【三、流程控制】
/*是在存储过程或函数里面使用。
    条件判断语句:if语句和case语句
    循环语句:loop、while和repeat语句
    跳转语句:iterate和level语句
*/
#【3.1 if语句】
/*
语法结构:
   if 表达式1 then 操作1
   [elseif 表达式2 then 操作2]...
   [else 操作N]
   end if
*/
DELIMITER //
CREATE PROCEDURE test_if()
BEGIN
	#声明局部变量
	DECLARE stu_name VARCHAR(15) DEFAULT 'aaa';
	
	IF stu_name IS NULL
		THEN SELECT 'stu_name is null';
	ELSE 
		SELECT 'stu_name is not null';#注意:无then
	END IF;
END //
DELIMITER ;
CALL test_if();
DROP PROCEDURE test_if;

#【3.2 case语句】
/*
语法结构1:类似于switch
    case 表达式
    when 值1 then 结果1或语句1    #如果是语句,需要加分号
    when 值2 then 结果2或语句2
    else 结果n或语句n
    end [case];   #如果放在begin end中需要加case,如果放在select后面不需要加
    
语法结构2:类似于多次if
    case
    when 条件1 then 结果1或语句1    #如果是语句,需要加分号
    when 条件2 then 结果2或语句2
    else 结果n或语句n
    end [case];   #如果放在begin end中需要加case,如果放在select后面不需要加

*/
DELIMITER //
CREATE PROCEDURE test_case()
BEGIN
	#声明局部变量
	DECLARE var1 INT DEFAULT 12;
	CASE 
		WHEN var1 >= 100 THEN SELECT '三位数';
		WHEN var1 >= 10 THEN SELECT '两位数';
		ELSE SELECT '一位数';
	END CASE;
	
END //
DELIMITER ;
CALL test_case();

#【3.3 loop循环结构】
/*需要结合leave使用,跳出循环
语法格式:
    [loop_label:]loop
	循环执行的语句;
    end loop [loop_label]
    #loop_label表示LOOP语句的标注名称,该参数可以省略
*/
DELIMITER //
CREATE PROCEDURE test_loop()
BEGIN
	DECLARE num INT DEFAULT 1;
	loop_label:LOOP
		#重新赋值
		SET num=num+1;
		IF num>100 THEN LEAVE loop_label;
		END IF;
	END LOOP loop_label;
	#查看num值
	SELECT num 'num';
	
END //
DELIMITER ;
CALL test_loop();
DROP PROCEDURE test_loop;

#【3.4 while循环结构】
/*
语法格式:
   [while_label:]while 循环条件 do
	循环体
   end while [while_label];
*/

#【3.5 repeat循环语句】
/*
与WHILE循环不同的是,REPEAT 循环首先会执行一次循环
语法格式:
   [repeat_label:]repeat
	循环体的语句
   until 结束循环的条件表达式
   end repeat [repeat_label];
REPEAT语句内的语句或语句群被重复,直至expr_condition为真。
*/

#【3.6 leave语句】
/*相当于break
   可以用在循环语句内,或者以BEGIN和END包裹起来的程序体内,表示跳出循环或者跳出程序体的操作。
基本格式:
   leave 标记名; #离开标记名
*/


#【3.7 iterate语句】
/*相当于continue
    只能用在循环语句(LOOP、REPEAT和WHILE语句)内,表示重新开始循环,将执行顺序转到语句段开头处。
基本语法:
    iterate 标记名;
*/



#【四、游标或者光标】
/*相当于一个指针。
   游标,提供了一种灵活的操作方式,让我们能够对结果集中的每一条记录进行定位,
并对指向的记录中的数据进行操作的数据结构。游标让SQL这种面向集合的语言有了面向过程开发的能力。
   在SQL中,游标是一种临时的数据库对象,可以指向存储在数据库表中的数据行指针。
这里游标充当了指针的作用,我们可以通过操作游标来对数据行进行操作。

游标必须在声明处理程序之前被声明,并且变量和条件还必须在声明游标或处理程序之前被声明。

四步骤:声明游标,打开游标,使用游标,关闭游标
*/

#【4.1 声明游标】
/*
语法格式:
   declare cursor_name cursor for select_statement;
  
这个语法适用于MySQL,sQL Server,DB2和MariaDB。如果是用oracle或者PostgresQL,需要修改为:
   declare cursor_name cursor is select_statemment;
  
要使用SELECT语句来获取数据结果集,而此时还没有开始遍历数据,
这里select_statement代表的是SELECT语句,返回一个用于创建游标的结果集。

*/


#【4.2 打卡游标】
/*
语法格式:
  open cursor_name;
 
当我们定义好游标之后,如果想要使用游标,必须先打开游标。
打开游标的时候SELECT 语句的查询结果集就会送到游标工作区,
为后面游标的逐条读取结果集中的记录做准备。
*/

#【4.3 使用游标(从游标中获取数据)】
/*
基本语法:
    fetch cursor_name into var_name[,var_name]...
    
这句的作用是使用cursor_name这个游标来读取当前行,并且将数据保存到var_name这个到下一行。
如果游标读取的数据行有多个列名,则在INTO关键字后面赋值给多个变量名即可。
注意: var_name必须在声明游标之前就定义好。
*/

#【4.4 关闭游标】
/*
语法格式:
   close cursor_name;
*/

#举例:创建存储过程"get_count_by_limit_total_salary()",声明IN参数limit_total_salary,
#DOUBLE类型;声明ouT参数total_count,INT类型。函数的功能可以实现累加薪资最高的几个员工的薪资值,
#直到薪资总和达到limit_total_salary参数的值,返回累加的人数给total_count。
DELIMITER //
CREATE PROCEDURE get_count_by_limit_total_salary(IN limit_total_salary DOUBLE,OUT total_count INT)
BEGIN
	#声明局部变量
	DECLARE sum_sal DOUBLE DEFAULT 0.0;#记录累加的工资总额
	DECLARE emp_sal DOUBLE;#记录每一个员工的工资
	DECLARE emp_count INT DEFAULT 0;#记录累加的人数
	
	#声明游标
	DECLARE emp_cursor CURSOR FOR SELECT salary FROM employees ORDER BY salary DESC;
	
	#打开游标
	OPEN emp_cursor;
	
	REPEAT 
		#使用游标
		FETCH emp_cursor INTO emp_sal;
		
		SET sum_sal=sum_sal+emp_sal;
		SET emp_count=emp_count+1;
	UNTIL sum_sal>=limit_total_salary
	END REPEAT;
	
	SET total_count=emp_count;
	
	#关闭游标
	CLOSE emp_cursor;
END //
DELIMITER ;

CALL get_count_by_limit_total_salary(200000,@total_count);
SELECT @total_count;

#【补充:全局变量的持久化】
/*
set global 变量名=值;   #在数据库重启时候,失效。

语法格式:
    set persist 变量名=值;  
    
MySQL会将该命令的配置保存到数据目录下的mysqld-auto.cnf文件中,
下次启动时会读取该文件,用其中的配置来覆盖默认的配置文件。


*/


#####################################【变量练习题】############################################
USE dbtest15;
#1. 创建函数get_count(),返回公司的员工个数
DELIMITER //
CREATE FUNCTION get_count() RETURNS INT
BEGIN
	DECLARE c INT DEFAULT 0;#定义局部变量
	SELECT COUNT(*) INTO c#赋值
	FROM employees;
	RETURN c;
END //
DELIMITER ;
#调用
SELECT get_count();
SELECT * FROM employees;

#2. 创建函数ename_salary(),根据员工姓名,返回它的工资
DELIMITER //
CREATE FUNCTION ename_salary(emp_name VARCHAR(15))RETURNS DOUBLE
BEGIN
	SET @sal=0;#定义用户变量
	SELECT salary INTO @sal FROM employees WHERE last_name=emp_name;
	RETURN @sal;
END //
DELIMITER ;
SELECT ename_salary('Abel');


#3. 创建函数dept_sal() ,根据部门名,返回该部门的平均工资
DELIMITER //
CREATE FUNCTION dept_sal(dept_name VARCHAR(15))RETURNS DOUBLE
BEGIN
	DECLARE avg_sal DOUBLE DEFAULT 0.0;
	
	SELECT AVG(salary) INTO avg_sal
	FROM employees e
	JOIN departments d ON e.`department_id`=d.`department_id`
	WHERE d.department_name=dept_name;
	
	RETURN avg_sal;
END //
DELIMITER ;

SELECT dept_sal('Marketing');

#4. 创建函数add_float(),实现传入两个float,返回二者之和
DELIMITER //
CREATE FUNCTION add_float(f1 FLOAT,f2 FLOAT)RETURNS FLOAT
BEGIN
	SET @two_sum=f1+f2;
	RETURN @tuo_sum;
END //
DELIMITER;
SELECT add_float(1.1,2.2);

#####################################【流程控制练习题】############################################
#1. 创建函数test_if_case(),实现传入成绩,如果成绩>90,返回A,如果成绩>80,返回B,如果成绩>60,返回C,否则返回D
#要求:分别使用if结构和case结构实现
DELIMITER //
CREATE FUNCTION test_if_case(sc INT)RETURNS CHAR
BEGIN
	DECLARE ch CHAR;
	CASE
		WHEN sc>90 THEN SET ch='A';
		WHEN sc>80 THEN SET ch='B';
		WHEN sc>60 THEN SET ch='C';
		ELSE SET ch='D';
	END CASE;
	RETURN ch;
END //
DELIMITER ;
SELECT test_if_case(76);


#方式2:
DELIMITER //
CREATE FUNCTION test_if_case2(score DOUBLE)
RETURNS CHAR
BEGIN
	DECLARE ch CHAR;
	CASE
		WHEN score>90 THEN SET ch='A';
		WHEN score>80 THEN SET ch='B';
		WHEN score>60 THEN SET ch='C';
		ELSE SET ch='D';
	END CASE;
	RETURN ch;
END //
DELIMITER ;
#调用
SELECT test_if_case2(67);

#2. 创建存储过程test_if_pro(),传入工资值,如果工资值<3000,则删除工资为此值的员工,
#如果3000 <= 工资值 <= 5000,则修改此工资值的员工薪资涨1000,否则涨工资500
DELIMITER //
CREATE PROCEDURE test_if_pro(IN sal DOUBLE) 
BEGIN
	IF sal<3000 THEN
		DELETE FROM employees WHERE salary=sal;
	ELSEIF sal<=5000 THEN 
	        UPDATE employees SET salary=salary+1000;
	ELSE
		UPDATE employees SET salary=salary+500;
	END IF;
END //
DELIMITER ;
CALL test_if_pro(3500);


#3. 创建存储过程insert_data(),传入参数为 IN 的 INT 类型变量 insert_count,
#实现向admin表中批量插入insert_count条记录。
DELIMITER //
CREATE PROCEDURE insert_data(IN insert_count INT)
BEGIN
	DECLARE i INT DEFAULT 1;
	WHILE i<=insert_count DO
		INSERT INTO ADMIN(user_name,user_pwd)VALUE(CONCAT('Rose-',i),ROUND(RAND()*100000));
		SET i=i+1;
	END WHILE;
END //
DELIMITER ;
CALL insert_data(10);

#####################################【游标的使用练习题】############################################
/*
   创建存储过程update_salary(),参数1为 IN 的INT型变量dept_id,表示部门id;参数2为 IN的INT型
变量change_sal_count,表示要调整薪资的员工个数。查询指定id部门的员工信息,按照salary升序排列
,根据hire_date的情况,调整前change_sal_count个员工的薪资,详情如下。
*/
DELIMITER //
CREATE PROCEDURE update_salary(IN dept_id INT,IN change_sal_count INT)
BEGIN
	DECLARE int_count INT DEFAULT 0;
	DECLARE salary_rate DOUBLE DEFAULT 0.0;
	DECLARE emp_id INT;
	DECLARE emp_hire_date DATE;
	
	DECLARE emp_cursor CURSOR FOR SELECT employee_id,hire_date FROM employees
					WHERE department_id = dept_id ORDER BY salary;
					
	OPEN emp_cursor;
	
	WHILE int_countmgr_sal THEN
		SIGNAL SQLSTATE 'HY000' SET MASSAGE_TEXT='薪资高于领导薪资错误';
	END IF;
	
END //
DELIMITER ;

#####################################【触发器练习题】#############################################
#0. 准备工作
CREATE TABLE emps
AS
SELECT employee_id,last_name,salary
FROM atguigudb.`employees`;

#1. 复制一张emps表的空表emps_back,只有表结构,不包含任何数据
CREATE TABLE emps_back
AS
SELECT * FROM emps
LIMIT 0,0;

#2. 查询emps_back表中的数据
SELECT * FROM emps_back;

#3. 创建触发器emps_insert_trigger,每当向emps表中添加一条记录时,
#同步将这条记录添加到emps_back表中
DELIMITER //
CREATE TRIGGER emps_insert_trigger
AFTER INSERT ON emps
FOR EACH ROW
BEGIN
	INSERT INTO emps_back(employee_id,last_name,salary)
	VALUES(NEW.employee_id,NEW.last_name,NEW.salary);
END //
DELIMITER ;

#4. 验证触发器是否起作用
INSERT INTO emps(employee_id,last_name,salary)
VALUES(901,'zmj',9000);

SELECT * FROM emps_back;

##################################【窗口函数】############################################
/*
需要用到分组统计的结果对每一条记录进行计算的场景下,使用窗口函数更好。
【作用】
    类似于在查询中对数据进行分组,不同的是,分组操作会把分组的结果聚合成一条记录,
而窗口函数是将结果置于每一条数据记录中。

【分类】静态窗口函数和动态窗口函数
     静态窗口函数的窗口大小是固定的,不会因为记录的不同而不同;
     动态窗口函数的窗口大小会随着记录的不同而变化。

【语法结构】
    函数 over([partition by 字段名 order by 字段名 asc|desc]);
    函数 over 窗口名 ... window 窗口名 as ([partition by 字段名 order by 字段名 asc|desc]);
    
*/

#举例1
CREATE TABLE sales(
	id INT PRIMARY KEY AUTO_INCREMENT,
	city VARCHAR(15),
	county VARCHAR(15),
	sales_value DECIMAL
);
INSERT INTO sales(city,county,sales_value)
VALUES
	('北京','海淀',10.00),
	('北京','朝阳',20.00),
	('上海','黄埔',30.00),
	('上海','长宁',10.00);
SELECT * FROM sales;
/*
需求:现在计算这个网站在每个城市的销售总额、在全国的销售总额、每个区的销售额占
	所在城市销售额中的比率,以及占总销售额中的比率。
*/
SELECT city '城市',county '区',sales_value '区销售额',
SUM(sales_value) OVER(PARTITION BY city) '市销售额',
sales_value/SUM(sales_value) OVER (PARTITION BY city) '市比率',
SUM(sales_value) OVER() '总销售额',
sales_value/SUM(sales_value) OVER() '总比率'
FROM sales
GROUP BY city,county;


#举例2
CREATE TABLE goods(
	id INT PRIMARY KEY AUTO_INCREMENT,
	category_id INT,
	category VARCHAR(15),
	NAME VARCHAR(30),
	price DECIMAL(10,2),
	stock INT,
	upper_time DATETIME
);
INSERT INTO goods(category_id,category,NAME,price,stock,upper_time)
VALUES
	(1, '女装/女士精品', 'T恤', 39.90, 1000, '2020-11-10 00:00:00'),
	(1, '女装/女士精品', '连衣裙', 79.90, 2500, '2020-11-10 00:00:00'),
	(1, '女装/女士精品', '卫衣', 89.90, 1500, '2020-11-10 00:00:00'),
	(1, '女装/女士精品', '牛仔裤', 89.90, 3500, '2020-11-10 00:00:00'),
	(1, '女装/女士精品', '百褶裙', 29.90, 500, '2020-11-10 00:00:00'),
	(1, '女装/女士精品', '呢绒外套', 399.90, 1200, '2020-11-10 00:00:00'),
	(2, '户外运动', '自行车', 399.90, 1000, '2020-11-10 00:00:00'),
	(2, '户外运动', '山地自行车', 1399.90, 2500, '2020-11-10 00:00:00'),
	(2, '户外运动', '登山杖', 59.90, 1500, '2020-11-10 00:00:00'),
	(2, '户外运动', '骑行装备', 399.90, 3500, '2020-11-10 00:00:00'),
	(2, '户外运动', '运动外套', 799.90, 500, '2020-11-10 00:00:00'),
	(2, '户外运动', '滑板', 499.90, 1200, '2020-11-10 00:00:00');

SELECT * FROM goods;


#1.序号函数
#1.1 row_number()函数:能够对数据中的序号进行顺序显示
/*举例:查询goods数据表中每个商品分类下价格降序排列的各个商品信息。*/
SELECT ROW_NUMBER()OVER(PARTITION BY category_id ORDER BY price DESC)'row_num',
id,category_id,category,`name`,price,stock
FROM goods;

/*举例:查询 goods 数据表中每个商品分类下价格最高的3种商品信息。*/
SELECT * 
FROM (SELECT ROW_NUMBER() OVER(PARTITION BY category_id ORDER BY price DESC)'row_num',
	id,category_id,category,`name`,price,stock
	FROM goods
     )t
WHERE row_num<=3;


#1.2 rank()函数
/*rank():能够对序号进行并列排序,并且会跳过重复的序号,比如序号为1、1、3。*/

#举例:使用RANK()函数获取 goods 数据表中各类别的价格从高到低排序的各商品信息
SELECT RANK()OVER(PARTITION BY category_id ORDER BY price DESC)'row_num',
id,category_id,category,`name`,price,stock
FROM goods;

#举例:使用RANK()函数获取 goods 数据表中类别为“女装/女士精品”的价格最高的4款商品信息。
SELECT * 
FROM (SELECT RANK() OVER(PARTITION BY category_id ORDER BY price DESC)'row_num',
	id,category_id,category,`name`,price,stock
	FROM goods
     )t
WHERE category='女装/女士精品' AND row_num<=4;

#1.3 dense_rank()函数
/*对序号进行并列排序,并且不会跳过重复的序号,比如序号为1、1、2。*/
#举例:使用DENSE_RANK()函数获取 goods 数据表中各类别的价格从高到低排序的各商品信息。
SELECT DENSE_RANK()OVER(PARTITION BY category_id ORDER BY price DESC)'row_num',
id,category_id,category,`name`,price,stock
FROM goods;


#2.分布函数
#2.1 percent_rank()函数
/*求比例的。
   PERCENT_RANK()函数是等级值百分比函数。按照如下方式进行计算。(rank - 1) / (rows - 1)
其中,rank的值为使用RANK()函数产生的序号,rows的值为当前窗口的总记录数。
*/
#举例:计算 goods 数据表中名称为“女装/女士精品”的类别下的商品的PERCENT_RANK值。
#方式1
SELECT RANK()OVER w AS r,
PERCENT_RANK()OVER w AS pr,
id,category_id,category,`name`,price,stock
FROM goods
WHERE category_id=1 
WINDOW w AS (PARTITION BY category_id ORDER BY price DESC);
#方式2
SELECT RANK()OVER(PARTITION BY category_id ORDER BY price DESC) AS r,
PERCENT_RANK()OVER(PARTITION BY category_id ORDER BY price DESC) AS pr,
id,category_id,category,`name`,price,stock
FROM goods
WHERE category_id=1;

#2.2 cume_dist()函数
/*主要用于查询小于或等于某个值的比例。*/
#举例:查询goods数据表中小于或等于当前价格的比例。
SELECT CUME_DIST()OVER(PARTITION BY category_id ORDER BY price ASC)AS cd,
id,category,`name`,price
FROM goods;


#3. 前后函数
#3.1 lag('属性名',n)函数
/*返回当前行的前n行的'属性名'的值。*/
#举例:查询goods数据表中前一个商品价格与当前商品价格的差值。
SELECT id,category, NAME, price, pre_price, price - pre_price AS diff_price
FROM(SELECT id, category, NAME, price,LAG(price,1) OVER w AS pre_price
	FROM goods
	WINDOW w AS (PARTITION BY category_id ORDER BY price)
    )t;
 
#3.2 lead(expr,n)函数
/*返回当前行的后n行的expr的值。*/
#举例:查询goods数据表中后一个商品价格与当前商品价格的差值
SELECT id,category, NAME, price, pre_price, price - pre_price AS diff_price
FROM(SELECT id, category, NAME, price,LEAD(price,1) OVER w AS pre_price
	FROM goods
	WINDOW w AS (PARTITION BY category_id ORDER BY price)
    )t;
    
#4.首尾函数
#4.1 first_value(expr)函数
/*返回第一个expr的值*/
#举例:按照价格排序,查询第1个商品的价格信息。
SELECT id, category, NAME, price, stock,FIRST_VALUE(price)OVER w AS first_price
FROM goods
WINDOW w AS (PARTITION BY category_id ORDER BY price);

#4.2 last_value(expr)函数
/*返回最后一个expr的值*/
#举例:按照价格排序,查询最后一个商品的价格信息。
SELECT id, category, NAME, price, stock,LAST_VALUE(price)OVER w AS first_price
FROM goods
WINDOW w AS (PARTITION BY category_id ORDER BY price);


#5.其他函数
#5.1 nth_value(expr,n)函数
/*返回第n个expr的值。*/
#举例:查询goods数据表中排名第2和第3的价格信息
SELECT id, category, NAME, price,NTH_VALUE(price,2)OVER w AS second_price,
NTH_VALUE(price,3)OVER w AS third_price
FROM goods
WINDOW w AS (PARTITION BY category_id ORDER BY price);

#5.2 ntile(n)函数
/*将分区中的有序数据分为n个桶,记录桶编号。*/
#举例:将goods表中的商品按照价格分为3组。
SELECT NTILE(3) OVER w AS nt,id,category, NAME, price
FROM goods
WINDOW w AS (PARTITION BY category_id ORDER BY price);


#【总结】
/*窗口函数的特点是可以分组,而且可以在分组内排序。另外,窗口函数不会因为分组而减少
原表中的行数,这对我们在原表数据的基础上进行统计和排序非常有用。*/



##################################【公用表表达式】##########################################
/*
公用表表达式(或通用表表达式)简称为CTE(Common Table Expressions)。

    CTE是一个命名的临时结果集,作用范围是当前语句。CTE可以理解成一个可以复用的子查询,
当然跟子查询还是有点区别的,CTE可以引用其他CTE,但子查询不能引用其他子查询。所以,可
以考虑代替子查询。

【分类】
    普通公用表表达式 和 递归公用表表达式。
*/

#【1.普通公用表表达式】
/*
【语法结构】
    with CTE名称
    as (子查询)
    select|delete|update 语句;

    普通公用表表达式类似于子查询,不过,跟子查询不同的是,它可以被多次引用,
而且可以被其他的普通公用表表达式所引用。
*/
#举例:查询员工所在的部门的详细信息。
#方法1:子查询实现
SELECT * FROM departments
WHERE department_id IN (
				SELECT DISTINCT department_id 
				FROM employees
			);
#方法2:CTE实现
WITH cte_emp#相当于临时一个结果集,当成表看待即可
AS (SELECT DISTINCT department_id FROM employees)#注意此处没有结束,不要写分号
SELECT *
FROM departments d JOIN cte_emp e
ON d.`department_id`=e.`department_id`;


#【2.递归公用表表达式】
/*可以调用自己。
【语法格式】
   with recursive
   cte名称 as (子查询)
   select|delete|update 语句;
*/

#举例
/*  针对于我们常用的employees表,包含employee_id,last_name和manager_id三个字段。
如果a是b的管理者,那么,我们可以把b叫做a的下属,如果同时b又是c的管理者,那么c就是
b的下属,是a的下下属。*/
WITH RECURSIVE cte
AS(SELECT employee_id,last_name,manager_id,1 AS n FROM employees WHERE employee_id = 100 #种子查询,找到第一代领导
	UNION ALL
	SELECT a.employee_id,a.last_name,a.manager_id,n+1 FROM employees AS a JOIN cte
	ON (a.manager_id = cte.employee_id) #递归查询,找出以递归公用表表达式的人为领导的人
   )
SELECT employee_id,last_name FROM cte WHERE n >= 3

###################################【初级篇完结】##################################################

你可能感兴趣的:(数据库,mysql,数据库,学习)