MySql存储过程:保留每月末数据及最新一版数据

本文介绍了一个MySQL存储过程,用于在数据库中保留每月末的数据以及最新的记录。通过循环遍历表中的日期,对比当前月份与指定保留月份数,决定是否删除中间的旧数据。

CREATE PROCEDURE `p_preserve_his`(in months int)
BEGIN
    Declare lastMonth date;
    Declare lastdt int;
    Declare statisdate int;
    DECLARE i int;
    DECLARE loop_index int DEFAULT 0;
    DECLARE done boolean DEFAULT FALSE;
    DECLARE need_delete boolean DEFAULT TRUE;
    DECLARE cur CURSOR FOR select distinct dt from $table_name order by dt DESC;
    DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = TRUE;
    
    OPEN cur;
    read_loop: LOOP
  
        FETCH next from cur INTO statisdate;
        #结束循环
        IF done THEN
          LEAVE read_loop;
        END IF;
        
        set loop_index = loop_index + 1;
        #最大日期
        if loop_index = 1 then
           ITERATE read_loop;
        end if;
       
        #设置一个标记
           outer_label: BEGIN 
               set need_delete = true;
               SET i = 1;
            WHILE i  <= months DO
                 select last_day(date_add(curdate()-day(curdate())+1,interval -i month )) into lastMonth;
                 Set lastdt

 = DATE_FORMAT(lastMonth,'%Y%m%d');
                 if statisdate = lastdt then
                  set need_delete = false;
                  #需要保留
                  LEAVE outer_label;
                  end if;
                 SET  i = i + 1; 
             END WHILE;
            
            if need_delete then
                 #不需要保留
                    delete from $table_name where dt = statisdate;
                   commit;
               end if;
        END outer_label;
    END LOOP;
    CLOSE cur;

END

