文章目录
变量
系统变量
系统变量包括全局系统变量和会话系统变量,分别用global
和session
修饰。不加修饰符的话,默认是会话系统变量。
查看
查看全局系统变量:
# 全局系统变量
SHOW GLOBAL VARIABLES;
查看会话系统变量,有两种方式:
# 会话系统变量
SHOW SESSION VARIABLES;
# 会话系统变量
SHOW VARIABLES;
- 全局系统变量和会话系统变量有交集;
- 某个会话对会话系统变量的修改,不会同步到其他会话;
- 但如果修改的是全局系统变量,此修改会同步到修改后开启的所有会话;
- 系统变量针对的是当前MySQL服务实例,所以MySQL重启,系统变量会恢复到默认值。
查看指定系统变量:
SELECT
@@GLOBAL .max_connections;
SELECT
@@SESSION .character_set_client;
SELECT
@@GLOBAL .character_set_client;
SELECT
@@character_set_client;
如果某个系统变量既是系统又是会话,则默认显示会话值:
SELECT
@@SESSION .pseudo_thread_id;
上例说明全局系统变量和会话系统变量有交集。
修改系统变量
有三种方式,任选其一即可:
- 修改配置文件
my.ini
,重启MySQL set @@global/@@session.变量名 = 变量值
set global/session 变量名 = 变量值
SET @@GLOBAL .max_connections = 161;
SET GLOBAL max_connections = 171;
第一种方式永久有效,后两种方式重启或开启新会话后恢复默认值(mysql.ini
中的值)。
用户变量
用户变量分为会话用户变量和局部用户变量。
会话用户变量只对当前连接会话有效,局部用户变量只在存储过程或函数中使用,且只在其中有效。
会话用户变量定义
用=
和:=
均可:
SET @var1 = 1;
SET @var2 = 'Abel';
SET @var3 := 3;
SET @sum = @var1 + @var3;
查看用户变量
SELECT @sum;
通过select语句创建会话用户变量
有两种方式,任选其一。方式1:
# 方式1
SELECT
@count := count(*)
FROM
employees;
SELECT
@count;
方式2:
# 方式2
SELECT
avg(salary) INTO @avg_salary
FROM
employees;
SELECT
@avg_salary;
局部用户变量
局部用户变量可以通过declare
语句声明,但需要放到begin..end
语句开头,赋值方式跟会话用户变量相同。
下面用一个例子加以说明,在存储过程中使用局部变量,注意看其中的注释:
CREATE PROCEDURE test_var_local ()
BEGIN
# 局部用户变量声明
DECLARE
a INT DEFAULT 0;
DECLARE
b,
c INT;
DECLARE
name_ VARCHAR (25);
# 局部用户变量赋值
SET a = 1;
SET b = 2;
SET c := 4;
SELECT
last_name,
salary INTO name_,
c
FROM
employees
WHERE
employees.employee_id = 101;
# 局部用户变量读取
SELECT
a,
b,
c,
name_;
END;
CALL test_var_local ();
DROP PROCEDURE test_var_local;
DESC employees;
再看一个例子,创建存储过程,返回员工和他领导的薪资差距:
CREATE PROCEDURE diff_salary (
IN emp_id INT,
OUT dif_salary DOUBLE
)
BEGIN
DECLARE # 局部用户变量声明
worker_salary,
manager_salary DOUBLE;
SELECT
employees.salary,
worker.worker_salary INTO manager_salary,
worker_salary
FROM
employees,
(
SELECT
employees.manager_id manager_id,
employees.salary worker_salary
FROM
employees
WHERE
employees.employee_id = emp_id
) worker
WHERE
employees.employee_id = worker.manager_id;
SELECT
manager_salary - worker_salary INTO dif_salary;
END;
测试:
SET @dif_salary = 0; # 会话变量定义
CALL diff_salary (103, @dif_salary);
SELECT
@dif_salary;
DROP PROCEDURE diff_salary;
流程控制
MySQL中也有对判断、循环这种基本流程控制的支持,其中:
- 判断语句包括
if
、case……when
; - 循环语句包括
loop
、while
、repeat
,退出循环可用leave
,跳过当前迭代可用iterate
。
IF语句
SELECT
last_name,
IF (
salary >= 6000,
"high_salary",
"low_salary"
) salary_level
FROM
employees;
SELECT
last_name, commission_pct, IF(commission_pct is not NULL, commission_pct, 0) "pct",
salary * 15 + IF(commission_pct is not NULL, commission_pct * 1.2, 0) "annual"
FROM
employees;
存储过程或函数中的if
语句:
CREATE PROCEDURE test_if ()
BEGIN
# 局部变量声明
DECLARE
age INT DEFAULT 20;
# if...elseif...else判断分支
IF age > 40 THEN
SELECT
'middle or old';
ELSEIF age > 18 THEN
SELECT
'young';
ELSEIF age > 12 THEN
SELECT
'youth';
ELSE
SELECT
'child';
END
IF;
# 判断分支结束
END;
# 存储过程结束
CALL test_if ();
再看一个例子,入职五年的员工涨薪500:
CREATE PROCEDURE update_salary_by_eid1 (IN emp_id INT)
BEGIN
# 局部变量声明
DECLARE
hire_year DOUBLE;
DECLARE
current_salary DOUBLE;
# 执行语句定义
SELECT
salary,
hire_date INTO current_salary,
hire_year
FROM
employees
WHERE
employee_id = emp_id;
## if判断分支
IF DATEDIFF(CURDATE(), hire_year) / 365 > 5 THEN
UPDATE employees
SET salary = current_salary + 500
WHERE
employee_id = emp_id;
END
IF;
## 判断分支结束
END;
# 存储过程结束
# 存储过程调用
CALL update_salary_by_eid1 (103);
CALL update_salary_by_eid1 (104);
再看一个例子:
- 工资< 9000者,更新工资为9000;
- 9000 <= 工资 < 10000 且
commission_pct
为null
者,更新commission_pct
为0.01; - 其余者,加薪100:
CREATE PROCEDURE update_salary_by_eid2 (IN emp_id INT)
BEGIN
# 局部变量声明
DECLARE
current_salary DOUBLE;
DECLARE
comm_pct DOUBLE;
# 定义执行语句
SELECT
salary,
commission_pct INTO current_salary,
comm_pct
FROM
employees
WHERE
employee_id = emp_id;
## if...elseif...else判断分支
IF current_salary < 9000 THEN
UPDATE employees
SET salary = 9000
WHERE
employee_id = emp_id;
ELSEIF current_salary < 10000
AND comm_pct IS NULL THEN
UPDATE employees
SET commission_pct = 0.01
WHERE
employee_id = emp_id;
ELSE
UPDATE employees
SET salary = current_salary + 100
WHERE
employee_id = emp_id;
END
IF;
## 判断分支结束
END;
# 存储过程结束
# 存储过程调用
CALL update_salary_by_eid2 (102);
CALL update_salary_by_eid2 (103);
CALL update_salary_by_eid2 (104);
CASE WHEN … THEN … ELSE … END语句
相当于java里的switch-case
:
SELECT
last_name,
salary,
CASE # case...when语句
WHEN salary >= 15000 THEN
"A"
WHEN salary >= 10000 THEN
"B"
WHEN salary >= 5000 THEN
"C"
ELSE
"D"
END "level" ## case...when默认值
FROM
employees;
SELECT
last_name,
department_id,
CASE # case...when语句
WHEN department_id = 10 THEN
salary * 1.1
WHEN department_id = 20 THEN
salary * 1.2
WHEN department_id = 30 THEN
salary * 1.3
END "salary" ## case...when默认值
FROM
employees
WHERE
department_id IN (10, 20, 30);
同样,该语句也可以用在存储过程或存储函数中:
# 流程控制之CASE语句
CREATE PROCEDURE test_case ()
BEGIN
DECLARE
v1 INT;
SET v1 = 20;
## case语句匹配具体值,相当于switch-case
CASE v1
WHEN 10 THEN
SELECT
"v1 = 10";
WHEN 20 THEN
SELECT
"v1 = 20";
WHEN 30 THEN
SELECT
"v1 = 30";
ELSE
SELECT
"v1 is other value";
END CASE;
SET v1 = v1 + 10;
## case语句匹配范围,相当于if-else if-else
CASE
WHEN v1 < 20 THEN
SELECT
"v1 < 20";
WHEN v1 < 40 THEN
SELECT
"20 <= v1 < 40";
WHEN v1 < 60 THEN
SELECT
"40 <= v1 < 60";
ELSE
SELECT
"v1 is in other range";
END CASE;
END;
CALL test_case ();
循环结构
循环结构包括loop
、while
和repeat
三种循环。
其中while
循环和repeat
循环自带循环控制,而loop
循环的循环控制需要用循环标签和leave
或iterate
语句。
LOOP
CREATE PROCEDURE test_loop ()
BEGIN
DECLARE
num INT DEFAULT 1;
loop_label : # 循环标签
LOOP
# 循环体
SET num = num + 1;
IF num > 10 THEN
LEAVE loop_label; # 退出循环,指定要退出的循环标签
END
IF;
END
LOOP
loop_label; # 循环体结束
SELECT
num;
END;
CALL test_loop ();
看一个案例:全体加薪10%,直到平均工资>=20000,并输出加薪次数:
CREATE PROCEDURE add_avg()
BEGIN
# 局部变量定义
DECLARE loop_count INT DEFAULT 0;
DECLARE avg_sal DOUBLE DEFAULT 0;
# 循环标签定义
add_salary:
LOOP # 循环体
SELECT AVG(salary) INTO avg_sal from employees;
IF avg_sal >= 20000 THEN
LEAVE add_salary;
END IF;
UPDATE employees SET salary = salary * 1.1;
set loop_count = loop_count + 1;
END LOOP; # 循环体结束
select loop_count;
# 存储过程结束
END;
# 存储过程调用
call add_avg();
# 存储过程执行效果查看
SELECT AVG(salary) from employees;
# 存储过程删除
drop procedure add_avg;
WHILE
create PROCEDURE test_while()
BEGIN
DECLARE num INT DEFAULT 1;
WHILE num <= 10 DO # 循环条件
set num = num + 1; # 循环体
END WHILE; # 循环体结束
select num;
END;
call test_while();
看一个案例:全体降薪至平均工资小于10000,输出平均工资和降薪次数:
CREATE PROCEDURE reduce_salary()
BEGIN
# 局部变量定义
DECLARE avg_sal double DEFAULT 0;
DECLARE reduce_count INT DEFAULT 0;
SELECT AVG(salary) INTO avg_sal from employees;
# 循环体
WHILE avg_sal > 10000 DO
update employees set salary = salary *0.9;
SELECT AVG(salary) INTO avg_sal from employees;
set reduce_count = reduce_count + 1;
END WHILE;
# 循环体结束
select avg_sal, reduce_count;
# 存储过程结束
END;
# 存储过程调用
call reduce_salary();
# 存储过程删除
DROP procedure reduce_salary;
REPEAT
CREATE PROCEDURE test_repeat()
BEGIN
# 局部变量定义
DECLARE num INT DEFAULT 1;
REPEAT # 循环开始
SET num = num + 1; # 循环体
UNTIL num >= 10 # 循环结束条件
END REPEAT; # 循环结束
select num;
# 存储过程结束
END;
# 存储过程调用
CALL test_repeat();
对比三种循环
REPEAT
是先执行后判断,类似于java中的do-while
;
WHILE
则是先判断后执行,类似于java中的for
和while
;
LOOP
配合LEAVE
可以实现先执行后判断或先判断后执行。
LEAVE
CREATE PROCEDURE leave_begin(IN num INT)
begin_label: BEGIN
IF num <= 0 THEN LEAVE begin_label; # 退出某个标签
ELSEIF num = 1 THEN select avg(salary) from employees;
ELSEIF num = 2 THEN select min(salary) from employees;
ELSEIF num = 3 THEN select max(salary) from employees;
END IF;
select count(*) from employees;
# 存储过程结束
END;
# 存储过程调用
CALL leave_begin(0);
CALL leave_begin(1);
CALL leave_begin(2);
CALL leave_begin(3);
CALL leave_begin(4);
2.5、ITERATE
ITERATE
用于结束循环的当前迭代,类似于java里的continue
:
CREATE PROCEDURE test_iterate()
BEGIN
# 局部变量定义
DECLARE num INT DEFAULT 0;
# 循环体
loop_label: LOOP
SET num = num + 1;
IF num > 0 and num % 3 > 0 THEN ITERATE loop_label; # 结束当前迭代,指定循环标签名
ELSE SELECT num;
END IF;
IF num > 20 THEN LEAVE loop_label;
END IF;
END LOOP;
# 循环体结束
# 存储过程结束
END;
# 存储过程调用
call test_iterate();
# 存储过程删除
DROP PROCEDURE test_iterate;
游标
游标用于记录定位,充当了表的行指针,但需要放到变量定义之后定义。游标使用时会锁定对应的表数据,导致其他对表的查询会失败,因此必须及时关闭游标。
案例:累加工资最高的员工的工资,直到超过阈值,最后返回累加次数:
CREATE PROCEDURE get_count_by_limit_total_salary(IN limit_total_salary DOUBLE
, OUT total_count INT)
BEGIN
DECLARE sum_sal DOUBLE DEFAULT 0;
DECLARE cur_sal DOUBLE DEFAULT 0; # 保存游标所在行中的数据
DECLARE iter_count INT DEFAULT 0;
DECLARE emp_cursor CURSOR FOR # 创建游标:declare 游标名 cursor for 查询语句;
SELECT salary FROM employees ORDER BY salary DESC;
OPEN emp_cursor; # 打开游标
REPEAT
FETCH emp_cursor INTO cur_sal; # 使用游标,注意游标中的字段要和into后的字段一一对应
set sum_sal = sum_sal + cur_sal;
set iter_count = iter_count + 1;
UNTIL sum_sal > limit_total_salary
END REPEAT;
CLOSE emp_cursor; # 关闭游标
SELECT iter_count into total_count;
END; # 存储过程结束
# 存储过程调用,查看执行效果
set @total_count = 0;
call get_count_by_limit_total_salary(200000, @total_count);
SELECT @total_count;
# 删除存储过程
drop PROCEDURE get_count_by_limit_total_salary;
综上,可以总结书游标的使用四步骤:创建、打开、使用、关闭。