MySql 游标初识

认识

游标(cursor), 按字面意思可理解为, 游动的标识, 或者叫做"光标", 这样更容易理解. 就好比现有一张表存储了n行记录, 然后我想每次取出一行, 进行为所欲为, 这时候就用到了游标cursor, 数据的搬运工, 搬运完当前数据(游标指向当前), 然后又移动到下一条数据的位置.

"移动", 和 "指向" 这两个词很重要, 跟C的指针有点类似, 举一个链表的例子吧. a -> b -> c -> d, 需求是求计算链表的长度. 则我们需要定义一个游标变量cursor, 默认定位到"a" 节点位置, 还需定义一个技术变量count 默认为0, 然后移动游标cursor (Python里 "=" 即表示"指向"), 沿着 a->b->c->d, 每移动一次, 则游标指向当前节点, 计数器加1....

应用场景, 在于, 好比一条 select xxx where xxx ; 返回的是一个查询集, 但我不想啪啪啪一顿返回一堆数据, 我要自己逐行检查和写逻辑判断, 这时候就需要cursor. 即, 一条sql, 对应N条数据, 取出(依次 or 自定义顺序) 数据的接口(interface) / 句柄, 就是游标, 沿着游标方向, 依次可以一次取出1行.

接口(interface)

接口泛指实体把自己提供给外界的一种抽象化物(可以为另一实体),用以由内部操作分离出外部沟通方法,使其能被内部修改而不影响外界其他实体与其交互的方式。

通俗理解, 就是, A只想用B的一些功能,但并不关心B是怎么实现的, 由此B根据A的需求, 提供一些能满足A的服务, 这些服务, 就是"接口".举几个栗子.

  • 我想开车, 车里面是什么样的我并不关系, 这时候车给了提供了, 方向盘, 油门,离合器, 刹车, 挡位等, 我就是能开了, 这些就是"接口" (好像不太恰当哦); 或者是想学外语, 这时候给我提给了一张VIP卡, 那我就可以学外语了, 这张VIP卡, 就是接口.
  • 我编写了一个web网站, 想让用户用微信, QQ, 微博, 支付宝账号也能登陆, 那这时候,我就要向这些大佬公司申请这些用户ID验证, 怎么验证我不管, 只按照他们提供的 "验证规则"传参进去, 等待就好, 那, 这个验证规则逻辑, 就是微信, 微博...向外界提供的接口.
  • 我想要操作电脑, 这时候, windows / linux 给我提给了 图形化 / 命令行 的方式让我操作, 这也是接口.
  • 创建一张MySql二维表存储数据, 我可以对其进行增删改查, 那这些功能, 也是接口.
  • 编码时, 调用自定义的或别人的或系统的类的方法时, 这些方法也是接口.

语法

  • 声明游标: declare 游标名 cursor for select _staetment;
  • 打开游标: open 游标名
  • 取出数据: fetch 游标名 into var1, var2 ....
  • 关闭游标: close 游标名
-- 文档说明
-- 游标声明必须在procedure 数据处理之前, 和在变量声明之后.
DECLARE cursor_name CURSOR FOR select_statement;
OPEN cursor_name; -- 打开游标(当前块,唯一命名)
FETCH cursor_name INTO var_name, [, var_name] ... -- 读取游标数据, 并前进指针
CLOSE cursor_name; -- 如果不close, 则会在其被声明的复合语句末尾被关闭

案例

用之前的goods, 表操作一波.

-- 查看一下数据
-- out
mysql> select * from goods;
+-----+------+-----+
| gid | name | num |
+-----+------+-----+
| 1 | cat | 37 |
| 2 | dog | 72 |
| 3 | pig | 18 |
+-----+------+-----+
3 rows in set (0.09 sec) -- 更新一波 cat 的数量吧
mysql> update goods set num = 100 where gid=1; Query OK, 1 row affected (0.08 sec)
Rows matched: 1 Changed: 1 Warnings: 0 mysql> select * from goods;
+-----+------+-----+
| gid | name | num |
+-----+------+-----+
| 1 | cat | 100 |
| 2 | dog | 72 |
| 3 | pig | 18 |
+-----+------+-----+
3 rows in set (0.11 sec)

需求是要逐条取出每行数据, 而不是一下子都给我.