<think> 我们有个需求:每月自动执行SQL更新语句,将`finance_indicator`表中`fillDate`为当月的记录的`deptName`更新为`h_org_department`表中对应的部门名称。 解决方案:使用MySQL事件调度器(Event Scheduler)来定期执行更新操作。 步骤: 1. 确保MySQL事件调度器已开启。 2. 创建存储过程(可选,但这里可以直接用事件包含SQL语句)。 3. 创建事件,设置执行周期为每月最后天执行。 详细步骤: 1. 检查事件调度器状态: ```sql SHOW VARIABLES LIKE 'event_scheduler'; ``` 如果返回值为`ON`,则已开启;如果为`OFF`,则需要开启(需要权限): ```sql SET GLOBAL event_scheduler = ON; ``` 或者修改MySQL配置文件(永久生效)。 2. 创建事件: 我们想要在每月最后天的某个时间(比如23:30)执行更新。由于每月天数不同,我们可以使用`LAST_DAY`函数来获取当月的最后天,然后设置事件在`LAST_DAY(CURRENT_DATE) 23:30:00`执行。 但是,注意:事件调度器中的`AT`时间必须是常量或固定表达式,不能是动态的(如每月最后天是变化的)。因此,我们可以将事件设置为每月1日的前几分钟(比如00:05)执行,更新上个月的数据。或者,我们可以设置事件在每月最后天的23:30执行,但这样就需要动态计算时间,而事件调度器不支持在`ON SCHEDULE`子句中使用函数动态计算日期(除了使用内置的表达式如`EVERY`)。 另种思路:将事件设置为每月1日00:00执行,更新上个月的数据(因为此时上个月已经结束)。这样更简单可靠。 因此,我们可以这样: 事件执行时间:`EVERY 1 MONTH STARTS '具体开始日期 00:00:00'` 然后在事件中,我们将更新条件设置为上个月(即`fillDate = DATE_FORMAT(DATE_SUB(CURRENT_DATE, INTERVAL 1 MONTH), '%Y-%m')`) 例如:在每月1日00:00执行,更新`fillDate`为上月(如当前是2025-02-01,则更新fillDate='2025-01'的数据) 注意:我们也可以使用`CURRENT_DATE - INTERVAL 1 MONTH`来获取上个月的日期,然后格式化为'YYYY-mm'。 事件定义如下: ```sql DELIMITER $$ CREATE EVENT update_dept_name_monthly ON SCHEDULE EVERY 1 MONTH STARTS '2025-01-01 00:00:00' -- 从2025年1月1日开始,每月执行次 ON COMPLETION PRESERVE -- 执行后保留事件 DO BEGIN -- 设置上个月的日期字符串,格式为'YYYY-mm' SET @last_month = DATE_FORMAT(DATE_SUB(CURRENT_DATE, INTERVAL 1 MONTH), '%Y-%m'); -- 执行更新 UPDATE finance_indicator AS main JOIN h_org_department AS dept ON main.departmentId = dept.id SET main.deptName = dept.`name` WHERE main.fillDate = @last_month; END$$ DELIMITER ; ``` 但是,注意:在事件中直接使用用户变量(@last_month)可能不是最佳实践,我们可以使用局部变量(在存储过程中)或者将表达式直接写入UPDATE语句。 优化:将变量计算直接放入UPDATE的WHERE子句中,避免使用用户变量(更安全)。 修改为: ```sql DELIMITER $$ CREATE EVENT update_dept_name_monthly ON SCHEDULE EVERY 1 MONTH STARTS '2025-01-01 00:00:00' -- 实际使用时,可以设置为未来的某个时间,比如下个月1号开始 ON COMPLETION PRESERVE DO UPDATE finance_indicator AS main JOIN h_org_department AS dept ON main.departmentId = dept.id SET main.deptName = dept.`name` WHERE main.fillDate = DATE_FORMAT(DATE_SUB(CURRENT_DATE, INTERVAL 1 MONTH), '%Y-%m'); $$ DELIMITER ; ``` 但是,这里有个问题:在事件中,如果UPDATE语句本身是个独立的语句,则不需要使用BEGIN...END复合语句块(除非有多个语句)。所以如果只有个UPDATE,可以不用BEGIN...END。 因此,简化: ```sql DELIMITER $$ CREATE EVENT update_dept_name_monthly ON SCHEDULE EVERY 1 MONTH STARTS '2025-01-01 00:00:00' ON COMPLETION PRESERVE DO UPDATE finance_indicator AS main INNER JOIN h_org_department AS dept ON main.departmentId = dept.id SET main.deptName = dept.`name` WHERE main.fillDate = DATE_FORMAT(DATE_SUB(CURRENT_DATE, INTERVAL 1 MONTH), '%Y-%m'); DELIMITER ; ``` 注意:这里我们使用了`INNER JOIN`,因为只有存在部门表中的部门才更新。如果有些部门在部门表中不存在,而我们不想更新那些记录,那这样是合适的。如果想将不存在的部门名称更新为NULL,则可以使用`LEFT JOIN`,然后设置`dept.name`(注意处理NULL)。 根据原语句,我们使用的是JOIN(即INNER JOIN),所以只更新匹配的部门。 3. 事件创建后,可以通过以下命令查看事件: ```sql SHOW EVENTS; ``` 4. 如果以后需要修改事件,可以使用`ALTER EVENT`,或者先删除再创建。 5. 测试事件:可以临时创建个立即执行的事件进行测试,或者手动执行事件中的UPDATE语句,将`DATE_SUB(CURRENT_DATE, INTERVAL 1 MONTH)`替换为具体的日期,看是否符合预期。 注意:事件的执行需要权限,请确保创建事件的用户有`EVENT`权限。 补充:如果担心事件执行时数据量太大,可以考虑在`finance_indicator`表的`fillDate``departmentId`上建立索引。 问题:为什么选择每月1日00:00执行? 因为每月最后天可能因为月份不同(28,29,30,31日)而难以统,且事件调度器不支持动态的最后天。而每月1日0点执行,可以确保上个月的数据已经全部入库(假设在1日0点前已经完成了上个月的数据录入)。如果数据录入有延迟,可能需要调整执行时间(比如每月1日02:00)。 根据实际业务调整时间。 因此,最终方案是创建每月执行次的事件,在每月1日的指定时间更新上个月的数据。 如果业务要求必须更新当月的数据(即在当月最后天更新当月数据),那么我们可以将事件设置为在每月最后天的某个时间执行。但是,由于事件调度器不允许在`STARTS`中动态设置日期(如每月最后天),我们可以这样: 事件执行频率改为每天执行次,但只在每月最后天执行更新操作。 例如: ```sql DELIMITER $$ CREATE EVENT update_dept_name_monthly ON SCHEDULE EVERY 1 DAY STARTS '2025-01-01 23:30:00' -- 每天执行 ON COMPLETION PRESERVE DO -- 检查今天是否是当月的最后天 IF (CURRENT_DATE = LAST_DAY(CURRENT_DATE)) THEN -- 更新当月数据:注意,这里我们更新的是当月,所以fillDate应该是本月 UPDATE finance_indicator AS main INNER JOIN h_org_department AS dept ON main.departmentId = dept.id SET main.deptName = dept.`name` WHERE main.fillDate = DATE_FORMAT(CURRENT_DATE, '%Y-%m'); END IF; $$ DELIMITER ; ``` 这样,事件每天都会检查,但只在最后天执行更新。但是,这样每天执行个检查,如果不需要的话会增加负担。所以,如果能够接受在次月初更新上个月的数据,还是推荐第种方案(每月1日执行)。 根据用户需求,用户要求更新的是“当月”的数据,但注意:在当月最后天,当月数据已经完整,所以可以在最后天更新。所以,如果选择最后天更新,我们采用第二种每天检查的方案。 但是,我们也可以结合两种方案:创建两个事件,个在非月末日不执行任何操作,另个在每月1日执行(作为备用,以防月末事件执行失败)。但这样可能更复杂。 鉴于简单性可靠性,我们建议在次月初更新上个月的数据(即第种方案),并将fillDate理解为上个月(因为当月的最后天可能还没到,数据可能不全,而在次月初更新,则上个月的数据已经确定)。 用户需求是:“每月自动执行SQL语句更新finance_indicator表中fillDate为当月的deptName”。所以,应在月末更新当月数据。 因此,我们采用每天检查的方案(第二种)。但注意:如果个月有31天,那么事件会在31号执行,但有些月份只有30天、29天或28天,所以用`LAST_DAY`函数判断最后天是准确的。 优化:为了避免每天执行,我们可以使用更复杂的事件调度(比如在每月25-31日每天执行),但MySQL的事件调度器不支持这种调度。所以,要么每天执行,要么使用第种方案。 综合考虑,如果数据量不大,每天执行次检查(即使只是判断日期)开销很小,可以采用第二种方案。 因此,我们给出两种方案供选择: 方案(推荐,次月初更新上月数据): ```sql DELIMITER $$ CREATE EVENT update_dept_name_monthly ON SCHEDULE EVERY 1 MONTH STARTS '2025-01-01 00:00:00' -- 请根据实际情况调整开始时间 ON COMPLETION PRESERVE DO UPDATE finance_indicator AS main INNER JOIN h_org_department AS dept ON main.departmentId = dept.id SET main.deptName = dept.`name` WHERE main.fillDate = DATE_FORMAT(DATE_SUB(CURRENT_DATE, INTERVAL 1 MONTH), '%Y-%m'); DELIMITER ; ``` 方案二(当月最后天更新): ```sql DELIMITER $$ CREATE EVENT update_dept_name_daily_check ON SCHEDULE EVERY 1 DAY STARTS '2025-01-01 23:30:00' -- 每天23:30执行 ON COMPLETION PRESERVE DO IF (CURRENT_DATE = LAST_DAY(CURRENT_DATE)) THEN UPDATE finance_indicator AS main INNER JOIN h_org_department AS dept ON main.departmentId = dept.id SET main.deptName = dept.`name` WHERE main.fillDate = DATE_FORMAT(CURRENT_DATE, '%Y-%m'); END IF; DELIMITER ; ``` 注意:在方案二中,我们使用`CURRENT_DATE`来获取当前日期(不包含时间),并与当月的最后天比较。然后在更新语句中,`fillDate`的条件为`DATE_FORMAT(CURRENT_DATE, '%Y-%m')`,即当月。 但是,这里有个问题:在当月的最后天,我们更新的是`fillDate`为当月的记录,这符合要求。 两种方案的选择: - 方案更简单,执行频率低,但更新的是上个月的数据(在次月初更新上个月的数据,实际上在事件执行时,fillDate的值已经是上个月了)。 - 方案二每天执行,但只在最后天做更新,更新的是当月数据。 根据用户需求,要求更新的是“当月”数据,所以方案二更符合。但要注意,在最后天,可能有些数据还未生成,所以更新时间可以设置在晚上业务低峰期(如23:30)。 因此,我们按方案二给出最终代码。 但是,请根据实际业务情况选择。 另外,如果采用方案二,事件定义中的`IF`语句需要在事件中写多个语句,所以必须使用`BEGIN...END`,并且要修改`DELIMITER`。同时,需要确保事件有执行`IF`语句的权限。 我们调整方案二的事件定义(因为使用了复合语句,必须用`BEGIN...END`): ```sql DELIMITER $$ CREATE EVENT update_dept_name_daily_check ON SCHEDULE EVERY 1 DAY STARTS '2025-01-01 23:30:00' -- 每天23:30执行 ON COMPLETION PRESERVE DO BEGIN DECLARE last_day_flag BOOLEAN DEFAULT (CURRENT_DATE = LAST_DAY(CURRENT_DATE)); IF (last_day_flag) THEN UPDATE finance_indicator AS main INNER JOIN h_org_department AS dept ON main.departmentId = dept.id SET main.deptName = dept.`name` WHERE main.fillDate = DATE_FORMAT(CURRENT_DATE, '%Y-%m'); END IF; END$$ DELIMITER ; ``` 这里我们使用了个局部变量`last_day_flag`来存储判断结果。 或者可以直接在IF条件中写表达式: ```sql DELIMITER $$ CREATE EVENT update_dept_name_daily_check ON SCHEDULE EVERY 1 DAY STARTS '2025-01-01 23:30:00' -- 每天23:30执行 ON COMPLETION PRESERVE DO BEGIN IF (CURRENT_DATE = LAST_DAY(CURRENT_DATE)) THEN UPDATE finance_indicator AS main INNER JOIN h_org_department AS dept ON main.departmentId = dept.id SET main.deptName = dept.`name` WHERE main.fillDate = DATE_FORMAT(CURRENT_DATE, '%Y-%m'); END IF; END$$ DELIMITER ; ``` 注意:`CURRENT_DATE`返回当前日期,`LAST_DAY(CURRENT_DATE)`返回当月的最后天。如果当前日期等于最后天,则执行更新。 但是,需要注意时区问题:确保事件执行时的时区与数据时区致。 最后,我们选择方案二,因为它严格满足用户需求(在月末更新当月数据)。 创建事件后,可以通过以下命令查看事件状态: ```sql SHOW EVENTS FROM your_database_name; -- 或者 SHOW EVENTS; ``` 也可以手动执行次测试(如果当前日期是最后天): ```sql -- 手动执行事件(通常不支持直接执行事件,可以单独执行事件中的语句) -- 我们可以在测试时,将事件中的UPDATE语句单独拿出来,并替换变量: UPDATE finance_indicator AS main INNER JOIN h_org_department AS dept ON main.departmentId = dept.id SET main.deptName = dept.`name` WHERE main.fillDate = DATE_FORMAT(CURRENT_DATE, '%Y-%m'); ``` 请根据实际情况调整。如果数据量很大,建议在低峰期执行。 注意:如果使用方案二,事件每天都会执行次,所以即使不是最后天也会触发(但只做条件判断,开销很小)。 所以,我们最终给出方案二的事件创建代码。 但用户需要根据实际情况调整开始时间(`STARTS`)执行的精确时间(比如23:30也许不合适,可以更晚或更早,视业务情况而定)。 另外,如果用户希望减少事件执行次数,可以考虑使用MySQL事件更复杂的调度(比如在每月25-31日的每天执行),但MySQL事件不支持这样的周期设置。所以,要么每天执行,要么使用方案。 如果用户希望只在月末最后天执行,而不在其他天执行,且不想每天运行,可以考虑使用操作系统级的定时任务(如cron)来执行,但这样就需要外部脚本。 因此,在纯MySQL解决方案中,我们采用方案二(每天执行判断)或方案(次月初执行)。 根据用户需求,我们选择方案二。 创建事件代码(方案二): </think> ### 每月自动更新部门名称的实现方案 #### 方案概述 要实现每月自动更新财务指标表中的部门名称,建议使用MySQL事件调度器(Event Scheduler)创建定时任务。以下是具体实现步骤: #### 1. 启用MySQL事件调度器 首先确保MySQL事件调度器已启用: ```sql -- 检查当前状态 SHOW VARIABLES LIKE 'event_scheduler'; -- 若未启用则执行(需SUPER权限) SET GLOBAL event_scheduler = ON; ``` *注意:在my.cnf配置文件中添加 `event_scheduler=ON` 可永久启用[^1]* #### 2. 创建每月执行的事件 ```sql DELIMITER $$ CREATE EVENT update_dept_name_monthly ON SCHEDULE EVERY 1 MONTH STARTS TIMESTAMP(DATE_FORMAT(NOW() + INTERVAL 1 MONTH, '%Y-%m-01 02:00:00')) ON COMPLETION PRESERVE DO BEGIN SET @target_month = DATE_FORMAT(NOW() - INTERVAL 1 DAY, '%Y-%m'); UPDATE finance_indicator AS main JOIN h_org_department AS dept ON main.departmentId = dept.id SET main.deptName = dept.`name` WHERE main.fillDate = @target_month; END$$ DELIMITER ; ``` #### 关键参数说明: 1. **执行周期**: - `EVERY 1 MONTH`:每月执行次 - `STARTS ...`:从下个月1日凌晨2点开始执行(避开业务高峰) 2. **日期计算**: - `NOW() - INTERVAL 1 DAY`:获取上月末日期 - `DATE_FORMAT(..., '%Y-%m')`:生成'YYYY-MM'格式的月份标识符 3. **更新逻辑**: - 通过`@target_month`变量动态设置目标月份 - 使用JOIN确保部门ID匹配时更新名称 #### 3. 事件管理命令 ```sql -- 查看所有事件 SHOW EVENTS; -- 临时禁用事件 ALTER EVENT update_dept_name_monthly DISABLE; -- 删除事件 DROP EVENT IF EXISTS update_dept_name_monthly; ``` #### 注意事项 1. **执行时间选择**: - 每月1日凌晨执行(示例设为02:00) - 避开月末最后时刻(避免与数据录入冲突) - 实际时间应根据业务负载调整 2. **日期边界处理**: - 使用`NOW() - INTERVAL 1 DAY`确保获取完整月份 - 闰年2月等特殊情况自动正确处理 3. **权限要求**: ```sql GRANT EVENT ON database_name.* TO 'user'@'host'; ``` #### 备选方案:外部调度器 若需更灵活调度(如精确到月末最后天23:59),建议使用外部调度工具: ```bash # Linux crontab示例(每月最后天23:50执行) 50 23 28-31 * * [ $(date -d tomorrow +\%d) -eq 1 ] && mysql -uuser -p db_name -e "UPDATE finance_indicator..." ``` #### 性能优化建议 1. 在`finance_indicator.fillDate``departmentId`上创建索引 2. 大表更新时添加`LIMIT 1000`分批次执行 3. 监控事件执行日志: ```sql SELECT * FROM performance_schema.events_statements_history; ```
评论 1
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值