迁移到 MySQL8,存储过程中的游标导致导入错误

Migrating to MySQL8, cursor in stored procedure causes error on import

我正在将一个 Moodle 网站迁移到 MySQL8 并且核心系统没有遇到任何问题。但是,我们的一些报表依赖于两个自定义存储过程,其中一个如下:

CREATE DEFINER=`someuseraccount`@`somewhere` FUNCTION `getOnlineTimeForUser`(USERID_IN int) RETURNS int(11)
BEGIN

DECLARE currenttime integer;
DECLARE nexttime integer;
DECLARE count1 integer;
DECLARE count2 integer;
DECLARE totaldedication_time integer DEFAULT 0;
DECLARE dedication integer;
DECLARE session_start integer;
DECLARE done INT DEFAULT FALSE;
    
DECLARE cur CURSOR FOR SELECT  @rownum := @rownum + 1 as row_number, timecreated
FROM moodle.mdl_logstore_standard_log
cross join (select @rownum := 0) r
where userid=USERID_IN
order by timecreated asc;

DECLARE cur2 CURSOR FOR SELECT a.* FROM (SELECT  @rownum := @rownum + 1 as row_number, timecreated
FROM moodle.mdl_logstore_standard_log
cross join (select @rownum := 0) r
where userid=USERID_IN
order by timecreated asc) a where a.row_number = count1+1;

DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = TRUE;

OPEN cur;
    read_loop: LOOP

        FETCH cur INTO count1, currenttime;
        IF done THEN
            LEAVE read_loop;
        END IF;
        IF (count1 <2) THEN
            SET session_start = currenttime;
            SET totaldedication_time = 0;
        END IF;
        
        OPEN cur2;
            read_loop2: LOOP
                FETCH cur2 INTO count2, nexttime;
                IF done THEN
                    SET done = FALSE;
                    LEAVE read_loop2;
                END IF;

                IF (nexttime - currenttime) > 300 THEN
                    SET dedication = currenttime - session_start;
                    SET totaldedication_time = totaldedication_time + dedication;
                    SET session_start = nexttime;
                END IF;
            END LOOP;
        CLOSE cur2;
    END LOOP;
CLOSE cur;

SET totaldedication_time = totaldedication_time + dedication;

RETURN ceil(totaldedication_time/60);
    
END ;;

导入这些在 5.7.x 上运行良好的存储过程时,出现以下错误:

ERROR 1064 (42000) at line 36: You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'row_number, timecreated FROM moodle.mdl_logstore_standard_log cross join (se' at line 14

问题似乎出在第一个游标的定义中。如果我将整个过程从备份中注释掉并再次导入,下一个存储过程将失败并出现相同的错误,因此除非有人另有建议,否则为了简单起见我不会包括它。我对游标及其语法一点也不熟悉,而且我无法确定与我读过的有关升级问题的文章和答案有什么主要区别,关于为什么 5.7 中的这种语法可能不适用于 8.x,虽然当然这种语法可能已经在 5.7 中被弃用并在 8.x 中被删除 - 老实说,我真的在黑暗中游来游去。我不是 100% 确定我理解原始程序在做什么,这对我没有帮助。

ROW_NUMBER 是 MySQL 8.0 中的保留关键字。参见 https://dev.mysql.com/doc/refman/8.0/en/keywords.html

如果您需要将其用作标识符,请在每次使用时将其括在反引号中。

... SELECT  @rownum := @rownum + 1 as `row_number`, ...

或者重命名为非保留关键字的名称。

... SELECT  @rownum := @rownum + 1 as rownum, ...

另请注意,在 MySQL 8.0 中,他们不鼓励将变量赋值作为查询中的副作用的做法。 https://dev.mysql.com/doc/refman/8.0/en/user-variables.html 说:

Previous releases of MySQL made it possible to assign a value to a user variable in statements other than SET. This functionality is supported in MySQL 8.0 for backward compatibility but is subject to removal in a future release of MySQL.

在你的例子中,你只是用它来生成行号。这就是 ROW_NUMBER() 的用途,因此您可以将查询重写为:

SELECT ROW_NUMBER() OVER (ORDER BY timecreated) AS rownum, timecreated
FROM moodle.mdl_logstore_standard_log
WHERE userid=USERID_IN
ORDER BY timecreated ASC;

ROW_NUMBER() 只是 MySQL 8.0 中实现的众多标准 window 函数之一。请参阅 https://dev.mysql.com/doc/refman/8.0/en/window-functions.html 了解它们。

Window 函数是标准的 SQL 并且应该用于人们目前被迫使用 := 进行内联变量赋值的大多数方式。 Window 函数用途广泛,可以完成使用内联变量赋值技术难以做到的事情。