-- cursor: 依次获取每行数据
drop procedure if exists cur1;
delimiter //
create procedure cur1()
begin
-- select * from goods; 每行有3个值, gid, name, num
-- 首先要定义变量来存储
-- 声明和打开游标
-- fetch 每行数据
-- 处理逻辑
-- 关闭游标
end //
delimiter ;

具体实现

drop procedure if exists cur1;
delimiter //
create procedure cur1()
begin
-- select * from goods; 每行有3个值, gid, name, num
-- 首先就为每行数据, 定义相应临时变量来存储
declare tmp_gid int;
declare tmp_name varchar(20);
declare tmp_num int;
-- 声明和打开游标
declare getGoods cursor for select gid, name, num from goods;
open getGoods;
-- fetch 每行数据, 要一一有对应的变量来接收哦
fetch getGoods into tmp_gid, tmp_name, tmp_num;
-- 处理逻辑(这里只打印一下)
select tmp_gid, tmp_name, tmp_num;
-- 关闭游标
close getGoods; end //
delimiter ; -- out
mysql> call cur1();
+---------+----------+---------+
| tmp_gid | tmp_name | tmp_num |
+---------+----------+---------+
| 1 | cat | 100 |
+---------+----------+---------+
1 row in set (0.14 sec)

与之前直接取出一行的区别在于, 控制权在我们手里, 真的可以为所欲为, 进一步可以进行判断,取值等各种编程操作, 真的可以为所欲为.

如何fetch多行呢?

-- fetch 多行 及 游标到尾(没有数据了, 不会报错)
drop procedure if exists cur1;
delimiter //
create procedure cur1()
begin
declare tmp_gid int;
declare tmp_name varchar(20);
declare tmp_num int; declare getGoods cursor for select gid, name, num from goods;
open getGoods;
-- fetch and into val1, var2.....
-- fetch 多行
fetch getGoods into tmp_gid, tmp_name, tmp_num;
select tmp_gid, tmp_name, tmp_num; fetch getGoods into tmp_gid, tmp_name, tmp_num;
select tmp_gid, tmp_name, tmp_num; fetch getGoods into tmp_gid, tmp_name, tmp_num;
select tmp_gid, tmp_name, tmp_num; fetch getGoods into tmp_gid, tmp_name, tmp_num;
select tmp_gid, tmp_name, tmp_num;
-- cursor 即便到尾了(没有data, 也不会报错哦)
fetch getGoods into tmp_gid, tmp_name, tmp_num;
select tmp_gid, tmp_name, tmp_num;
-- close
close getGoods;
end //
delimiter ; -- out mysql> call cur1();
+---------+----------+---------+
| tmp_gid | tmp_name | tmp_num |
+---------+----------+---------+
| 1 | cat | 100 |
+---------+----------+---------+
1 row in set (0.20 sec) +---------+----------+---------+
| tmp_gid | tmp_name | tmp_num |
+---------+----------+---------+
| 2 | dog | 72 |
+---------+----------+---------+
1 row in set (0.37 sec) +---------+----------+---------+
| tmp_gid | tmp_name | tmp_num |
+---------+----------+---------+
| 3 | pig | 18 |
+---------+----------+---------+
1 row in set (0.54 sec) mysql> select * from goods;
+-----+------+-----+
| gid | name | num |
+-----+------+-----+
| 1 | cat | 100 |
| 2 | dog | 72 |
| 3 | pig | 18 |
+-----+------+-----+
3 rows in set (0.18 sec)

可以看出, 游标的特点是, 每fetch一次, 就往后游动一次, 即称为游标嘛.

现在, 要采用循环与游标相配合 取出每条数据. 思路可以是先查询到表的行数rows_num;作为循环的退出条件, 然后循环fetch即可.(while, repeat都行).

-- 通过cursor, 循环取出每行数据
drop procedure if exists curRepeat;
delimiter //
create procedure curRepeat()
begin
declare getGoods cursor for select gid, name, num from goods;
open getGoods;
-- 循环fetch
repeat
-- 这里需要一大波的定义变量哦.
fetch getGoods into xxx, xxx, ...;
select xxx, xxx ...;
until i > rows_num
end repeat;
end //
delimiter ;

详细repeat 实现

