MySQL 进阶使用【函数、索引、视图、存储过程、存储函数、触发器】

前言

        做数仓开发离不开 SQL ,写了很多 HQL 回头再看 MySQL 才发现,很多东西并不是 HQL 所独创的,而是几乎都来自于关系型数据库通用的 SQL;想到以后需要每天和数仓打交道,那么不管是 MySQL 还是 Oracle ,都需要深入了解一下,不能只停留在之前的 CRUD 上了。

1、MySQL 函数

1.1、GROUP_CONCAT 聚合函数

1.1.1、语法

GROUP_CONCAT([DISTINCT] 字段名* [ORDER BY DESC|ASC] [SEPARATOR] )

 1.1.2、使用案例

把不同部门的员工合并到一行,按照 id 进行升序排序,并用 '-' 分割开来:

SELECT department,GROUP_CONCAT(DISTINCT emp_id,emp_name ORDER BY emp_id SEPARATOR '-') AS id_with_name
FROM emp
GROUP BY department;

运行结果:

1.2、数学函数

简单的函数没有必要啰嗦,这里只介绍一些常用的:

  • ABS:绝对值
  • CEIL:向上取整
  • FLOOR:向下取整
  • RAND:0~1随机数
  • ROUND(x):四舍五入取整
  • ROUND(x,y):四舍五入保留 y 位小数
  • TRUNCATE(x,y):不四舍五入保留 y 位小数

1.3、字符串函数

1.4、日期函数

1.5、if 函数

MySQL 的 IF 函数和 HQL 是一样的,这里不做介绍。

1.6、窗口函数

        窗口函数一般都是配合聚合函数使用的,毕竟使用窗口就是为了把一类时间或者其它属性有关系的数据联系在一起。这里同样只介绍一些我没用过的函数:

1.6.1、CUME_DIST

用途:分组内小于、等于当前rank值的行数 / 分组内总行数

公式:rank / rows

场景:查询小于等于当前行某个值的记录占总记录的比例

解释:其实就是用 rank 函数的结果 / 记录总数

-- 该员工的薪资超过了相同部门多少人
SELECT emp_id,emp_name,department,salary,ROUND(CUME_DIST() OVER (PARTITION BY department ORDER BY salary),2) AS rate
FROM emp;

1.6.2、FIRST_VALUE 和 LAST_VALUE

用途:分组内的第一条/最后一条记录的某个字段的值

应用场景:截止目前按照xx排序后的第一名/最后一名的值

注意:它是对每一条记录都进行一次计算(相当于 rows between unbounded preceding and current row),而不是全局进行计算(它不会把该行之后考虑进计算范围)

-- 查询出同部门内工资最低和最高的人的薪资
SELECT emp_name,salary,FIRST_VALUE(salary) OVER (PARTITION BY department ORDER BY salary) AS first,LAST_VALUE(salary) OVER (PARTITION BY department ORDER BY salary) AS last
FROM emp;

1.6.3、NTH_VALUE

用途:返回窗口内地 exper 个值。(expr 可以是表达式,也可以是数字)

SELECT emp_name,department,salary,NTH_VALUE(salary,2) over (PARTITION BY department ORDER BY salary) AS second
FROM emp;

1.6.4、NTILE

用途:将分区中的有序数据分为n个等级,记录等级数(等级规则取决于是否分区和排序字段)

应用场景:将员工按照薪资进行划分等级

SELECT emp_name,department,salary,NTILE(3) over (ORDER BY salary) AS grade
FROM emp;

2、视图

2.1、视图的概念

介绍

  • 视图(view)是一个虚拟表,非真实存在,其本质是根据SQL语句获取动态的数据集,并为其命名,用户使用时只需使用视图名称即可获取结果集,并可以将其当作表来使用。
  • 数据库中只存放了视图的定义,而并没有存放视图中的数据。
  • 这些数据存放在原来的表中。 使用视图查询数据时,数据库系统会从原来的表中取出对应的数据。因此,视图中的数据是依赖于原来的表中的数据的。一旦表中的数据发生改变,显示在视图中的数据也会发生改变。

