MySQL存储过程for循环处理查询结果方式

作者:技术拾光

时间:2026-06-13

浏览:365

MySQL存储过程可利用游标实现查询结果的循环处理,关键步骤包括声明游标、打开游标、循环取数、判断结束标识及关闭游标。通过FETCH逐行获取数据,结合LOOP循环执行业务逻辑,适用于批处理场景。

文章目录

在日常的 MySQL 使用中,存储过程是非常实用的功能——它相当于一组预先编译好的 SQL 指令,可以反复调用。然而,当存储过程查询出多条结果后,往往需要逐条处理这些数据。此时,循环就显得至关重要。接下来介绍如何在 MySQL 存储过程中实现查询结果的循环处理。

1. 创建表

先搭建一个简单的场景。我们准备两张表:t_job 用于存储任务,t_task 用于记录每次执行的具体情况。建表语句如下:

CREATE TABLE `t_job` (
  `job_id` int(11) unsigned NOT NULL AUTO_INCREMENT,
  `job_name` varchar(50) DEFAULT NULL,
  `next_time` timestamp NULL DEFAULT NULL COMMENT '下次执行时间',
  `last_task` int(11) DEFAULT NULL,
  PRIMARY KEY (`job_id`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8mb4;

CREATE TABLE `t_task` (
  `task_id` int(11) unsigned NOT NULL AUTO_INCREMENT,
  `start_time` datetime DEFAULT NULL,
  `end_time` datetime DEFAULT NULL,
  `status` tinyint(1) DEFAULT NULL,
  `job_id` int(11) NOT NULL,
  PRIMARY KEY (`task_id`)
) ENGINE=InnoDB AUTO_INCREMENT=25 DEFAULT CHARSET=utf8mb4;

2. 存储过程查询结果

2.1 创建存储过程

先给出一个基础版本,只处理单条记录。逻辑很简单:查询需要执行的 job,然后向 t_task 插入一条记录,同时更新 t_job 表的下次执行时间和最近一次任务 ID。

CREATE DEFINER=`root`@`%` PROCEDURE `p_sayn_job`()
BEGIN
  #Routine body goes here...
  DECLARE v_cnt INT;
  DECLARE v_job_id INT;

  SELECT count( 1 ) INTO v_cnt 
  FROM t_job j 
  WHERE j.next_time < SYSDATE();

  IF v_cnt > 0 THEN
    -- 插入数据
    INSERT INTO t_task ( start_time, end_time, STATUS, job_id )
    VALUES ( SYSDATE(), SYSDATE()+ 1, 1, v_job_id );

    -- 更新数据
    UPDATE t_job j 
    SET j.last_task = ( SELECT MAX( t.task_id ) FROM t_task t WHERE t.job_id = j.job_id ),
        j.next_time = DATE_ADD( j.next_time, INTERVAL 1 DAY ) 
    WHERE j.job_id = v_job_id;
  END IF;
END

这个版本只能处理一条记录,但如果符合条件的 job 有多条,就需要使用循环。

2.2 添加 for 循环语句

这里的关键是游标(Cursor)。简单来说,游标就是用来逐行遍历结果集的一个指针。下面拆开来看具体步骤。

先用 DECLARE 声明一个游标,把需要循环的查询结果放入:

-- DECLARE语句声明游标
DECLARE jobs CURSOR FOR 
  (SELECT j.job_id FROM t_job j WHERE j.next_time > SYSDATE());

接着声明一个变量来标记循环何时结束:

-- 声明变量
DECLARE v_finished  int DEFAULT FALSE;
-- 结束标识
DECLARE CONTINUE HANDLER FOR NOT FOUND SET v_finished = TRUE;

然后打开游标,开始处理:

-- OPEN语句打开游标
OPEN  jobs ;

真正循环迭代的部分,用一个 LOOP 结构包起来:

-- 循环迭代 jobs
read_loop : LOOP
END LOOP read_loop;

在循环内,用 FETCH 语句取出一行数据,同时将游标移动到下一行:

-- 使用FETCH语句检索光标指向的下一行,并将光标移动到结果集中的下一行。
FETCH jobs into v_job_id;

如何判断循环结束?使用之前声明的 v_finished 变量。当 FETCH 发现没有更多数据时,提前设置的 CONTINUE HANDLER 会将 v_finished 设为 TRUE:

-- 使用v_finished变量来检查列表是否有id来终止循环。
IF v_finished THEN
  LEA VE read_loop; 
END IF;

在这段判断之后,就可以放心地加入自己的业务逻辑。最后需要用 CLOSE 关闭游标,释放内存:

-- CLOSE语句以停用游标并释放与其关联的内存
CLOSE jobs;

将以上零件组合起来,就是一个完整的带循环的存储过程:

CREATE DEFINER=`root`@`%` PROCEDURE `p_sayn_job`()
BEGIN
  #Routine body goes here...
  DECLARE v_cnt INT;
  DECLARE v_finished  int DEFAULT FALSE;
  DECLARE v_job_id INT;

  -- DECLARE语句声明游标
  DECLARE jobs CURSOR FOR  
    (SELECT j.job_id FROM t_job j WHERE j.next_time > SYSDATE());

  -- 结束标识
  DECLARE CONTINUE HANDLER FOR NOT FOUND SET v_finished = TRUE;

  SELECT count( 1 ) INTO v_cnt 
  FROM t_job j 
  WHERE j.next_time < SYSDATE();

  IF v_cnt > 0 THEN
    -- OPEN语句打开游标
    OPEN  jobs ;

    -- 循环迭代 jobs
    read_loop : LOOP
      -- 使用FETCH语句检索光标指向的下一行,并将光标移动到结果集中的下一行。
      FETCH jobs into v_job_id;

      -- 使用v_finished变量来检查列表是否有id来终止循环。
      IF v_finished THEN
        LEA VE read_loop; 
      END IF;

      -- 处理业务SQL就在这了
      INSERT INTO t_task ( start_time, end_time, STATUS, job_id )
      VALUES ( SYSDATE(), SYSDATE()+ 1, 1, v_job_id );

      UPDATE t_job j 
      SET j.last_task = ( SELECT MAX( t.task_id ) FROM t_task t WHERE t.job_id = j.job_id ),
          j.next_time = DATE_ADD( j.next_time, INTERVAL 1 DAY ) 
      WHERE j.job_id = v_job_id;

    END LOOP read_loop;

    -- CLOSE语句以停用游标并释放与其关联的内存
    CLOSE jobs;
  END IF;
END

MySQL存储过程for循环处理查询结果方式

2.3 保存执行存储过程

编写完成后,调用一下验证效果:

CALL p_sayn_job();

MySQL存储过程for循环处理查询结果方式

总结

游标的用法并不复杂,关键步骤包括:声明、打开、循环取数、判断结束、关闭。许多复杂的数据处理逻辑,都可以通过上述方式逐一拆解。希望这篇文章能帮助你少走弯路。

实用工具推荐

电子包浆

绿化、压缩、噪点和虚拟水印,自由调节做旧程度。

使用 ›
图片压缩

选择画质和输出格式,预览压缩前后的大小。

使用 ›
相关文章