【MySQL基础篇】

第一章:多表查询

  1. 连接的分类

1.1等值连接与非等值连接:

①等值连接:两个表之间连接时,以两边值相等作为连接条件就是等值连接

写法一:

SELECT a.student_name,a.student_age,b.class_name
FROM t_student,t_class
WHERE a.id = b.id;

写法二:

SELECT a.student_name,a.student_age,b.class_name
FROM t_student a JOIN t_class b
ON WHERE a.id = b.id;  (这种写法也叫内连接)
②非等值连接:两个表之间连接时,不以两边值相等作为连接条件就是等值连接

写法一:

SELECT a.employee_name,a.employee_salary,b.salary_level
FROM t_employee a,t_job_grade b
WHERE a.employee_salary between b.lowest_sal and b.highest_salary

写法二:还是使用ON的方式,比较简单我就不写了,其实这里的SQL并不重要,重要的只是让你们了解这几个连接的含义;

1.2自连接与非自连接

①自连接:自己连接自己

写法一:

SELECT emp.employee_id,emp.employee_name,mgr.employee_id,mgr.employee_name
FROM t_employee emp,t_employee mgr
WHERE emp.manager_id = mgr.employee_id

写法二:通过ON进连接,比较简单不写了

②非自连接:只要不连自己就是非自连接