-- 通过cursor, 循环取出每行数据
drop procedure if exists curRepeat;
delimiter //
create procedure curRepeat()
begin
declare tmp_gid int;
declare tmp_name varchar(20);
declare tmp_num int; declare i int default 0; -- 自增变量
declare rows_num int default 0; -- 存储查询集的行数(循环的退出条件) declare getGoods cursor for select gid, name, num from goods; -- 获取查询集的行数 rows_num, 注意顺序, 操作要放在 declare 之后哦
-- 错误语法: set rows_num := select count(*) from goods;
select count(*) into rows_num from goods;
open getGoods;
-- 循环fetch
repeat
fetch getGoods into tmp_gid, tmp_name, tmp_num;
-- 业务逻辑处理(这里只是简单打印), 游标的作用就在于此, 这里可以为所欲为.
select tmp_gid, tmp_name, tmp_num; set i := i + 1;
until i > rows_num
end repeat;
-- 别忘了close
close getGoods;
end //
delimiter ; -- out
call curRepeat();
Query OK, 0 rows affected (0.00 sec) Query OK, 0 rows affected (0.00 sec) +---------+----------+---------+
| tmp_gid | tmp_name | tmp_num |
+---------+----------+---------+
| 1 | cat | 100 |
+---------+----------+---------+
1 row in set (0.12 sec) +---------+----------+---------+
| tmp_gid | tmp_name | tmp_num |
+---------+----------+---------+
| 2 | dog | 72 |
+---------+----------+---------+
1 row in set (0.26 sec) +---------+----------+---------+
| tmp_gid | tmp_name | tmp_num |
+---------+----------+---------+
| 3 | pig | 18 |
+---------+----------+---------+
1 row in set (0.41 sec)

思路二: 游标取值越界时, 利用越界标识判断, 用到DECLARE ... HANDLER... 处理程序

  • SQLWARNING 是对所有以01开头的SQLSTATE代码的标记
  • NOT FOUND 是对02开头的SQLSTATE代码标记
  • SQLEXCEPTION 是对除了01, 02外的代码标记
DECLARE handler_type HANDLER FOR condtion_value ... sp_statement.
-- cursor 越界标识来退出循环
drop procedure if exists curBorder;
delimiter //
create procedure curBorder()
begin
declare tmp_gid int;
declare tmp_name varchar(20);
declare tmp_num int;
-- 游标遍历数据结束的标志
declare done boolean default false;
declare getGoods cursor for select gid, name, num from goods;
-- 退出的 handler 标记, not found 时执行
-- declare continue handler for NOT FOUND set done := True;
-- 解决continue 多取一行的问题, 用 EXit 即可
declare EXIT handler for NOT FOUND set done := True; open getGoods;
-- 循环取每行值
repeat
fetch getGoods into tmp_gid, tmp_name, tmp_num;
-- 业务逻辑处理
select tmp_gid, tmp_name, tmp_num;
until done=True
end repeat;
-- 总是忘了最后关闭游标呀
close getGoods; end //
delimiter ; -- out mysql> call curBorder();
+---------+----------+---------+
| tmp_gid | tmp_name | tmp_num |
+---------+----------+---------+
| 1 | cat | 100 |
+---------+----------+---------+
1 row in set (0.12 sec) +---------+----------+---------+
| tmp_gid | tmp_name | tmp_num |
+---------+----------+---------+
| 2 | dog | 72 |
+---------+----------+---------+
1 row in set (0.27 sec) +---------+----------+---------+
| tmp_gid | tmp_name | tmp_num |
+---------+----------+---------+
| 3 | pig | 18 |
+---------+----------+---------+
1 row in set (0.38 sec) Query OK, 0 rows affected (0.00 sec)

小结 handler 类型

  • continue handler ...not found ... 是触发后, 后面的语句继续执行
  • exit handler ...not found ... 是出发后, 后面的语句不执行
  • undo handler ... 前面的语句撤销...

从逻辑上, 就用continue handler 来取出数据

-- 坚持用contine取出所有行,并考虑特殊情况
drop procedure if exists curContinue;
delimiter //
create procedure curContinue()
begin
declare tmp_gid int;
declare tmp_name varchar(20);
declare tmp_num int; declare done boolean default False;
declare getGoods cursor for select gid, name, num from goods;
declare continue handler for not found set done := True; open getGoods;
-- 先fetch 一行出来
fetch getGoods into tmp_gid, tmp_name, tmp_num; repeat
-- 先取出一条来出来(不论是0,1或多), 再继续 fetch
select tmp_gid, tmp_name, tmp_num;
fetch getGoods into tmp_gid, tmp_name, tmp_num;
until done = True
end repeat;
-- colse
close getGoods;
end //
delimiter ; -- out mysql> call curContinue();
+---------+----------+---------+
| tmp_gid | tmp_name | tmp_num |
+---------+----------+---------+
| 1 | cat | 100 |
+---------+----------+---------+
1 row in set (0.14 sec) +---------+----------+---------+
| tmp_gid | tmp_name | tmp_num |
+---------+----------+---------+
| 2 | dog | 72 |
+---------+----------+---------+
1 row in set (0.28 sec) +---------+----------+---------+
| tmp_gid | tmp_name | tmp_num |
+---------+----------+---------+
| 3 | pig | 18 |
+---------+----------+---------+
1 row in set (0.39 sec) Query OK, 0 rows affected (0.00 sec)