作用

  • 简化代码,可以把重复使用的查询封装成视图重复使用,同时可以使复杂的查询易于理解和使用。
  • 安全原因,如果一张表中有很多数据,很多信息不希望让所有人看到,此时可以使用视图视,如:社会保险基金表,可以用视图只显示姓名,地址,而不显示社会保险号和工资数等,可以对不同的用户,设定不同的视图。

2.2、创建视图

这里只介绍常用的创建方法:

语法

CREATE [OR REPLACE]
VIEW 视图名称
AS SELECT语句

案例

CREATE OR REPLACE
VIEW salary_grade
AS SELECT *, NTILE(3) over (ORDER BY salary) AS grade FROM emp;

2.3、查看所有表和视图

SHOW FULL TABLES;

2.4、修改视图

-- 1.通过alter修改视图
ALTER VIEW 视图名 AS SELECT语句;
-- 2.通过 create or replace 覆盖视图
CREATE OR REPLACE
VIEW 视图名
AS SELECT语句;

某些视图是可更新的,比如基表。对于可更新的视图,在视图中的行和基表中的行之间必须具有一对一的关系。如果视图包含下述结构中的任何一种,那么它就是不可更新的:

  • 聚合函数(SUM(), MIN(), MAX(), COUNT()等)
  • DISTINCT
  • GROUP BY
  • HAVING
  • UNION或UNION ALL
  • 位于选择列表中的子查询
  • JOIN
  • FROM子句中的不可更新视图
  • WHERE子句中的子查询,引用FROM子句中的表。
  • 仅引用文字值(在该情况下,没有要更新的基本表)

注意:视图中虽然可以更新数据,也就是说,可以在UPDATE、DELETE或INSERT等语句中使用它们,以更新基表的内容。但是有很多的限制。一般情况下,最好将视图作为查询数据的虚拟表,而不要通过视图更新数据。因为,使用视图更新数据时,如果没有全面考虑在视图中更新数据的限制,就可能会造成数据更新失败。

-- 往视图中插入数据
INSERT INTO emp_order_by_salary(emp_id, emp_name, salary, department) VALUES (1001,'谢永强',3500,'人事部');
-- 修改视图数据
UPDATE emp_order_by_salary SET emp_name='狄仁杰' WHERE emp_id=1001;
-- 删除视图数据
DELETE FROM emp_order_by_salary WHERE emp_id=1001;

注意:

  • 视图永远只是一个虚拟表,不存储数据,修改视图就是修改基表!

3、存储过程

3.1、存储过程介绍

什么是存储过程

  • MySQL 5.0 版本开始支持存储过程。
  • 简单的说,存储过程就是一组SQL语句集,功能强大,可以实现一些比较复杂的逻辑功能,类似于JAVA语言中的方法;
  • 存储过就是数据库 SQL 语言层面的代码封装与重用。

特性

  • 有输入输出参数,可以声明变量,有if/else, case,while等控制语句,通过编写存储过程,可以实现复杂的逻辑功能;
  • 函数的普遍特性:模块化,封装,代码复用;
  • 速度快,只有首次执行需经过编译和优化步骤,后续被调用可以直接执行,省去以上步骤;

其实也就是把可以实现某一功能的一堆 SQL 封装起来,等到需要用的时候直接调用即可,大大提高了SQL 的复用。

3.2、入门案例

为了区分普通的 SQL ,我们需要自定义 SQL 的结束符。下面我们看看存储过程的基本语法:

delimiter 自定义结束符号
create procedure 储存名([ in ,out ,inout ] 参数名 数据类形...)
beginsql语句
end 自定义的结束符号
delimiter ; -- 恢复为原本的结束符 ';'

自定义存储过程:

-- 自定义存储过程
DELIMITER $$
CREATE PROCEDURE func01()
BEGINSELECT * FROM emp;
END $$
DELIMITER ;-- 调用存储过程
call func01();

3.3、存储过程的使用

3.3.1、局部变量的定义

  • 局部变量是用户自定义,在 begin/end 快中有效
1)使用 declare 声明局部变量
declare 变量名 变量类型 [default 默认值]

测试: 

DELIMITER $$
CREATE PROCEDURE func2()
BEGINdeclare tmp int default 1;set tmp = 2;SELECT tmp;
end $$
DELIMITER ;call func2();    -- 2
2)使用 select into 语句

