Mysql系列的目標是:通過這個系列從入門到全面掌握一個高級開發所需要的全部技能。
這是Mysql系列第19篇。
環境:mysql5.7.25,cmd命令中進行演示。
代碼中被[]包含的表示可選,|符號分開的表示可選其一。
需求背景
當我們需要對一個select的查詢結果進行遍歷處理的時候,如何實現呢?
此時我們需要使用游標,通過游標的方式來遍歷select查詢的結果集,然后對每行數據進行處理。
本篇內容
- 游標定義
- 游標作用
- 游標使用步驟
- 游標執行過程詳解
- 單游標示例
- 嵌套游標示例
准備數據
創建庫:javacode2018
創建表:test1、test2、test3
/*建庫javacode2018*/
drop database if exists javacode2018;
create database javacode2018;
/*切換到javacode2018庫*/
use javacode2018;
DROP TABLE IF EXISTS test1;
CREATE TABLE test1(a int,b int);
INSERT INTO test1 VALUES (1,2),(3,4),(5,6);
DROP TABLE IF EXISTS test2;
CREATE TABLE test2(a int);
INSERT INTO test2 VALUES (100),(200),(300);
DROP TABLE IF EXISTS test3;
CREATE TABLE test3(b int);
INSERT INTO test3 VALUES (400),(500),(600);
游標定義
游標(Cursor)是處理數據的一種方法,為了查看或者處理結果集中的數據,游標提供了在結果集中一次一行遍歷數據的能力。
游標只能在存儲過程和函數中使用。
游標的作用
如sql:
select a,b from test1;
上面這個查詢返回了test1中的數據,如果我們想對這些數據進行遍歷處理,此時我們就可以使用游標來進行操作。
游標相當於一個指針,這個指針指向select的第一行數據,可以通過移動指針來遍歷后面的數據。
游標的使用步驟
聲明游標:這個過程只是創建了一個游標,需要指定這個游標需要遍歷的select查詢,聲明游標時並不會去執行這個sql。
打開游標:打開游標的時候,會執行游標對應的select語句。
遍歷數據:使用游標循環遍歷select結果中每一行數據,然后進行處理。
關閉游標:游標使用完之后一定要關閉。
游標語法
聲明游標
DECLARE 游標名稱 CURSOR FOR 查詢語句;
一個begin end中只能聲明一個游標。
打開游標
open 游標名稱;
遍歷游標
fetch 游標名稱 into 變量列表;
取出當前行的結果,將結果放在對應的變量中,並將游標指針指向下一行的數據。
當調用fetch的時候,會獲取當前行的數據,如果當前行無數據,會引發mysql內部的
NOT FOUND
錯誤。
關閉游標
close 游標名稱;
游標使用完畢之后一定要關閉。
單游標示例
寫一個函數,計算test1表中a、b字段所有的和。
創建函數:
/*刪除函數*/
DROP FUNCTION IF EXISTS fun1;
/*聲明結束符為$*/
DELIMITER $
/*創建函數*/
CREATE FUNCTION fun1(v_max_a int)
RETURNS int
BEGIN
/*用於保存結果*/
DECLARE v_total int DEFAULT 0;
/*創建一個變量,用來保存當前行中a的值*/
DECLARE v_a int DEFAULT 0;
/*創建一個變量,用來保存當前行中b的值*/
DECLARE v_b int DEFAULT 0;
/*創建游標結束標志變量*/
DECLARE v_done int DEFAULT FALSE;
/*創建游標*/
DECLARE cur_test1 CURSOR FOR SELECT a,b from test1 where a<=v_max_a;
/*設置游標結束時v_done的值為true,可以v_done來判斷游標是否結束了*/
DECLARE CONTINUE HANDLER FOR NOT FOUND SET v_done=TRUE;
/*設置v_total初始值*/
SET v_total = 0;
/*打開游標*/
OPEN cur_test1;
/*使用Loop循環遍歷游標*/
a:LOOP
/*先獲取當前行的數據,然后將當前行的數據放入v_a,v_b中,如果當前行無數據,v_done會被置為true*/
FETCH cur_test1 INTO v_a, v_b;
/*通過v_done來判斷游標是否結束了,退出循環*/
if v_done THEN
LEAVE a;
END IF;
/*對v_total值累加處理*/
SET v_total = v_total + v_a + v_b;
END LOOP;
/*關閉游標*/
CLOSE cur_test1;
/*返回結果*/
RETURN v_total;
END $
/*結束符置為;*/
DELIMITER ;
上面語句執行過程中可能有問題,解決方式如下。
錯誤信息:Mysql 創建函數出現This function has none of DETERMINISTIC, NO SQL, or READS SQL DATA
This function has none of DETERMINISTIC, NO SQL, or READS SQL DATA in its declaration and binary
mysql的設置默認是不允許創建函數
解決辦法1:
執行:
SET GLOBAL log_bin_trust_function_creators = 1;
不過 重啟了 就失效了
注意: 有主從復制的時候 從機必須要設置 不然會導致主從同步失敗
解決辦法2:
在my.cnf里面設置
log-bin-trust-function-creators=1
不過這個需要重啟服務
見效果:
mysql> SELECT a,b FROM test1;
+------+------+
| a | b |
+------+------+
| 1 | 2 |
| 3 | 4 |
| 5 | 6 |
+------+------+
3 rows in set (0.00 sec)
mysql> SELECT fun1(1);
+---------+
| fun1(1) |
+---------+
| 3 |
+---------+
1 row in set (0.00 sec)
mysql> SELECT fun1(2);
+---------+
| fun1(2) |
+---------+
| 3 |
+---------+
1 row in set (0.00 sec)
mysql> SELECT fun1(3);
+---------+
| fun1(3) |
+---------+
| 10 |
+---------+
1 row in set (0.00 sec)
游標過程詳解
以上面的示例代碼為例,咱們來看一下游標的詳細執行過程。
游標中有個指針,當打開游標的時候,才會執行游標對應的select語句,這個指針會指向select結果中第一行記錄。
當調用fetch 游標名稱
時,會獲取當前行的數據,如果當前行無數據,會觸發NOT FOUND
異常。
當觸發NOT FOUND
異常的時候,我們可以使用一個變量來標記一下,如下代碼:
DECLARE CONTINUE HANDLER FOR NOT FOUND SET v_done=TRUE;
當游標無數據觸發NOT FOUND
異常的時候,將變量v_down
的值置為TURE
,循環中就可以通過v_down
的值控制循環的退出。
如果當前行有數據,則將當前行數據存到對應的變量中,並將游標指針指向下一行數據,如下語句:
fetch 游標名稱 into 變量列表;
嵌套游標
寫個存儲過程,遍歷test2、test3,將test2中的a字段和test3中的b字段任意組合,插入到test1表中。
創建存儲過程:
/*刪除存儲過程*/
DROP PROCEDURE IF EXISTS proc1;
/*聲明結束符為$*/
DELIMITER $
/*創建存儲過程*/
CREATE PROCEDURE proc1()
BEGIN
/*創建一個變量,用來保存當前行中a的值*/
DECLARE v_a int DEFAULT 0;
/*創建游標結束標志變量*/
DECLARE v_done1 int DEFAULT FALSE;
/*創建游標*/
DECLARE cur_test1 CURSOR FOR SELECT a FROM test2;
/*設置游標結束時v_done1的值為true,可以v_done1來判斷游標cur_test1是否結束了*/
DECLARE CONTINUE HANDLER FOR NOT FOUND SET v_done1=TRUE;
/*打開游標*/
OPEN cur_test1;
/*使用Loop循環遍歷游標*/
a:LOOP
FETCH cur_test1 INTO v_a;
/*通過v_done1來判斷游標是否結束了,退出循環*/
if v_done1 THEN
LEAVE a;
END IF;
BEGIN
/*創建一個變量,用來保存當前行中b的值*/
DECLARE v_b int DEFAULT 0;
/*創建游標結束標志變量*/
DECLARE v_done2 int DEFAULT FALSE;
/*創建游標*/
DECLARE cur_test2 CURSOR FOR SELECT b FROM test3;
/*設置游標結束時v_done1的值為true,可以v_done1來判斷游標cur_test2是否結束了*/
DECLARE CONTINUE HANDLER FOR NOT FOUND SET v_done2=TRUE;
/*打開游標*/
OPEN cur_test2;
/*使用Loop循環遍歷游標*/
b:LOOP
FETCH cur_test2 INTO v_b;
/*通過v_done1來判斷游標是否結束了,退出循環*/
if v_done2 THEN
LEAVE b;
END IF;
/*將v_a、v_b插入test1表中*/
INSERT INTO test1 VALUES (v_a,v_b);
END LOOP b;
/*關閉cur_test2游標*/
CLOSE cur_test2;
END;
END LOOP;
/*關閉游標cur_test1*/
CLOSE cur_test1;
END $
/*結束符置為;*/
DELIMITER ;
見效果:
mysql> DELETE FROM test1;
Query OK, 9 rows affected (0.00 sec)
mysql> SELECT * FROM test1;
Empty set (0.00 sec)
mysql> CALL proc1();
Query OK, 0 rows affected (0.02 sec)
mysql> SELECT * from test1;
+------+------+
| a | b |
+------+------+
| 100 | 400 |
| 100 | 500 |
| 100 | 600 |
| 200 | 400 |
| 200 | 500 |
| 200 | 600 |
| 300 | 400 |
| 300 | 500 |
| 300 | 600 |
+------+------+
9 rows in set (0.00 sec)
成功插入了9條數據。
總結
- 游標用來對查詢結果進行遍歷處理
- 游標的使用過程:聲明游標、打開游標、遍歷游標、關閉游標
- 游標只能在存儲過程和函數中使用
- 一個begin end中只能聲明一個游標
- 掌握單個游標及嵌套游標的使用
- 大家下去了多練習一下,熟練掌握游標的使用
Mysql系列目錄
- 第1篇:mysql基礎知識
- 第2篇:詳解mysql數據類型(重點)
- 第3篇:管理員必備技能(必須掌握)
- 第4篇:DDL常見操作
- 第5篇:DML操作匯總(insert,update,delete)
- 第6篇:select查詢基礎篇
- 第7篇:玩轉select條件查詢,避免采坑
- 第8篇:詳解排序和分頁(order by & limit)
- 第9篇:分組查詢詳解(group by & having)
- 第10篇:常用的幾十個函數詳解
- 第11篇:深入了解連接查詢及原理
- 第12篇:子查詢
- 第13篇:細說NULL導致的神坑,讓人防不勝防
- 第14篇:詳解事務
- 第15篇:詳解視圖
- 第16篇:變量詳解
- 第17篇:存儲過程&自定義函數詳解
- 第18篇:流程控制語句
- 第19篇:游標詳解
- 第20篇:異常捕獲及處理詳解
- 第21篇:什么是索引?
mysql系列大概有20多篇,喜歡的請關注一下,歡迎大家加我微信itsoku或者留言交流mysql相關技術!