1.3内连接与外连接(重点

① 7种JOIN连接:看下图

在SQL中,连接方式总共有7种:

②内连接:两个表连接,查询出两个表相交的部分就叫内连接

写法一:

SELECT emp.employee_id,emp.employee_name,dept.name
FROM t_employee emp,t_dept dept
WHERE emp.dept_id = dept.id

写法二:

SELECT emp.employee_id,emp.employee_name,dept.name
FROM t_employee emp INNER JOIN t_dept dept 
ON emp.dept_id = dept.id

inner 可以省略。

③左外连接:两个表相连,除了要将相交的部分查出来,还要将左表不相交的部分也查出来,如下图所示:

  • SQL92与SQL99

在讲解外连接之前,我需要先讲一下SQL语法的协议,SQL是一门语法,关于这门语法市面上主流的有两个版本的协议:SQL92与SQL99,SQL92定义了关于SQL的语法,如何实现联表查询,等一切跟SQL有关系的东西,都是由SQL92进行定义的,SQL92语法简单但是不利于人眼理解,SQL99语法复杂但是更便于程序员理解,SQL99是对SQL92的进一步优化,在这两个版本中,关于外连接的语法实现有区别,所以请看这两个不同的版本分别来实现外连接查询

写法一:SQL92版本实现左外连接查询:

现在我有一个员工表,有117条记录,其中有1个员工没有部门,此时我用正常的连表查询,是无法将没有部门的这个员工查出来的,如下就是正常的SQL语法,也就是内连接语法:

SELECT emp.employee_id,emp.employee_name,dept.name
FROM t_employee emp,t_dept dept
WHERE emp.dept_id = dept.id

图解:

下图员工表多了一个员工,相当于比部门表多出来一部分,要想将多的那个员工也查出来,SQL92的设计者们就想办法把部门表补起来,两边不就一样长了吗,一样长再查询时,就可以把左边多的员工查出来了呀,同时多查出来的这个员工,正好它的部门也会是null,因为我补起来的这个部门,根本就没有;

那如何补呢?在缺少的那个表处添加一个(+)即可,如下:

SELECT emp.employee_id,emp.employee_name,dept.name
FROM t_employee emp,t_dept dept
WHERE emp.dept_id = dept.id(+)
写法二:SQL99的语法
SELECT emp.employee_id,emp.employee_name,dept.name
FROM t_employee emp LEFT JOIN t_dept dept
ON emp.dept_id = dept.id

注意:mysql不支持SQL92语法,只支持SQL99,但是Oracle两种语法都支持

④ 右外连接:两个表相连,除了要将相交的部分查出来,还要将右表不相交的部分也查出来,如下图所示:

现在我有一个员工表,有117条记录,其中有1个员工没有部门,但是还有一个部门没有员工,此时我用正常的连接查询,是无法将员工的这个部门查出来的:

写法一:此时采用SQL92的语法应该是这样写
SELECT emp.employee_id,emp.employee_name,dept.name
FROM t_employee emp,t_dept dept
WHERE emp.dept_id(+) = dept.id
写法二:采用SQL99的语法应该这样写
SELECT emp.employee_id,emp.employee_name,dept.name
FROM t_employee emp RIGHT JOIN t_dept dept
ON emp.dept_id = dept.id
⑤满外连接:就是将全部都查出来,除了将量表相交部分查出来,还要将左边多出来的部分查出来,右表多出来的部分查出来
写法一:由上面的学习,我们可以很容易联想到满的单词是FULL,那么满连接的写法是不是只需要写成FULL JOIN ON就可以了呢?嘿,确实是的!
SELECT emp.employee_id,emp.employee_name,dept.name
FROM t_employee emp FULL JOIN t_dept dept
ON emp.dept_id = dept.id

但是一旦你将这个写法拿到mysql上执行,就会提示语法错误,这是因为Mysql虽然支持SQL99语法,但是很蛋疼的是,它不支持满连接的写法,Oracle却支持,此时就要学习Mysql中实现满连接的方式了:

UNION 操作符:

注意看下图:UNION的意思是左表和右表的数据我全都有,同时中间重叠的部分,我只要一份;

这个UNION底层会做什么呢:首先它会将左表中的数据全查出来,右表中的数据也会查出来,然后将两个表相叠加,中间产生的重叠部分会进行一次去重操作,只保留一份重复的数据:

UNION ALL操作符:

看下图,注意对比跟上图的区别,下图想表达的意思是:UNION ALL将左表跟右表的数据都查出来后,直接叠加放在一起,重复的部分就让它重复,反正我UNION ALL就是不对中间重复的这部分去重,这样的好处是啥?当然是减少了一次去重操作,效率更高了;

注意:在实际的开发过程中:由于UNION ALL所需要的资源更少,如果你明确知道两表合并后的数据并不会出现重叠的部分,或者你的业务需求不需要取出重复的部分,那么我们建议你使用UNION ALL以提高查询效率,同时日常开发中,确实也是UNION ALL用的更多;

注意:刚才我们不是说到使用UNION ALL会出现重叠的数据吗,那我们如何操作呢?

如果你直接将A的数据跟B的数据进行UNION ALL,确实会多出重复数据,就如下图:

但你可以另辟蹊径,我让下图这哥俩UNION ALL,不就可以拼凑出一个满外连接吗:

所以,写法二来了:

写法二:使用UNION ALL来完成满外连接
SELECT emp.employee_id,emp.employee_name,dept.name
FROM t_employee emp LEFT JOIN t_dept dept
ON emp.dept_id = dept.id
UNION ALL
SELECT emp.employee_id,emp.employee_name,dept.name
FROM t_employee emp RIGHT JOIN t_dept dept
ON emp.dept_id = dept.id
WHERE emp.dept_id != dept.id;

注意1:你别看使用UNION ALL的代码量一大串,但他就是比你直接使用UNION的效率更高;

注意2:看下图,UNION ALL上下查询语句的字段名必须相同,字段类型也必须相同,字段个数也必须相同,

因为这相当于两个结果集合并,要进行合并,一定得保证结果集中的字段个数,字段类型,字段名完全相同;

⑥ 实现下图的连接查询:

这个图相当于是先左外连接,再将中间部分去掉,你使用WHERE条件过滤一下就好了,所以SQL如下:

SELECT emp.employee_id,emp.employee_name,dept.name
FROM t_employee emp LEFT JOIN t_dept dept
ON emp.dept_id = dept.id
WHERE emp.dept_id != dept.id;
⑦实现下图的连接:更上例同理
SELECT emp.employee_id,emp.employee_name,dept.name
FROM t_employee emp RIGHT JOIN t_dept dept
ON emp.dept_id = dept.id
WHERE emp.dept_id != dept.id;
⑧实现下图的连接

思路:使用下面两个进行UNION ALL就可以了

写法:

SELECT emp.employee_id,emp.employee_name,dept.name
FROM t_employee emp RIGHT JOIN t_dept dept
ON emp.dept_id = dept.id
WHERE emp.dept_id != dept.id;
UNION ALL
SELECT emp.employee_id,emp.employee_name,dept.name
FROM t_employee emp LEFT JOIN t_dept dept
ON emp.dept_id = dept.id
WHERE emp.dept_id != dept.id;
  1. 多个表连接(超过2个)

拓展:下面这种写法也是可以的

  1. SQL99语法新特性

3.1自然连接:

自然的单词是NATURAL,连接的单词是JOIN,所以自然连接就叫NATURAL JOIN

①多连接条件:

在之前的学习中,我们的连接条件都是一个,比如下图:

ON后面跟着的就是连接条件,这里的连接条件只有一个,那多个连接条件如何写呢?

我在ON后面又用AND多加了一个连接条件,这就是多连接条件,只有满足

emp.dept_id = dept.id AND emp.manager_id = dept.manager_id 的数据才两表之间的重叠部分;

②自然连接的语法:
SELECT emp.employee_id,emp.employee_name,dept.name
FROM t_employee emp NATURAL JOIN t_dept dept

我们会发现,自然连接的语法,跟我们刚才写的多连接查询的结果是一样的,为什么呢?

这是因为SQL99的自然连接会帮你自动找到两张表中所有相同的自然,然后进行等值连接,

可以看到上图:我们的员工表和部门表中有两个完全相同的字段,manager_id和department_id,

所以你使用自然连接,跟你直接使用多连接查询时一样的等价的(注意,我这个图中是dept_id,这是我写错了,应该是department_id,但是我懒,我不想改了,你自己能懂意思就好)

自然连接的缺点:不灵活,你了解自然连接就行,还是建议自己写连接条件;

3.2 USING:

USING是用来替换连接条件ON的,在什么情况下能替换呢?看下图:

当你的连接条件,左表的连接字段,跟右边的连接字段完全一致时(名称相同,类型相同),就可以用USING替换ON emp.dept_id=dept.dept_id。

写成:

  1. 阿里巴巴规约

①超过三个表禁止用JOIN

②在进行多表连接时,需要JOIN的字段需要保持数据类型的绝对一致

③多表关联查询时,必须保证被关联的字段有索引;

第二章:单行函数

概念:

MYSQL的内置函数可以分为单行函数跟多行函数,本章专门讲解单行函数

单行函数:表示对一个数据做处理,返回一个结果的函数,都叫单行函数,

我输入一个参数,你返回一个参数,只对一行进行变换,参数可以是一个字段或者一个值

比如我输入一个1.1,你将其取整返回给我1,这就是单行函数。

多行函数:又叫聚合函数,聚集函数,分组函数,表示对一组数据做处理,返回一个结果的函数,都叫多行函数,

比如我输入一堆数字,你返回这些数字的和,这就是多行函数。注意:这里的分组函数不是GROUP BY,不要学混了;

  1. 数值函数

1.1 基本数值函数:

这里需要注意一点的就是:

①天花板函数:CEIL(x),CEILING(x),这两个都是天花板函数,等价的
  • 举例:CEIL(42.33)的结果就等于43,42.33的天花板,往上就是43,所以结果为43,注意这里的天花板并不是值无穷大,而是仅仅只大1个数;

②地板函数:FLOOR(x)
  • 举例:FLOOR(42.33)的结果就等于42,42.33的地板就是向下取整,所以结果为42;

FLOOR(-42.33)的结果是-43;

③随机函数:RAND(),RAND(x)
  • RAND()函数是返回0~1的随机数。

  • RAND(x)中的x表示随机因子,假如你现在有两个RAND(10),RAND(10),

SELECT RAND(10),RAND(10) FROM daul 此时你会发现,这两个RAND(10)每次随机出来的值都是一样的;

所以随机因子只要一样,那么两个Rand函数返回的值就是一样的;

>>>>> 科普一下MySQL的daul表

平时我们直接使用select语句,后面必须要跟着一个张表,否则这个select语法就是错的,但是有时候,比如我只是单纯的想实现一个简单功能,并不想从某个表中查数据,比如我SELECT concat('许海','666'),我只是想单纯的将字符串'许海'跟'666'拼接,完全没有必要FROM哪个表,没有daul表之前,你就完不成这个操作,daul表就是专门用来解决这个问题的,此时你就可以SELECT concat('许海','666') FROM daul;

④四舍五入函数:ROUND(x),ROUND(x,y)

举例:

  • SELECT ROUND(123.456,1) FROM DAUL :意思是保留一位小数,且四舍五入

  • 保留一位小数,就是保留到123.4,同时又因为4后面是5,再四舍五入,结果就是123.5;

  • SELECT ROUND(123.556,0) FROM DAUL :意思是保留零位小数,且四舍五入

  • 保留零位小数,就是保留123,有由于3后面是5,四舍五入就是124;

  • SELECT ROUND(123.556) FROM DAUL 结果就是124,它跟ROUND(123.556,0) 是等价的

⑤截断函数:TRUNCATE(x,y)
  • TRUNCATE(123.456,0)的意思是:将123.456截断成0位小数,翻译过来就是保留0位小数,这个函数是不会四舍五入的;

结果就是123;

  • TRUNCATE(123.456,1)结果就是123.4

>>>>> 单行函数的嵌套

举例:

SELECT TRUNCATE(ROUND(123.456,2),0)

这就是嵌套,结果是123

1.2 三角函数:

1.3 指数和对数

1.4 进制间的转换

  1. 字符串函数(重要

值得注意的是:

①CONCAT(s1,s2,...,sn),将s1,s2,... ,sn拼接为一个字符串

②CONCAT_WS(x,s1,s2,...,sn),将s1,s2,... ,sn拼接为一个字符串,同时每个字符串之间用字符串x进行连接

③INSERT(str,idx,len,replacestr)将目标字符串str从第idx位置开始数,len个字符长的子串替换为字符串replacestr。

  • 注意:在java中,字符串的索引是从0开始,但是在SQL中,索引是从1开始

  • 所以 INSERT('helloworld',2,3,'aaaaa')的结果就为'haaaaaoworld'

④REPLACE(str,a,b)用字符串b替换字符串str中所有的字符串a

⑤UPPSER(s)或UCASE(s),将字符串s中所有字母转成大写

⑥LOWER(s)或LCASE(s),将字符串s中所有字母转成小写

⑦LEFT(str,n),返回字符串str最左边的n个字符

⑧RIGHT(str,n),返回字符串str最右边的n个字符

  1. 日期和时间函数(重要

3.1获取日期,时间

注意:通过最下面两个函数获取的世界标准时间,跟我们的北京时间是有八小时的差值的。

举例:

结果:

3.2 日期与时间戳的转换(重要

3.3获取月份,星期,星期数,天数等函数

这里不太好理解,讲一下:

① YEAR(date)/MONTH(date)/DAY(date):

返回日期类型中的年/月/日,比如YEAR('2023-03-21 00:33:00')返回的就是2023,也是我当前写这篇博客的时间。。。

②HOUR(time)/MINUTE(time)/SECOND(time):同理,返回时、分、秒
③DAYNAME(date):返回date对应的星期几,结果是MONDAY,TUESDAY等英文单词
④ WEEKDAY(date):返回date对应的星期几,结果:周一是0,周2是1,周天是6

其他的不讲了,好理解了。

3.4日期操作函数:EXTRACT(type FROM date) ,返回指定日期date中特定的部分,type指返回的值。

  • type的取值:

举例:

结果:获取到了当前时间的秒数

3.5 时间和秒钟的转换函数

举例:

结果:

3.5这部分用的很少,甚至我都不该写;

3.6 计算日期和时间的函数(实际开发中有应用)

第一组函数:

举例1:

注意:INTERVAL是关键字,必须写,1和DATE_ADD共同表示加1,这里的type取的是YEAR,表示单位是年,所以是把当前时间往后加1年。

结果:

举例2:

我把+1,改成-1,就表示往前1年,

结果就是:

举例3:往当前时间后面加1分钟01秒,type就得用MINUTE_SECOND,1分钟01秒用字符串'1_1'表示,中间有个下划线,因为1_1不再是一个数值了,所以要加单引号。

SELECT DATE_ADD(NOW(),INTERVAL '1_1' MINUTE_SECOND) FROM dual;

第二组函数:比第一组函数功能更为丰富一些。

①ADDTIME(time1,time2):在time1的基础上加上time2,当time2为一个数字时,代表的是秒,time2可以为负数

举例:

  • ADDTIME(now(),1),表示对当前时间往后加1秒,这里1是数字,所以不用加单引号

  • ADDTIME(now(),'1:1:3'),'1:1:3'表示时分秒,整句的意思是对当前时间往后加1小时1分01秒

②SUBTIME(time1,time2):在time1的基础上减去time2,当time2为一个数字时,代表的是秒,time2可以为负数;

举例:

  • SUBTIME(now(),1),表示对当前时间往前减1秒

  • SUBTIME(now(),'1:1:3'),表示对当前时间往前减1小时1分01秒

③DATEDIFF(date1,date2),返回date1与date2之间的间隔天数,结果是date1减date2

举例:

  • DATEDIFF(now(),'2023-02-11')):意思就是计算当前时间跟'2023-02-11'之间的天数间隔

  • DATEDIFF(now(),'2023-02-11 23:22:12'):你传一个完整的包含时分秒的时间进去也是一样的

返回的也是天数

④TIMEDIFF(time1,time2),返回time1与time2之间的时间间隔,结果是time1减time2
  • TIMEDIFF(now(),'2023-02-11 23:22:12')):意思就是计算当前时间跟'2023-02-11 23:22:12'之间的时间间隔

返回的结果就是以时分秒的形式返回,如下图,就表示两个时间差了838个小时59分59秒:

⑤FROM_DAYS(N)返回从0000年1月1日起,N天之后的日期
⑥TO_DAYS(date)返回日期date距离0000年1月1日的天数
⑦LAST_DAY(date)返回date所在月份最后一天的日期
⑧MAKEDATE(year,n)针对给定年份,与所在年份中的天数返回一个日期

翻译过来就是:返回给定年份year中的第n天的日期

⑨MAKETIME(hour,minute,second)将给定的小时,分钟数,秒,组合成时间返回
⑩PERIOD_ADD(time,n)返回time加上n后的时间(这个别用,建议用其他的)

3.7日期的格式化和解析

fmt格式:

①格式化:日期格式 >>>>> 时间字符串

DATE_FORMAT(date,fmt)与TIME_FORMAT(time,fmt)就是将字符串格式化成日期格式

举例:

结果:

②解析:也就是逆格式化,时间字符串 >>>>> 日期格式

STR_TO_DATE(str,fmt)

举例:

结果:

注意:你在进行逆格式化时,你选定的格式一定要对了,否则是解析不出来的;

4.流程控制函数:

①iF(value,value1,value2)如果value的值为true,则返回value1,否则返回value2

②IFNULL(value1,value2)如果value1不为null,则返回value1,否则返回value2

③CASE WHEN 条件1 THEN 结果1 WHEN条件2 THEN 结果2.....[ELSE resultn] END,相当于java的if else

SELECT name,salary,CASE WHEN salary >= 1500 THEN '高富帅'
                        WHEN salary >= 8000 THEN '潜力股'
                        WHEN slaary >= 3000 THEN '大学生'
                        ELSE '草根' END as 别名
FROM t_people;

④CASE expr WHEN 常量值1 THEN 值1 WHEN 常量值1 THEN 值1.....[ELSE 值n] END,相当于java的switch case

练习1:查询部门号为10,20,30的员工信息,若部门号为10,则打印其工资的1.1倍,若部门号为20,打印1.2倍,部门号为30,打印1.3倍,其他部门打印1.0倍;
SELECT name,salary,dept_no,CASE dept_no WHEN 10 THEN salary * 1.1
                        WHEN 20 THEN salary * 1.2
                        WHEN 30 THEN salary * 1.3
                        ELSE salary * 1 END as 别名
FROM t_employee;
练习2:实现跟练习1同样的逻辑,但是不统计其他部门
SELECT name,salary,dept_no,CASE dept_no WHEN 10 THEN salary * 1.1
                        WHEN 20 THEN salary * 1.2
                        WHEN 30 THEN salary * 1.3
                        END as 别名
FROM t_employee
WHERE dept_no in (10,20,30);

你用where加一个限制条件就好了,就排除掉了其他部门,同时因为你排除掉了其他部门,case when这个条件里面就不可能出现dept_no等于其他部门的情况,所以你就可以把ELSE去掉了

5.加密解密函数

6.mysql信息函数:信息函数的作用是主要是帮助数据库开发,运维人员等更好的对数据库进行维护工作的。

第三章:多行函数(也叫聚合函数,聚集函数,分组函数,表示是对一组数据进行处理的函数)

一.常用的几个聚合函数

  1. AVG,SUM,MAX,MIN

这几个太简单了,不多说,

唯一值得注意的是:这几个函数都不会计算NULL值

  1. COUNT

①注意1:COUNT(字段A),COUNT里面填字段,表示统计这张表中,该字段有值的记录有多少行,假如某行中的该字段是NULL,则这一行不会被统计;
②COUNT(1)这就是不统计某字段有值的个数了,它的含义是:将表中的每一行都当做一个1查出来,把整张表查完后,有多少个1,就表示有多少行
③COUNT(*),这里的*代表该表中的所有字段,所以COUNT(*)在执行时,会查看表中每一行的每一列,只要这一行的任意一个列有值,就对其进行统计,如果某一行的所有列都是null,那么则不会进行统计

学到这里,下面的面试题就很好解答了:

面试题:COUNT(1)和COUNT(*),COUNT(字段)谁的速度更快?

答:显然是COUNT(1)最快,COUNT(字段)次之,COUNT(*)最慢

二. GROUP BY

  1. 关于GROUP BY的使用细节:

① SELECT中的非组函数的字段,必须出现在GROUP BY中,否则逻辑是错的;

举例:我现在有一个员工表,表中有字段department_id,job_id(工种id),salary工资,

我要统计每个部门下,不同工种的平均工资,我像下面这样写可以吗?

SELECT department_id,job_id,AVG(salary)   ( AVG(salary)这种就叫组函数字段 )
FROM employee
GROUP BY department_id;

这里很容易犯错误,这种写法是错的,我只是GROUP BY department_id,相当于只是对部门进行分组,有多少个部门,结果就有多少条记录,AVG(salary)也是计算的每个部门的平均工资,

你查出的结果就是这样:

但是此时你SELECT 后面明明就还有job_id字段,但是这个job_id字段到底取谁呢?一个部门中有多个工种,我到底取哪一个?显然这里的逻辑就出现错误了,此时执行应该报错;但很神奇的来了,mysql这样查是不会报错的,且还能查出来结果,结果如下,但我们可以肯定这个结果是错的,

这就是mysql跟Oracle的差距了,我们在oracle中执行这样的sql,会直接提示你的GROUP BY表达式错误:

②但是GROUP BY中出现的字段,不是必须出现在SELECT中。
③如果你按照多个字段分组,也就是GROUP BY后面跟了多个字段,这些字段的书写顺序是不影响结果的。

2.HAVING的使用

①当过滤条件中有聚合函数时,则此过滤条件必须声明在HAVING中,当过滤条件中没有聚合函数时,过滤条件可以声明在WHERE中也可以声明在HAVING中,都是一样的效果,但是建议声明在WHERE中,因为效率更高。
②WHERE和HAVING对比:
  • HAVING的使用范围更广,由于在查询语句结构中,WHERE是在GROUP BY之前执行,所以WHERE无法对分组结果进行筛选,HAVING是在GROUP BY之后,所以HAVING可以对GROUP BY之后的分组结果进行筛选,这个功能是WHERE无法完成的

  • 如果过滤条件中没有聚合函数时,这种情况下WHERE的执行效率要高于HAVING。

3.SQL底层执行过程

3.1执行过程
  • 第一步:先FROM :表示你要查哪一张表,如果这里是多张表,此时查出来的就是包含笛卡尔积的结果

  • 第二步:再执行ON:这一步是消灭上面的笛卡尔积,因为ON后面指定了连接条件,可以消灭笛卡尔积

  • 第三步:再执行LEFT/RIGHT JOIN:上一步是相当于是查询出了内连接的结果,这一步会判断你到底是左连接还是右连接,或者是内连接,如果是左连接,就会继续多查出左表的数据,反之同理,内连接同理;

  • 第四步:再执行WHERE:再对上面的结果进行条件过滤

  • 第五步:再执行GROUP BY:再执行分组

  • 第六步:再执行HAVING,将分组后的数据再过滤一遍

  • 第七步:再执行SELECT,一张表的字段有很多,有可能你并不想查询出全部的字段,所以此时执行SELECT,就可以筛选出那些字段是要展示的;

  • 第八步:再执行DISTINCT,执行去重

  • 第九步:再执行ORDER BY

  • 第十步:再执行LIMIT

问题1:为什么WHERE的执行效率要高于HAVING?

问题2:为什么WHERE中不能使用聚合函数作为过滤条件?

问题3:为什么在SELECT对某个字段取的别名,在WHERE中不能用,在GROUP BY中也不能用,在HAVING中也不能用,但是在ORDER BY中能用呢?

3.2 SQL底层执行原理

在上面的执行过程中,每执行一步,都会生成对应的虚拟表,往后执行,就是对虚拟表的修改过程,等执行到最后,得到的就是我们能看到的表数据。

第四章:子查询

一.子查询概念

需求:查询出员工表中谁的工资比Abel高:

思路:先查出Abel的工资,再查出其他人的工资跟Abel的工资做对比

方式1:分两步查询,这种方式的特点是效率低,因为要跟mysql server做两次tcp连接,两次tcp连接总比一次tcp连接慢。

SELECT salary
FROM employee
WHERE last_name = 'Abel';  结果为11000

SELECT last_name,salary
FROM employee
WHERE salary > 11000;

方式2:自连接

SELECT last_name,salary
FROM employee e1,employee e2
WHERE e1.salary > e2.salary
AND e2.last_name = 'Abel';

方式3:子查询

SELECT last_name,salary
FROM employee
WHERE salary > (SELECT salary
FROM employee
WHERE last_name = 'Abel');

二. 子查询分类

  1. 按位置分,可以分为内查询和外查询

举例:查询学生中分数高于小明的学生信息

SELECT name,age FROM t_student
WHERE score > (SELECT score FROM t_student WHERE name = '小明');

里面的SELECT score FROM t_student WHERE name = '小明'就叫内查询,

外面的SELECT就叫外查询;

  1. 从内查询返回结果的条目数来分:单行子查询,多行子查询

  1. 从内查询是否被执行多次来分:关联子查询,非关联子查询

  1. 单行子查询:内查询的结果只有一个,这就叫单行子查询;

比如:上面的例子,查询学生中分数高于小明的学生信息,内查询时查询出小明的分数,小明的分数只可能有一个,所以内查询的结果只有一个,这就是单行内查询。

①单行比较操作符

5. 多行子查询:内查询返回的结果有多个,就是多行内查询;

①多行比较操作符

由于多行子查询中内查询返回的结果有多个,所以此时你再用'='等于符号就不太合适了,所以就有了多行比较操作符

IN:不必说
ANY:表示任一,要跟单行比较操作符共同使用,和子查询返回的某一个值进行比较
ALL:表示所有,要跟单行比较操作符共同杀死用,和子查询返回的所有值进行比较
SOME:跟ANY是完全一样的用法,你掌握ANY即可
②练习:
  • 查询平均工资最低的部门id:

方式一:

//先查出每个部门的平均工资
SELECT AVG(salary) FROM t_department GROUP BY department_id
//然后我再AVG聚合函数外面再套一层MIN函数不就可以了吗?
SELECT MIN(AVG(salary)) FROM t_department GROUP BY department_id

照这个思路应该是可以的,但是执行,就直接报错,如下,这是因为MYSQL不支持聚合函数外面再套聚合函数(也叫多行函数,分组函数),但是Oracle中支持;

但是MYSQL中支持单行函数外面套单行函数。

方式二:在FROM中写子查询

将SELECT AVG(salary) FROM t_department GROUP BY department_id的结果看成一张表

在关系型数据中,查询语句并不是一次性就得到结果,而是经过一定的SQL执行过程,这个中间数据是呈现在一个虚拟表中的,随着SQL的执行,虚拟表不断变化,最终呈现出我们看到的样子,比如我们之前学的查询SQL执行过程,它会先执行FROM找到要从哪张表或者哪几张表查,这个时候虚拟表就是一个带有笛卡尔积的很大的表,然后再执行ON消除笛卡尔积等等。

所以你在写解这道题目时也可以将SELECT AVG(salary) FROM t_department GROUP BY department_id的结果看成一张临时的表,你给AVG(salary)取一个别名,外查询就可以用了,所以写法如下:

不过要注意的是,这种将子查询看做临时表的方式,一定要给这临时表取别名,否则会报错。

上面还写没完,只不过是找到了最低的平均工资,你还需要找到部门中平均工资等于这个最低工资的部门,这就很简单了;

SELECT department_id,department_name FROM t_department
GROUP BY department_id HAVING AVG(salary) = (
    SELECT MIN(avgsalary) FROM (
        SELECT AVG(salary) as avgsalary FROM t_department GROUP BY department_id
    ) faketablename;
)

方式三:使用多行操作符ALL

SELECT department_id,department_name FROM t_department
GROUP BY department_id HAVING AVG(salary) <= ALL (
    SELECT AVG(salary) as avgsalary FROM t_department GROUP BY department_id
)

多行子查询的空值问题

6.相关子查询:

6.1不相关子查询举例:
  • 查询员工表中大于公司平均工资的员工

SELECT * FROM t_employee WHERE salary > (SELECT AVG(salary) FROM t_employee )

这个例子就是不相关子查询:对于外层查询来说,每进行一行比较,内查询的结果就是一样的,所以是不相关子查询;

6.2相关子查询举例:
  • 题目1:查询员工表中工资大于本部门平均工资的员工姓名,工资,和部门id

方式一:

SELECT name,salary,department_id
FROM employee e1
WHERE salary > (
    SELECT AVG(salary)
    FROM employee e2
    GROUP BY department_id
    WHERE department_id = e1.department_id
)

在内查询中使用到了外查询的表,这就是相关子查询;

方式二:在FROM中写相关子查询

  • 题目2:在ORDER BY中使用相关子查询

现在有一个员工表,部门表,请查询员工的id,salary,最后按照department_name进行排序(部门名称是在部门表中)

SELECT e.id,e.salary 
FROM department e
ORDER BY (
    SELECT department_name 
    FROM department d
    WHERE e.department_id = d.id
) ASC;

写这道题目的目的是:ORDER BY后面不仅仅能跟字段名,还可以跟子查询的结果,上面的这个例子中子查询的结果就是真正的部门名称

6.3结论:在哪些位置可以写子查询呢?

在SELECT,FROM,WHERE,HAVING,ORDER BY中能写,

在GROUP BY和lmit中不能写,

也就是除了GROUP BY和lmit中不能写,其他地方都能写

6.4 EXISTS关键字和NOT EXISTS

题目1:查询公司管理者的员工id,姓名,岗位id,部门id

方式一:自连接

方式二:子查询

方式三:EXISTS

题目2:

第五章:DDL,DML,DCL

一、SQL的分类

SQL的分类是基础,请记住。

二、保留字

当你在对数据库,字段,表名等命名时,建议不要使用保留字命名,比如你将某个表命名成order,而order是保留字,你再执行sql时就会报错,

如果你非要使用,那么在执行sql时,可以将order加上着符,如:`order` ,注意:不是单引号,而是键盘上波浪键上的那个符号;

三、Mysql中的数据类型:

  1. mysql中数据类型:

  • double(10,2):表示的意思是double类型,总共运行10位数字,小数点后允许有2位小数,那么小数点前面就最多允许有8位数字

  1. 阿里巴巴规约

  • 主键id的类型,需要为bigint unsigned,unsigned表示无符号,也就是不能为负数,将bigint设置为无符号可以有效的提高bigint能表示的范围,因为主键不可能有负数。

  • 不仅仅是主键,只要其他字段是数字类型,都要设置为unsigned。

  1. 一些常见的字段选择类型的建议:

四、库的操作:

  1. 创建数据库:

①创建数据库
create database 库名;
②创建数据库并指定字符集与比较规则
create database 库名  character set '字符集名称' collate '比较规则名称';

如果你不指定,默认就是utf8mb4与utf8mb4_0900_ai_ci

案例:

create database 库名  character set 'utf8mb4' collate 'utf8mb4_0900_ai_ci';
③创建数据库前先判断该库是否存在,如果不存在就创建,如果存在就什么都不做
create database if not exists 库名  character set '字符集名称' collate '比较规则名称';
  1. 查看所有库

show databases
  1. 切换数据库:

use 库名
  1. 查看当前正在使用的数据库

select database();
  1. 查看当钱库下的所有表

show tables;

查看指定库下的所有表:

show tables from 库名;
  1. 查看数据库的创建信息

show create database 库名
或
show create database 库名\G
  1. 修改数据库

① 更改数据库的字符集
alter database 库名 character set '新的字符集名称';
② 删除数据库
  • 方式一:

drop database 库名;
  • 方式二:

drop database if exists 库名 (推荐)

五、表的操作

  1. 创建表,并指定存储引擎和字符集

方式一:
create table [if not exists] 表名(
   字段1 数据类型 [约束条件] [默认值] [备注],
   字段2 数据类型 [约束条件] [默认值] [备注] 
)engine = InnoDB,characterset = 'utf8mb4'

注意1:上面的语法中,中括号【】括起来的表示可选项,并不是必填项;

注意2:在创建表时,如果不指定存储引擎,默认就是innodb,不指定字符集,默认跟库使用的字符集保持一致;

约束条件:如unique,primary key,not null,分别就是唯一约束,主键约束,非空约束,

还有一个约束条件为unsigned | signed,unsigned表示非负数,这个比较实用,它可以显著提高数值类型能表示的范围;

默认值:default 值

备注:comment '备注内容'

方式二:基于现有的表来创建表
create table 表名 as select * from 旧表名;

将旧表完全复制一遍,包括数据与数据结构。

①技巧一:

注意:你也可以针对虚拟表创建表,你使用select语句时,会得到虚拟表,你通过create table as 虚拟表就能创建出虚拟表的实体表;

②技巧二:

假如我只想复制某张表的所有列,不想要数据,怎么实现呢?

只需要在后面加上where过滤条件即可,只要给它一个不可能达到的条件,那么查出来就没有数据,只剩下数据结果,

比如下面的department_id > 10000000就是我举的例子,部门id不可能达到10000000;

create table 表名 as select * from 旧表名
where department_id > 10000000;
或者
create table 表名 as select * from 旧表名
where 1 > 2;
  1. 查看表的基本信息

DESC 表名;

结果如下:这句话的作用就等同于navicat的ctrl+D编辑表页面。

  1. 修改表

①为表添加一个字段
alter table 表名 add [column] 字段名 数据类型 [约束条件] [默认值] [备注] [first | after 某字段名];

注意1:最后的first表示你本次添加的字段会在表中字段的第一个位置,

after 某字段名,表示你本次添加的字段会在这个字段后面;

注意2:column关键字可要可不要,但是我建议你还是写上吧。

②修改一个字段
  • 修改字段的数据类型

alter table 表名 add [column] 字段名 数据类型 [约束条件] [默认值] [备注] [first | after 某字段名];
  • 修改字段的长度

alter table 表名 modify [column] 字段名 varchar(20); 将字段的长度修改成了20
  • 修改字段名称

alter table 表名 change [column] 旧字段名 新字段名 字段的类型;

注意1:最后你必须把字段类型跟上,否则会报错,也就是说你只是改个字段名,就算数据类型前后不变,你在最后也必须把数据类型带上;

注意2:change除了能修改字段名,还可以修改字段的类型,长度,约束条件,等等都能修改,但是modify不能修改字段名

③删除一个字段
alter table 表名 drop column 列名;
  1. 删除表

drop table if exists 表名;
  1. 清空表

truncate table 表名;
  1. 重命名表

方式一:
rename table 旧表名 to 新表名;
方式二:
alter table 旧表名 rename [to] 新表名;

六、DCL操作

  1. COMMIT操作

表示提交,一旦提交后,数据就会被永久存储,提交后无法回滚,你要`回滚`只能删除;

  1. ROLLBACK操作

执行ROLLBACK语句,则可以进行数据回滚,但无法保证回滚一定成功。

回滚到什么程度呢?最后一次commit之后的操作你都能回滚,但是之前的你回滚不了,你非要`回滚`只能删除;

  1. 通过truncate 和 delete from的区别引出DDL,DML在回滚上的区别

  • truncate:一旦执行,无法使用rolllback,及时你使用rollback语句也无法恢复数据。它属于DDL。

  • delete from:执行delete语句后,默认是无法回滚,但如果你在删除之前SET autocommit = false,就可以回滚。它属于DML。

注意:

  • DDL的操作一旦执行,就不可回滚

  • DML的操作执行后,默认情况下,也是不可回滚的,但是我们可以修改某参数,在DML语句执行前,修改这个参数SET autocommit = false就可以让DML支持回滚,默认情况下autocommit这个参数是true。

  • 但是DDL操作,及时我们修改autocommit = false,DDL操作也还是无法回滚的。

  • 原因有2:

  1. DDL操作之后会有一次自动的COMMIT操作,它的COMMIT操作是不受aotocommit这个参数的控制的,我们无法改变

  1. DML在执行时,mysql底层会有事务,mysql server会一遍操作,一边备份数据(记录事务日志),以便后续进行回滚,但是DDL在执行操作时,根本就没有考虑事务的事,所以DDL执行的效率的更高,因为它不占用事务日志资源。

案例演示:回滚操作

SET autocommit = false;  //先关闭自动提交
DELETE FROM employees; //再删除全表
ROLLBACK;                //再执行回滚,就能撤销刚才的删除操作

案例演示:提交操作

SET autocommit = false;  //先关闭自动提交
DELETE FROM employees; //再删除全表
COMMIT;                   //再手动提交一次 
ROLLBACK;                //再执行回滚,你就会发现不管用了,因为你前面的删除操作已经commit了

4.面试题:假如一张表中的数据你不想要了,你到底是使用truncate还是使用delete from呢?

回答这个问题,请将我们第3点讲的内容都回答出来,不要漏。

七、MYSQL8新特性之一:DDL原子化

在我们刚学习的知识中,DDL是不支持事务的,但是在MYSQL8中InnoDB已经支持对DDL的事务了,也就是说你对MYSQL8的InnoDB进行DDL操作是可以回滚的。

以下我分别在mysql5.7,mysql8中举例子:

mysql5.7:

//创建test库,设置字符集为utf8mb3,mb3的意思是1~3个bit表示一个字符,
//如果是英文,数字,就是用1个bit表示,如果是中文就用3个bit表示;
//utf8mb4中mb4的意思是1~4个bit表示一个字符,mb4能比mb3表示更多字符,如冷门字,生僻字,表情符号等;
create database test character set 'utf8mb3' collate 'utf8mb3_0900_ai_ci';

use test;

//再创建一个名为table1的表
create table table1(
    id bigint unsigned primary key,
    age tinyint unsigned
)
//我们再执行这条删除语句,很显然,table2这张表是不存在的,
//当这句话执行到table1,它就会把table1删除,但是table2不存在
//这条DDL语句就会报错。
drop table table1,table2;

//当我们再查看当前库中的表时,我们会发现table1这张表被删掉了,且我们无法回滚。
//及时drop table table1,table2执行出现了异常也没有回滚。
show tables from test;

但是到了mysql8.0中就不一样了,完全一样的流程,当drop table table1,table2执行出错时,它会把已经删除的table1给恢复回来。

八、增删改操作

增删改属于DML操作。

  1. insert

① 一条条添加
insert into 表名(字段1,字段2,字段3) values (值1,值2,值3);
insert into 表名 values (值1,值2,值3);
②批量添加
insert into 表名(字段1,字段2,字段3) values (值1,值2,值3),(值1,值2,值3),(值1,值2,值3);
insert into 表名 values (值1,值2,值3),(值1,值2,值3),(值1,值2,值3);
③将查询结果插入到表中
insert into 表1(字段1,字段2) select 字段1,字段2 from 表2;

注意1:这种方式不需要在select前面加values;

注意2:被插入表的字段名,字段顺序,要跟select查出来的字段名,字段顺序保持一致;

注意3:在插入时表1时,要注意字段长度的问题,假如字段1在表2中是varchar(20),但是在表1中是varchar15,那么很有可能表2的数据是插不进去表1的,所以你一定要注意字段长度的问题。

  1. update

  1. delete

九、Mysql8新特性:计算列

  1. 概念:

某一个列的值是通过其他列的计算得到的,比如a列值为1,b列值为2,c列不需要我们手动插入,定义a+b的结果为c的中,那么c就是计算列;

  1. 创建表时指定计算列

create table 表名(
    a bigint unsigned,
    b bigint unsigned,
    c bigint generated always as (a + b) virtual //注意:virtual这个词一定要加
)

c列就作为了计算列,它的值始终等于a+b的值

第六章:Mysql数据类型

在上一章,我提到过mysql数据类型,但是没有细致讲解,细致对mysql的所有数据类型进行完整的讲解

解释一下几个类型:

  • ENUM:枚举类型,存进来其实也算`字符串`,只不过枚举是只能从几个`字符串`中选一个,比如我设定了一个叫季节的字段,那么这个字段的值就只能从春夏秋冬四个值中任选一个了,不能写成其他的值;

  • 二进制字符串类型:虽然这里写的是`字符串`,但他并不是字符串,而是二进制的数据,假如你要在mysql存储音频,视频等文件,你就可以选择二进制字符串类型来存储,通常会用其中的BLOB类型。

  • JSON类型:

  • 空间数据类型:跟地图相关的数据可能会用到空间数据类型。

一、创建字段时设置字符集

在上面,我们学习过,创建数据库时能指定字符集,创建表时也能指定字符集,其实创建字段时也能指定字符集,

但我们通常只会在创建数据库时指定一个字符集,然后下面的所有表都使用同一个字符集。

create table tableName(
    id bigint unsigned primary key auto_increment,
    name varchar(20) character set 'gbk'
)

二、整数类型:

三、小数类型:

DOUBLE:

FLOAT:

注意:在mysql中对DOUBLE和FLOAT使用unsigned修饰没有任何意义,它不会改变float,double的表示范围,这跟整数类型不一样,你死记即可;

四、日期类型

修改mysql的时区:

set time_zone = '+8:00';

五、字符串类型

  1. 面试题:CHAR跟VARCHAR的区别

  • char是固定长度,长度范围是:0~255,比如你设置字段类型为char(20),就算存进来的值没有达到20的长度,也会占用20的长度;

  • varchar是可变长度,长度范围是0~65535,varchar字段占用的空间是实际长度+1个字节,因为会多用1个字节来记录到底使用了多少长度;

第七章:存储过程与存储函数

一、存储函数的概念:

存储过程:

二、存储过程:

1.概念:预先将一组sql逻辑编译好,放到mysql server中存储起来,等你下一次要调用这个SQL逻辑时,只需要发送特定的命令就可以执行该逻辑了;

2. 存储过程的好处:

①因为SQL语句已经提前编译好了(也就是被提前解析成了二进制文件),你不用再像以前那样每次都新写一个SQL,然后把SQL字符串通过网络IO传递给mysql server,你只需要传递一个命令给mysql server,减少了IO通信传递的消息数量,同时也不需要server再对sql进行解析,优化,编译,因为mysql server早就已经提前做好了,所以能提高我们的效率

②提高了sql语句的重复利用性,你可以把它想象成在java中写了一个方法,你要使用某个特定功能时,你只需要调用一下即可;

③减少了SQL语句暴露在网络中的风险,提高了系统的安全性,因为上面第一点才提到,你调用mysql server根本就不是传递sql过去了,而是传指令;

3.存储过程的分类:

4.存储过程语法:

CREATE PROCEDURE 存储过程名称(IN | OUT | INOUT 参数名 参数类型 ...)

5.创建存储过程;

5.1举例:

将SELECT * FROM employees这句SQL封装成一个存储过程,以便我们以后使用

按照存储过程的语法,那么应该这么写:

CREATE PROCEDURE my_first_procedure() --这个存储过程不需要传参数,所以括号里我们什么都不写
BEGIN                                  
    SELECT * FROM employees;
END

我们这么写语法上是没错的,但是我们一执行,立马就会报错,为什么呢?

因为你看这里的分号,表示SQL语句的结束,当myssql server解析到分号时它就会认为你这句sql结束了,但是此时你的END关键字根本没有被读进去,

就会导致mysql server收到一个不完整的存储过程语法,所以会报错;

这里正确的做法是:使用DELIMITER关键字。

5.2 DELIMITER关键字

语法:DELIMITER $

DELIMITER的作用是:将SQL中的结束符替换成后面跟着的字符,比如DELIMITER $的意思,就是将SQL结束符---分号' ; '改成用美元符号' $ '表示结束,

但我们一般是不会替换的,目前能用到的场景就是在创建存储过程时用到,不过你在使用DELIMITER时,一定要先在用完之后,将结束符改回分号;

5.3 正确的创建存储过程案例:

我们再接着完善上面的例子,正确的写法如下:

例2:创建一个无参数无返回值的存储过程,返回所有员工的平均工资 (简单)

delimiter $
create procedure avg_salary()
begin
    select avg(salary) from employees;
end$
delimiter ;

调用我们创建好的存储过程:CALL 存储过程名称;

call avg_salary();

例3:创建一个无参有返回值的存储过程:获取员工表的最低工资,并将该最低工资通过out返回

调用无参有返回值的存储过程:CALL 存储过程名(@变量名)

说明:

>>> 变量名这里填你在存储过程中out后面定义的变量名,

>>> 然后在变量名中加@,加了@的变量,表示是用户自定义变量,mysql中还有会话变量,全局变量等,下一章再讲。

所以我们上面写的无参有返回值的存储过程,就应该这么调用

这句话的意思,就是调用我们刚才写的存储过程,又由于我们上面的存储过程中写了into minsalary,所以最后存储过程的结果就到了变量minsalary中,而不是直接像select语句那样在控制显示给你,

所以你要查看这个结果的话,必须再调用一下这个变量,就可以查看到最小的工资了,如下:

select minsalary;

例4:创建有参数无返回值的存储过程:查询员工表中某个员工的工资

调用有参无返回值的存储过程:CALL 存储过程名(变量值)

调用方式一:

call salary('许海');

我们调用后,就能在控制台看到结果:

这里要注意一下了:为什么我们例2中,调用存储过程在控制台看不到结果呢?

因为有返回值的存储过程使用了into将结果封装到了变量中,这个变量被存到了mysql server中,而不是直接输出到控制台,所以你要查看结果,

只能再次调用一次这个变量。

调用方式二:

先用一个变量把'许海'装起来,然后再将变量传到自定义存储过程中

set @empname = '许海';  -- 也可以写成 set @empname := '许海';     := 是mysql中明确表示赋值的符号;
call salary(@empname);

结果同样是11000。

注意:一个@表示用户自定义的变量,两个@@表示系统变量

例5:创建有参有返回值的存储过程:查看员工表中某员工的工资,用in接收参数,并用out将结果封装到变量中

delimiter $
create procedure salary(in empname varchar(20),out empsalary double)
begin
    select salary into empsalary from employees where name = empname;
end$
delimiter ;

调用有参有返回值的存储过程:

set @empname := '许海'; 
call salary(@empname,@empsalary);  也可以写成call salary('许海',@empsalary);
select @empsalary

例6:带有inout的存储过程:查询某个员工领导的姓名,并用intou参数输入员工姓名,输出领导姓名

delimiter $
create procedure show_manager_name(inout empname varchar(20))
begin
    select name into empname
    from employee
    where employee_id = (
        select manager_id from employee
        where name = empname;
    )
end$
delimiter ;

从inout的用法可以看出:inout是用同一个变量来接收参数,返回参数,入参跟出参类型都可能是不一样的,所以我不喜欢用inout

还是in,out比较明确;

调用带inout的存储过程:

set @empname := '许海';
call show_manager_name(@empname);
select @empname;

6.存储过程的缺点:

三、存储函数:

1.概念:

我们之前学习的单行函数,多行函数(聚合函数,聚集函数,分组函数)都是mysql为我们定义好的函数,我们直接拿来使用即可,

存储函数其实就是自定义函数;

2.自定义函数的语法:

create function 函数名(参数名 参数类型...)
returns 返回值类型
[ characteristics ...]
BEGIN
    函数体
    return 结果;  --函数体中肯定有return 语句,返回的内容可以用select查询;
END

说明:

  • 参数列表:存储过程中指定参数为in,out,或者inout都是可以的,但是在存储函数中,只能指定参数为in参数,如果你不指定,那么它默认也是in参数,反正就是不能改,并且由于存储函数中只能是in类型,所以创建存储函数时可以不写in;

  • returns 返回值类型:必须写,这句是用来说明本函数返回值的类型的,

它是跟函数体中的 return 结果;配合的,return 结果只是表示具体返回的结果是多少,但是不知道返回的类型,所以"returns 返回值类型"就是用来说明返回值类型是什么的;

  • 【 characteristics 】是用来表示对本函数的约束的:

3. 创建存储函数

案例1:创建一个存储函数:该函数的作用是查询员工表中名为`许海`的员工的邮箱,并返回;

错误的写法:当我们这些写时,会发现会报错,因为在创建存储函数时,它要求我们必须加上对函数的约束characteristics,

但是在创建存储过程时就不需要加,这是它们两者的不同点之一;

delimiter $
create function get_email_by_name()
returns varchar()
begin
    return (select email from employee where name = '许海');
end$
delimiter ;

正确的写法1:加上characteristics

delimiter $
create function get_email_by_name()
returns varchar(20)
    DETERMINISTIC
    CONTAINS SQL
    READS SQL DATA
begin
    return (select email from employee where name = '许海');
end$
delimiter ;

正确的写法2:

先设置set GLOBAL_log_bin_trust_function_creators = 1,这个全局变量的意思是:让mysql server信任我们创建的存储函数,

mysql server默认是不信任的,所以要求我们加上characteristics,修改成1后,就不需要加了;

set GLOBAL_log_bin_trust_function_creators = 1;

delimiter $
create function get_email_by_name()
returns varchar(20)
begin
    return (select email from employee where name = '许海');
end$
delimiter ;
如何调用函数:使用select调用
select 函数名();
案例2:创建一个函数:传入一个参数name1,查询员工表中名称为name1的员工的email
set GLOBAL_log_bin_trust_function_creators = 1;

delimiter $
create function get_email_by_name(name1 varchar(20))
returns varchar(20)
begin
    return (select email from employee where name = name1);
end$
delimiter ;

4.存储过程与存储函数的区别:

5.存储过程、存储函数的删除:

drop function | procedure if exists 函数名|存储过程名; //注意:函数名|存储过程名后面不用加()

6. 存储过程的优缺点:

优点:

缺点:

第八章:变量、流程控制、游标

一、变量的概念

在mysql的存储过程与存储函数中,可以用变量来存储查询或计算的中间结果数据,

在Mysql中,变量分为系统变量和用户自定义变量。

二、系统变量:

  1. 系统变量的分类:全局变量、会话变量
  1. 查看全局变量:
  • 查看所有系统变量

show global variables;
  • 查看指定系统变量

show global variables like '%名称%';
select @@global.变量名;
  1. 查看会话变量:
  • 查看所有会话变量

show session variables;
show variables; //如果不指明serssion,默认就是查看会话变量;
  • 查看指定会话变量

show session variables like '%名称%';
或
select @@session.变量名;
或
select @@变量名; //注意,没指定session或者global的话,系统会先去会话变量中找,找不到就再去系统变量中找;
  1. 修改全局、会话变量

方式一:

//修改全局变量
set @@global.变量名 = 值;
或
set global 变量名 = 值;
//修改会话变量
set @@session.变量名 = 值;
或set @@变量名 = 值;
或set session 变量名 = 值;

方式二:还可以修改mysql的配置文件my.ini。

注意:通过命令行set的方式,只针对于当前正在运行的mysql实例有效,重启后就失效了,换句话说:mysql服务重启后,它还是会去读my.ini里面的配置;

三、用户变量:

  1. 用户变量的分类:会话用户变量,局部变量
  • 会话用户变量:只对当前连接会话有效,连接断开,则该会话的会话用户变量失效

  • 局部变量:只在BEGIN,END语句块中有效,局部变量只能在存储过程和函数中使用

注意:前面我们说过一个@表示用户变量,其实准确的说,应该是会话用户变量,并不代表局部变量,因为局部变量只在存储过程,存储函数中有效,根本用不上@符号;

  1. 会话用户变量的定义

方式一:

set @a := 1;

方式二:使用':='或into关键字,将sql语句的结果赋给用户变量,

这种方式必须使用select,不能用set,因为要先将sql的结果查出来,才能设置,所以要用select;

//意思是将员工数量赋值给会话用户变量@a
select @a := count(1) from employees;
或
select count(1) into @a from employees;
  1. 局部变量的定义

在存储函数,存储过程中,可以使用DECLARE语句定义一个局部变量,DECLARE仅仅在BEGIN...END代码块中有效,并且只能放在BEGIN...END代码块中的第一句

  • 局部变量声明格式

  • 局部变量的赋值:

  • 赋值方式一:

set 变量名 = 值 或者 set 变量名 := 值

  • 赋值方式二:使用into关键字赋值

select 字段名或表达式 into 变量名 from 表;

  • 案例

set GLOBAL_log_bin_trust_function_creators = 1;

delimiter $
create function get_email_by_name()
returns varchar(20)
begin
    declare a int default 0;
    declare b int;
    declare emp_name varchar(20);
    //如果两个变量类型相同,也可以写成
    //declare a,b int default 0;
    select last_name into emp_name from emoloyee where emp_id = 1;
    return emp_name;
end$
delimiter ;

四、程序出错的处理机制

在各种语言中都有针对异常的处理机制,SQL语言中也有此类机制。

  1. 定义条件

定义条件就是:我们实现定义好SQL程序执行过程中可能会遇到的问题。

  1. 处理程序

处理程序就是:我们针对程序执行过程中可能遇到的问题所专门的处理方式。

  1. 错误演示:
delimiter $
create procedure update_data_no_condition()
begin
    set @x = 1;
    update employee set email = null where name = 'able';
    set @x = 2;
    update employee set email = 'sdsd' where name = 'able';
    set @x = 3;
end$
delimiter ;

背景:员工表的email表有非空约束,所以当执行到update employee set email = null where name = 'able'这句时一定会报错,报错如下:

4.为了执行过程中不报错,解决方案如下:

①先定义条件:

定义条件的语法:

declare 错误名称 condition for 错误码(或错误操作)

错误码说明: 错误码有两种,

  • MySQL_error_code:表示数值类型错误代码

  • sqlstate_value:表示长度为5的字符类型错误代码

你在错误码这里填1418或者HY000都可以表示这个错误类型,不同的错误,填不同的值;

举例:对Mysql的"ERROR 1148(42000)"定义条件:

declare Field_Not_be_NULL condition for 1048;
或
declare Field_Not_be_NULL condition for sqlstate ‘42000';//因为'42000'可能被隐式转换为整数类型,为了避免歧义,所以在前面加上一个sqlstate避免歧义;

②再定义处理程序

declare 处理方式 handler for 错误类型 处理语句
  1. 正确演示:
delimiter $
create procedure update_data_no_condition()
begin
    //意思是:如果出现1048的异常,就continue继续执行,并声明一个变量prc_value = -1;
    //这是没有用定义条件的用法;
    //我感觉定义条件的方式很鸡肋,我就不举例了,不定义条件,直接用错误码更好;
    declare continue handler for 1048 set @prc_value = -1;
    set @x = 1;
    update employee set email = null where name = 'able';
    set @x = 2;
    update employee set email = 'sdsd' where name = 'able';
    set @x = 3;
end$
delimiter ;

五、流程控制

  1. if语法

举例:

delimiter $
create procedure test(age int)
begin
    if age > 40
        then select '中老年';
    elseif age > 18
        then select '靑年';
    else
        select '少年';
    end if;
end$
delimiter ;

  1. 循环结构之loop

loop语法:

举例1:

delimiter $
create procedure test()
begin
    declare num int default 1;
    loop_name:loop
        set num = num + 1;
        if num >= 10 
            then leave loop_name; //leave的意思就是跳出循环结构
    end loop loop_name;
end$
delimiter ;
delimiter $
create procedure update_salary_loop(inout num int)
begin
    set num = 0; //这里将num改成0的原因是:万一外面传了一个num不等于0,那么计算次数就不是从0开始计数,计数就错了
    loop_name:loop
        update employee set salary = salary * 1.1;
        select avg(salary) into avg_salary from employee;
        if avg_salary >= 12000 
            then leave loop_name;
        end if;
        set num = num + 1;
    end loop loop_name;
end$
delimiter ;

  1. 循环结构之while
  1. 循环结构之repeat
  1. leave和iterate的使用

leave表示跳出循环;

iterate可以看做java中continue的意思,跳过本次循环直接进入下一次循环;

leave语法:

leave 循环名称;

iterate语法:

iterate 循环名称;

这里的循环名称就是我们在循环结构中给当前循环取的名称,取名的目的是就是为了给leave,iterate,end loop使用;

  1. 游标的使用(也叫光标)
①游标是什么?

现在有一个语句:我将员工表中工资大于15000的员工查出来了,这是一个结果集。

结果集如下:

假如我现在想修改结果集中的第10行数据,我该怎么做呢?

由现有的知识并不好做,我们只能用肉眼看,找到第10的数据,再找到它的id,再使用where id = ?的方式进行修改,

显然这个过程很low。

这个时候就需要游标了;

②声明游标:
declare 游标名称 cursor for 查询语句;

注意1:这里的查询语句就是我们上面说的结果集。

注意2:在存储过程、存储函数中,游标的声明需要放在变量的声明后面,变量的声明需要放在begin...end代码块的最前面。

③打开游标
open 游标名称;

当我们定义好游标后,如果要使用它,必须先打开游标,打开游标胡,select语句的查询结果集就会被送到游标工作区,为后面游标逐条读取结果集中的记录做准备。

④使用游标(从游标中取得数据)
fetch 游标名称 into 字段1,字段2,字段3;

这里需要注意:假如你在创建游标时,查询语句的结果集中有字段1,字段2,字段3,那么你在fetch 游标名称 into 后面也必须跟字段1,字段2,字段3,必须 一 一对应,否则会报错。

  1. 游标的使用案例:

第九章、触发器

一、触发器的概念:

触发器是由事件驱动的,这些事件包括insert,update,delete等事件。

二、创建触发器:

1.创建语法:

说明:

  • INSERT | UPDATE | DELETE :表示你要监听表上哪种事件。

  • ON 表名:触发器是一定要跟表进行绑定的,因为触发器就是用来监控表的,所以要有一个ON 表名。

  • BEFORE | AFTER:表示你到底要在INSERT | UPDATE | DELETE 事件之前还是之后执行。

  • FOR EACH ROW:表示监控表中的每一行,任意一行发生INSERT | UPDATE | DELETE 时,就会触发这个触发器。

  • 触发器要执行的语句块:触发器被触发后,要执行的逻辑,可以是一句简单的SQL,如果逻辑复杂,可以用BEGIN...END代码块表示。

2. 举例1:创建一个名为before_insert_test_trigger的触发器,实现:在往test表中插入记录前, 往test_log表中插入一条记录,用于记录日志的功能。
delimiter $
create trigger before_insert_test_trigger
before insert on test
for each row
begin
    insert into test_log(operation,time) values ('向test表插入了一条语句','2023-03-27 00:00:00');
end$
delimiter ;
  1. 举例2:创建名为salary_check_trigger的触发器,基于员工表的insert事件,在insert之前检查将要添加的员工薪资是否大于他领导的薪资,如果大于领导薪资,就报sqlstate_value为'HY000'的错误,从而使得添加失败。

这里要注意几点:

  • 执行insert语句时,如何表示新插入进来的那个对象?在insert事件中,用new关键字代表新插入进来的对象。

  • 当批量插入时,new关键字也是表示新插入的一个对象,因为前面有for each row语法,你可以将其理解为一个循环,new关键字代表这个循环中的当次循环中的元素。

  • 在删除、更新事件中,我们用old关键字表示旧的那一行记录;

  • 在mysql中如何像java一样手动抛出异常?

  • signal sqlstate '异常代码名称' set message_text = '异常代码提示' ,按照这个语法就能手动抛出异常,这是固定语法,只有'异常代码名称'和'异常代码提示'我们能改,其他的都不能改。

三、查看触发器:

方式1:

show triggers;

方式2:从系统库information_schema的TRIGGERS表中查询触发器的信息;

select * from information_schema.TRIGGERS;

四、删除触发器:

drop trigger if exists 触发器名称;

五、触发器的优缺点

优点:

  • 通过对表事件的监控,保证数据的完整性,同时也可以实现一些很有用的功能,比如监控某张重要表的记录,将其操作记录通过触发器写到一张单独的日志表中。

  • 触发器还可以在操作数据前,对数据进行合法性的检查。

缺点:

  • 0
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
要在MySQL数据库中存储圆形要素,需要使用空间数据类型和空间函数。 MySQL支持几种空间数据类型,包括POINT、LINESTRING、POLYGON、MULTIPOINT、MULTILINESTRING、MULTIPOLYGON和GEOMETRYCOLLECTION。其中,圆形可以用POLYGON类型来表示。一个简单的圆形可以表示为一个由多边形组成的正多边形,多边形的边数越多,表示的圆形越接近圆形。 例如,以下代码创建了一个圆形的多边形: ``` SET @center = GeomFromText('POINT(116.397428 39.90923)'); SET @radius = 1000; SET @poly = CONCAT('POLYGON((', X(@center) + @radius * COS(RADIANS(45)),' ', Y(@center) + @radius * SIN(RADIANS(45)),',', X(@center) + @radius * COS(RADIANS(135)),' ', Y(@center) + @radius * SIN(RADIANS(135)),',', X(@center) + @radius * COS(RADIANS(225)),' ', Y(@center) + @radius * SIN(RADIANS(225)),',', X(@center) + @radius * COS(RADIANS(315)),' ', Y(@center) + @radius * SIN(RADIANS(315)),',', X(@center) + @radius * COS(RADIANS(45)),' ', Y(@center) + @radius * SIN(RADIANS(45)),'))'); ``` 在MySQL中,可以使用空间函数进行圆形的操作,例如计算两个圆形之间的距离、判断一个点是否在圆形内等。 以下是一个例子,计算一个点是否在圆形内: ``` SET @center = GeomFromText('POINT(116.397428 39.90923)'); SET @radius = 1000; SET @point = GeomFromText('POINT(116.383668 39.902188)'); SELECT Contains( GeomFromText(CONCAT('POLYGON((', X(@center) + @radius * COS(RADIANS(45)),' ', Y(@center) + @radius * SIN(RADIANS(45)),',', X(@center) + @radius * COS(RADIANS(135)),' ', Y(@center) + @radius * SIN(RADIANS(135)),',', X(@center) + @radius * COS(RADIANS(225)),' ', Y(@center) + @radius * SIN(RADIANS(225)),',', X(@center) + @radius * COS(RADIANS(315)),' ', Y(@center) + @radius * SIN(RADIANS(315)),',', X(@center) + @radius * COS(RADIANS(45)),' ', Y(@center) + @radius * SIN(RADIANS(45)),' ))'), @point ); ``` 如果返回值为1,则表示点在圆形内,否则不在。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值