MySQL学习笔记之变量、流程控制和游标

变量

系统变量

系统变量包括全局系统变量和会话系统变量,分别用globalsession修饰。不加修饰符的话,默认是会话系统变量。

查看

查看全局系统变量:

# 全局系统变量
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;

上例说明全局系统变量和会话系统变量有交集。

修改系统变量

有三种方式,任选其一即可:

  1. 修改配置文件my.ini,重启MySQL
  2. set @@global/@@session.变量名 = 变量值
  3. 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中也有对判断、循环这种基本流程控制的支持,其中:

  • 判断语句包括ifcase……when
  • 循环语句包括loopwhilerepeat,退出循环可用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_pctnull者,更新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 ();

循环结构

循环结构包括loopwhilerepeat三种循环。
其中while循环和repeat循环自带循环控制,而loop循环的循环控制需要用循环标签和leaveiterate语句。

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中的forwhile
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;

综上,可以总结书游标的使用四步骤:创建、打开、使用、关闭。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值