注意:select into 只能给变量赋值,变量依然需要使用 declare 声明!

语法

-- 把查询结果赋值给变量
SELECT col1, col2 into var1, var2 from table where condition;

注意:返回结果只能是单行结果!

测试:

DELIMITER $$
CREATE PROCEDURE func03()
BEGINdeclare eid int;declare ename varchar(20);declare esalary double;declare dname varchar(20);SELECT emp_id, emp_name, salary, departmentINTO eid,ename,esalary,dnameFROM emp WHERE emp_id=1;SELECT eid,ename,esalary,dname;
END $$
DELIMITER ;call func03();

注意:局部变量名不能和表字段相同,否则查询为 null (字段类型可以不同)。

3.3.2、用户变量的定义

语法

@@变量名

注意

  • 不需要像局部变量一样提前声明!(要声明也可以 直接 set @变量名 = xxx)
  • 生命周期是当前会话
DELIMITER $$
CREATE PROCEDURE func04()
BEGINSET @result = 1;
end $$
delimiter ;call func04();SELECT @result; -- 必须调用之后才能被初始化赋值 否则为null

3.3.3、系统变量

  • 系统变量又分为全局变量会话变量
  • 全局变量在 MYSQL 启动的时候由服务器自动将它们初始化为默认值,这些默认值可以通过更改my.ini这个文件来更改。
  • 会话变量在每次建立一个新的连接的时候,由MYSQL来初始化。MYSQL会将当前所有全局变量的值复制一份。来做为会话变量。
  • 也就是说,如果在建立会话以后,没有手动更改过会话变量与全局变量的值,那所有这些变量的值都是一样的。
  • 全局变量与会话变量的区别就在于,对全局变量的修改会影响到整个服务器,但是对会话变量的修改,只会影响到当前的会话(也就是当前的数据库连接)。
  • 有些系统变量的值是可以利用语句来动态进行更改的,但是有些系统变量的值却是只读的,对于那些可以更改的系统变量,我们可以利用set语句进行更改。
1. 全局变量

注意:全局变量由系统提供,整个数据库有效。

语法

@@global.变量名

查看全局变量:

show global variables ;

查看某个全局变量的值:

SELECT @@global.binlog_format;

设置全局变量(两种方式):

set global sort_buffer_size = 40000;
set @@global.sort_buffer_size = 40000;
2. 会话变量

语法:

@@session.变量名

查看会话变量:

show session variables;

查看某个会话变量的值:

SELECT @@session.auto_increment_increment;

修改会话变变量的值(两种方法):

set session sort_buffer_size = 50000;
set @@session.sort_buffer_size = 50000;

3.3.4、存储过程传参 in

in 表示传入的参数, 可以传入数值或者变量,即使传入变量,并不会更改变量的值,可以内部更改,仅仅作用在函数范围内。

-- in 传参
delimiter $$
create procedure func05(in id int)
beginselect * from emp where emp.emp_id = id;
end $$
delimiter ;call func05(1); -- 查询 emp_id = 1 的员工信息

3.3.5、存储过程传参 out

out 表示从存储过程内部传值给调用者(内部值指的是用户变量),用户变量作用于整个会话,所以调用这个方法时,用户变量就会被初始化并赋值。

delimiter $$
create procedure func06(in id int,out ename varchar(20))
beginselect emp_name into ename from emp where emp_id=id;
end $$
delimiter;call func06(1,@ename); -- 这里的@name是用户变量所以并不需要声明select @ename; -- 张晶晶

3.3.6、存储过程传参 inout

inout 表示从外部传入的参数经过修改后可以返回的变量,既可以使用传入变量的值也可以修改变量的值(即使函数执行完)。

说人话就是:这个参数既可以当做普通参数用,也可以当做用户变量对它进行赋值

delimiter $$
create procedure func07(inout id int,inout name varchar(20))
beginupdate emp set emp_name = name where emp_id = id;
end $$
delimiter ;set @name = '李大喜';
set @id = 1;
call func07(@id,@name);select * from emp where emp_id = 1; -- 张晶晶变成了李大喜

3.3.7、存储过程传参总结

  • in 输入参数,意思说你的参数要传到存过过程的过程里面去,在存储过程中修改该参数的值不能被返回
  • out 输出参数:该值可在存储过程内部被改变,并向外输出
  • inout 输入输出参数,既能输入一个值又能传出来一个值