同样, 换成while循环也是一样的.

drop procedure if exists cur_while;
delimiter //
create procedure cur_while()
begin
-- 声明临时变量来存储fetch每行值和一状态变量
declare tmp_gid int;
declare tmp_name varchar(20);
declare tmp_num int;
declare done boolean default True;
-- 声明游标对应的查询集
declare getGoods cursor for select gid, name, num from goods;
-- 声明退出条件的 handler
declare continue handler for not found set done := False; open getGoods;
-- 先取一行
fetch getGoods into tmp_gid, tmp_name, tmp_num;
-- while 循环 来取数据
while done do
-- 处理每行的业务逻辑
select tmp_gid, tmp_name, tmp_num;
-- 继续往后fetch
fetch getGoods into tmp_gid, tmp_name, tmp_num;
end while;
close getGoods;
end //
delimiter ; -- out
mysql> call cur_while();
+---------+----------+---------+
| tmp_gid | tmp_name | tmp_num |
+---------+----------+---------+
| 1 | cat | 100 |
+---------+----------+---------+
1 row in set (0.12 sec) +---------+----------+---------+
| tmp_gid | tmp_name | tmp_num |
+---------+----------+---------+
| 2 | dog | 72 |
+---------+----------+---------+
1 row in set (0.27 sec) +---------+----------+---------+
| tmp_gid | tmp_name | tmp_num |
+---------+----------+---------+
| 3 | pig | 18 |
+---------+----------+---------+
1 row in set (0.38 sec) Query OK, 0 rows affected (0.00 sec)

小结游标

  • 理解游标cusror的概念, "移动的光标", 在脑海里要有画面感, 结合 C 的"指针" 和Python的 "=".
  • 游标的用处在于,可以依次去一行数据, 然后可按业务需求逻辑, 对其为所欲为.
  • 游标的语法: declare, open, fetch, close.
  • 结合循环代码实现
    • 声明 fetch 每行数据, 每个值的变量取接收数据
    • 声明一个状态变量(boolean型), 当游标走到最后时, 此变量改变状态,并作为退出循环的依据
    • declare getGoods cursor for select xxxx;
    • declare continue handler for not found set done := False;
    • 先试着取出一行 + 对应该行的业务逻辑处理
    • 再进行循环取数据 + 业务逻辑
    • close cursor_name;
  • 语法小细节
    • 游标声明要在 declare变量之后, 在handler之前
    • 注意不要忘了结束的 "; " 和 关键字 for, set , 单词拼错, 这些
    • 记得最好要close cursor_name; 释放资源