3.3.8、存储过程流程控制 - if 语句

DELIMITER $$
CREATE PROCEDURE func08(in id int)
begindeclare num int default 0;declare result varchar(50);select count(*) into num from emp;if id < 1 || id > numthen set result = '超出范围';elseset result = (select emp_name from emp where emp_id = id);end if;select result;
end $$
delimiter ;call func08(1);

3.3.9、存储过程控制流程 - case 语句

需要注意的是 case 语句有两种语法:一种是匹配变量是否等于某个值,一种是匹配变量是否符合某种条件。

匹配条件

DELIMITER $$
CREATE PROCEDURE func09(in salary int)
BEGINDECLARE grade VARCHAR(3);CASEWHEN salary > 5000 THEN SET grade = '高';WHEN salary > 3000 THEN SET grade = '中';ELSE SET grade = '低';END CASE;SELECT grade;
END $$
DELIMITER ;CALL func09(8000); -- 高

匹配值: 

delimiter $$
create procedure func10(in grade varchar(1))
begindeclare result varchar(20);case gradewhen 'A' then set result = '90~100';when 'B' then set result = '80~90';when 'C' then set result = '70~80';when 'D' then set result = '60~70';when 'E' then set result = '0~60';else set result = '参数错误';end case;select result;
end $$
delimiter ;call func10('B');

3.3.10、循环语句和循环控制

先介绍循环控制:

  • leave:类似于 break
  • iterate:类似于 continue
1. while 循环

语法

[标签]: while 循环条件 do循环体
end while [标签];

测试:

delimiter $$
create procedure func11()
begindeclare i int default 0;declare num int default (select count(*) from emp);while i<num doselect i;set i = i+1;end while;
end $$
delimiter ;call func11();

测试结果就是会创建 num 张表,表内就是 i 的值。

2. repeat 循环

语法

[标签:] repeat循环体;
until 条件表达式
end repeat 标签;

测试:

delimiter $$
create procedure func12()
begindeclare i int default 0;label: repeatset i = i+1;until i > 10end repeat label;select '循环结束';
end $$
delimiter ;call func12();
3. loop 循环

语法

[标签:] loop循环体if 条件表达式 thenleave 标签;endif;
end loop;

测试:

delimiter $$
create procedure func13()
begindeclare i int default 0;label: loopif i!=5 thenset i = i+1;else leave label;end if;end loop;
end $$
delimiter ;call func13();

3.3.11、存储过程之游标(Cursor)

        游标(cursor)是用来存储查询结果集的数据类型 , 在存储过程和函数中可以使用光标对结果集进行循环的处理。光标的使用包括光标的声明、OPEN、FETCH 和 CLOSE.

语法

-- 声明语法
declare cursor_name cursor for select_statement
-- 打开语法
open cursor_name
-- 取值语法
fetch cursor_name into var_name [, var_name] ...
-- 关闭语法
close cursor_name

测试:

-- 游标
delimiter $$
create procedure func14(in id int)
begin-- 声明局部变量declare eid int;declare ename char(20);declare esalary decimal(10,2);-- 声明游标declare my_cursor cursor forselect emp_id,emp_name,salaryfrom empwhere emp_id=id;-- 打开游标open my_cursor;-- 通过游标获取每一行label: loopfetch my_cursor into eid,ename,esalary;select eid,ename,esalary;leave label;end loop;-- 关闭游标close my_cursor;
end $$
delimiter ;drop procedure func14;call func14(1);

注意循环体中必须有退出的条件,否则就是死循环!

3.3.12、句柄 handler

4、存储函数(自定义函数)

注意:自定义函数之前必须设置全局变量:

-- 信任函数的创建者
set global log_bin_trust_function_creators=TRUE;
-- 信任函数的创建者
set global log_bin_trust_function_creators=TRUE;delimiter $$
create function oneToNum(n int) returns int
begindeclare sum int default 0;while n!=0 doset sum = sum + n;set n = n-1;end while;return sum;
end $$
delimiter ;select oneToNum(3); --6

注意:自定义函数不能包含递归,递归需要使用专门的语法。

5、触发器

5.1、介绍

  • 触发器,就是一种特殊的存储过程。触发器和存储过程一样是一个能够完成特定功能、存储在数据库服务器上的SQL片段,但是触发器无需调用,当对数据库表中的数据执行DML操作时自动触发这个SQL片段的执行,无需手动条用。
  • 在MySQL中,只有执行insert,delete,update操作时才能触发触发器的执行
  • 触发器的这种特性可以协助应用在数据库端确保数据的完整性 , 日志记录 , 数据校验等操作 。
  • 使用别名 OLD 和 NEW 来引用触发器中发生变化的记录内容,这与其他的数据库是相似的。现在触发器还只支持行级触发,不支持语句级触发。

5.2、触发器的特性

  • 什么条件会触发:I、D、U
  • 什么时候触发:在增删改前或者后
  • 触发频率:针对每一行执行
  • 触发器定义在表上,附着在表上

5.3、触发器语法

5.3.1、创建只有一个执行语句方触发器

注意:这里的触发事件只能是 insert、update、delete。

create trigger 触发器名 before|after 触发事件
on 表名 for each row 
执行语句;

5.3.2、创建有多个执行语句的触发器

create trigger 触发器名 before|after  触发事件 
on 表名 for each row
begin执行语句列表
end;

5.4、触发器的使用

5.4.1.创建触发器

-- 触发器
drop trigger if exists trigger_emp;-- 创建受触发器影响的表格
create table emp_log(id int primary key auto_increment,time timestamp,log_text varchar(255)
);-- 创建触发器
create trigger trigger_emp
after insert on emp
for each row
insert into emp_log values (NULL,now(),'新的员工注册');insert into emp values (NULL,'刘海柱',8999,'技术部');

当向 emp 表进行 insert 操作时,就会触发触发器向 emp_log 插入一条日志。

5.4.2、NEW 和 OLD

MySQL 中定义了 NEW 和 OLD,用来表示触发器的所在表中,触发了触发器的那一行数据,来引用触发器中发生变化的记录内容,具体地:

        这让我很自然的联想到了 MySQL 的 binlog 功能,我们数仓中使用 MaxWell 来监听 binlog 实现数据同步,但是 binlog 的底层并不是触发器。

语法

NEW.列名
OLD.列名

测试:

-- 不要影响到其它触发器对 emp_log 的操作,比如这里增加了两个字段,
-- 当对该表进行insert 操作时,因为给 emp_log 增加字段之后没有更新触发器的行为
-- 就会导致给 emp 和 emp_log 插入数据是全部失败alter table emp_log add old varchar(50);
alter table emp_log add new varchar(50);create trigger trigger_test
after update
on emp for each row
insert into emp_log values (null,now(),concat('更新数据'),concat(OLD.emp_id,OLD.emp_name,OLD.salary,OLD.department),concat(NEW.emp_id,NEW.emp_name,NEW.salary,NEW.department)
);
update emp set emp_name = '李元芳' where emp_id = 3;select * from emp_log;

5.5、触发器使用的注意事项

  • MYSQL中触发器中不能对本表进行 insert ,update ,delete 操作,以免递归循环触发
  • 尽量少使用触发器,假设触发器触发每次执行1s,insert table 500条数据,那么就需要触发500次触发器,光是触发器执行的时间就花费了500s,而insert 500条数据一共是1s,那么这个insert的效率就非常低了。
  • 触发器是针对每一行的;对增删改非常频繁的表上切记不要使用触发器,因为它会非常消耗资源。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/705216.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

uniapp + vue3 设置 axios proxy 代理,并重写路径

uniapp vue2 设置代理如下&#xff1a; 已生成的项目架构里面找到manifest.json文件&#xff0c;通过源码视图的方式打开文件&#xff0c;在文件中添加一下代码即可完成代理&#xff1a; "h5": {"devServer": {"disableHostCheck": true, //禁…

多区域OSPF路由配置

一、基础配置 1.搭建实验拓扑图 2.实验编址 具体如何配置可以看这一篇详细的博文&#xff1a;单区域OSPF实验-CSDN博客 3.分别检查六个路由器的配置&#xff1a; 使用命令display ip interface brief R1的配置 其他大家可以调出来&#xff0c;再与实验拓扑图进行比对&#…