Mysql 游标初识的更多相关文章

  1. SqlServer和MySQL游标学习

    一 sqlserver游标使用 /*** 游标的使用  讲了这个多游标的优点,现在我们就亲自来揭开游标的神秘的面纱.  使用游标的顺序: 声名游标.打开游标.读取数据.关闭游标.删除游标. 1.3.1 ...

  2. MySQL游标操作指南

    本篇文章是对MySQL游标的具体使用进行了详细的分析介绍,需要的朋友参考下   测试表 level  代码如下: create table test.level (name varchar(20)); ...

  3. 个人笔记mysql游标

    经过测试,mysql游标是无法读取自定义函数计算的结构,mysql自带的函数计算值是可以读取的.

  4. MySQL游标的简单实践

    Q:为什么要使用游标? A: 在存储过程(或函数)中,如果某条select语句返回的结果集中只有1行,可以使用select into语句(上几篇博客有介绍到用法)来得到该行进行处理:如果结果集中有多行 ...

  5. mysql 游标 demo

    一.MySQL游标的概念 1.游标介绍 MySQL的游标(cursor)是一个重要的概念,通过查找资料与自己的理解,主要得出以下几点关于自己的理解. 有数据缓冲的思想:游标的设计是一种数据缓冲区的思想 ...

  6. {MySQL数据库初识}一 数据库概述 二 MySQL介绍 三 MySQL的下载安装、简单应用及目录介绍 四 root用户密码设置及忘记密码的解决方案 五 修改字符集编码 六 初识sql语句

    MySQL数据库初识 MySQL数据库 本节目录 一 数据库概述 二 MySQL介绍 三 MySQL的下载安装.简单应用及目录介绍 四 root用户密码设置及忘记密码的解决方案 五 修改字符集编码 六 ...

  7. [转]MySQL游标的使用

    转自:http://www.cnblogs.com/sk-net/archive/2011/09/07/2170224.html 以下的文章主要介绍的是MySQL游标的使用笔记,其可以用在存储过程的S ...

  8. Mysql:初识MySQL

    转载自:https://www.cnblogs.com/hellokuangshen/archive/2019/01/09/10246029.html Mysql:初识MySQL 只会写代码的是码农: ...

  9. MySQL 游标

    概述 本章节介绍使用游标来批量进行表操作,包括批量添加索引.批量添加字段等.如果对存储过程.变量定义.预处理还不是很熟悉先阅读我前面写过的关于这三个概念的文章,只有先了解了这三个概念才能更好的理解这篇 ...

随机推荐

  1. 请写出css中选择器(元素选择器、类选择器、id选择器)的优先级顺序,和当各种选择器组合时,优先级的计算规则是什么?

    id选择器>类选择器>元素选择器 规则:选择器的权重值表述为4个部分,用0,0,0,0表示. 通配符*的权重为0,0,0,0 标签选择器.伪元素选择器的权重为0,0,0,1 类选择器.属性 ...

  2. vue CSS使用/deep/

    比如你使用了别人的组件或者自己开发一个组件,有时候你修改一处就可能影响到别的地方,这个时候要么你不用别人的组件,自己重新封装一个,但很多时候是不太现实的,所以就需要有一个方法或者方式,既不影响到别的地 ...

  3. 浅谈[0,1]区间内的n个随机实数变量中增加偏序关系类题目的解法

    浅谈[0,1]区间内的n个随机实数变量中增加偏序关系类题目的解法 众所周知,把[0,1]区间内的n个随机.相互独立的实数变量\(x_i\)之间的大小关系写成一个排列\(\{p_i\}\),使得\(\f ...

  4. Mikrotik: Setup SSTP Server for Windows 10 Client

    原文: http://www.dr0u.com/mikrotik-setup-sstp-server-for-windows-10-client/ Basic how-to on SSTP for a ...

  5. kafka作为elk缓存使用

    ELK集群在大规模的日志收集中面临着数据量大,收集不及时,或宕机的风险,可以选择单节点的redis,但是相比redis,kafka集群高可用的特性,更优,下面来配置kafka集群配置elk作为缓存的方 ...

  6. Win10创建mysql8.0桌面快捷方式以及启动mysql.exe闪退问题

    1.先找到mysql的bin目录,将Mysql.exe发送快捷方式到桌面. 2.然后右键选择属性,将目标后面添加上 -uroot -p 我的完整目标如下: E:\mysql-8.0.17-winx64 ...

  7. 利用Travis IC实现Hexo博客自动化部署

    1.Hexo博客的利与弊 Hexo中文 我就默认为看到这篇文章的人都比较了解Hexo博客,也都能够成功手动部署吧.所以第一部分推荐两篇文章一笔带过,让我们快速进入本文的重点内容.实在不知道也不要方先看 ...

  8. SAP: Smartform中存在渐变色图片的上传失真问题的解决

    下载GIMP编辑软件,导入图像选择Image->Mode->Indexed 设置Color dithering然后通过File->Export as导出bmp文件.如果上传后不好用请 ...

  9. golang学习笔记--接口

    go 的接口类型用于定义一组行为,其中每个行为都由一个方法声明表示. 接口类型中的方法声明只有方法签名而没有方法实体,而方法签名包括且仅包括方法的名称.参数列表和结果列表. 只要一种数据类型的方法集合 ...

  10. 【1】【leetcode-17】电话号码的字母组合

    给定一个仅包含数字 2-9 的字符串,返回所有它能表示的字母组合. 给出数字到字母的映射如下(与电话按键相同).注意 1 不对应任何字母. 示例: 输入:"23"输出:[" ...