Java中的数组、Set、List、Map类型的互相转换总结

序言 数组、Set、List、Map是Java语言非常常用的几种数据类型&#xff0c;他们之间存在着千丝万缕的联系。关于底层的数据结构我这里就不再多说啦&#xff0c;直接从应用出发&#xff0c;总结他们之间的转换方法&#xff0c;并给出推荐方法。 大家可以点赞收藏等到需要的时候…

【考研数学】进入强化,基础过关《660》不会做怎么办?

做题没思路&#xff0c;说明学习的过程中走了弯路 很多人&#xff0c;按部就班的学习&#xff0c;觉得课我也听了&#xff0c;讲义也看了&#xff0c;怎么别人做题很顺&#xff0c;自己翻开书就一头雾水。搞清楚其中的差别&#xff0c;也就解决了做题没思路的问题。 首先我们…

波搜索算法(WSA)-2024年SCI新算法-公式原理详解与性能测评 Matlab代码免费获取

​ 声明&#xff1a;文章是从本人公众号中复制而来&#xff0c;因此&#xff0c;想最新最快了解各类智能优化算法及其改进的朋友&#xff0c;可关注我的公众号&#xff1a;强盛机器学习&#xff0c;不定期会有很多免费代码分享~ 目录 原理简介 一、初始化阶段 二、全…

【35分钟掌握金融风控策略28】贷中模型体系策略应用

目录 贷中模型体系策略应用 信用模型体系和模型在策略中的应用 反欺诈模型体系和模型在策略中的应用 运营模型体系和模型在策略中的应用 贷中模型体系策略应用 在贷前模型部分已经讲过&#xff0c;贷前开发的很多模型是可以在贷中直接使用的。贷中与贷前的不同点在于&…

webhook 和 API:你了解吗

Webhooks 是许多 API 的补充。通过设置 webhook 系统&#xff0c;系统 B 可以注册接收有关系统 A 某些更改的通知。当更改发生时&#xff0c;系统 A 推送 更改到系统 B&#xff0c;通常是以发出 HTTP POST 请求的形式。 Webhooks 旨在消除或减少不断轮询数据的需要。但根据我的…

软考一年只能考一次吗?24软考各科目考试时间一览表

软考考试次数&#xff1a; 软考高级【系统分析师】及【系统架构设计师】是一年考两次的。 此外&#xff0c;软考中级【软件设计师】和【网络工程师】也是一年考两次的。 其他科目一年都只开考一次&#xff0c;或者上半年开考&#xff0c;或者下半年开考&#xff0c;具体考试…

k8s 二进制安装 优化架构之 部署负载均衡,加入master02

目录 一 实验环境 二 部署 CoreDNS 1&#xff0c;所有node加载coredns.tar 镜像 2&#xff0c;在 master01 节点部署 CoreDNS 3&#xff0c; DNS 解析测试 4&#xff0c; 报错分析 5&#xff0c;重新 DNS 解析测试 三 master02 节点部署 1&#xff0…

DeepSpeed

文章目录 一、关于 DeepSpeed1、DeepSpeed 是什么2、深度学习训练和推理的极致速度和规模3、DeepSpeed 的四大创新支柱1&#xff09;DeepSpeed 训练2&#xff09;DeepSpeed 推理3&#xff09;DeepSpeed 压缩4&#xff09;DeepSpeed4Science 4、DeepSpeed 软件套件DeepSpeed 库推…

Golang RPC实现-day02

导航 Golang RPC实现一、客户端异步并发多个请求1、 客户端结构体2、 一个客户端&#xff0c;异步发送多个请求&#xff0c;使用call结构体代表客户端的每次请求3、客户端并发多个请求4、客户端接收请求 Golang RPC实现 day01 我们实现了简单的服务端和客户端。我们简单总结一…

景源畅信电商:做抖音有哪些未开发的蓝海领域?

在互联网信息爆炸的今天&#xff0c;抖音已经成为人们获取信息和娱乐的重要渠道。然而&#xff0c;随着用户数量的增加和内容的丰富&#xff0c;抖音的红海竞争也日益激烈。在这样的背景下&#xff0c;寻找还未被充分开发的蓝海领域&#xff0c;对于内容创作者来说&#xff0c;…