MySQL Workbench创建Procedure报错求助:1064语法错误排查
修复MySQL存储过程的语法错误
我帮你仔细排查了这个MySQL存储过程里的语法问题,大多是因为把SQL Server(T-SQL)的习惯带到MySQL里导致的,下面是详细的错误分析和修复后的完整代码:
主要错误点梳理
- 变量声明位置错误:MySQL要求所有变量声明必须放在
BEGIN块的最开头,不能在代码逻辑中途(比如while循环里)声明变量 - 临时表命名错误:MySQL不支持
#前缀的临时表,要用CREATE TEMPORARY TABLE esubmit(...)的标准语法 - 字符串拼接错误:MySQL没有
+=拼接运算符,必须用CONCAT()或CONCAT_WS()函数实现字符串拼接 - 游标未定义直接使用:你用到了游标
c和c1但没有提前声明、定义,也没有处理游标结束的NOT FOUND事件 - 变量声明语法错误:多个同类型变量声明可以用逗号分隔,但要统一格式;不同类型变量要分开声明
- 动态SQL的变量作用域问题:局部变量不能直接用于
PREPARE语句,需要用会话级变量(带@前缀)或者调整逻辑 - 分隔符设置缺失:创建存储过程时需要先修改语句分隔符(比如
DELIMITER $$),避免和语句内的分号冲突
修复后的完整存储过程代码
DELIMITER $$ DROP PROCEDURE IF EXISTS test_mysql_while_loop$$ DROP TEMPORARY TABLE IF EXISTS esubmit$$ CREATE PROCEDURE test_mysql_while_loop() BEGIN -- 所有变量声明必须放在BEGIN块的最开头 DECLARE v_startdate VARCHAR(45); DECLARE v_enddate VARCHAR(45); DECLARE v_sql LONGTEXT; DECLARE v_status INT; DECLARE v_count INT; DECLARE v_cid INT; DECLARE v_eid INT; DECLARE v_setdate VARCHAR(45); DECLARE v_setcnt INT; DECLARE v_getname VARCHAR(45); DECLARE done INT DEFAULT FALSE; DECLARE done_c1 INT DEFAULT FALSE; -- 初始化变量 SET v_startdate = '2018-05-22'; SET v_enddate = '2018-05-28'; SET v_status = 0; SET v_count = 1; -- 创建符合MySQL规范的临时表 CREATE TEMPORARY TABLE esubmit( id INT AUTO_INCREMENT PRIMARY KEY, Name VARCHAR(100) ); -- 动态拼接添加日期列的SQL语句 SET v_sql = 'ALTER TABLE esubmit ADD '; WHILE v_status != 1 DO SET @current_date = DATE_ADD(v_startdate, INTERVAL v_count DAY); IF @current_date = v_enddate THEN SET v_sql = CONCAT(v_sql, '`', @current_date, '` VARCHAR(45)'); SET v_status = 1; ELSE SET v_sql = CONCAT(v_sql, '`', @current_date, '` VARCHAR(45), '); END IF; SET v_count = v_count + 1; END WHILE; -- 执行动态SQL添加日期列 PREPARE stmt FROM v_sql; EXECUTE stmt; DEALLOCATE PREPARE stmt; -- 声明游标c:遍历work与productdata的关联数据(可根据实际需求调整查询逻辑) DECLARE c CURSOR FOR SELECT w.prid, w.pid FROM work w JOIN productdata pd ON w.prid = pd.prid; -- 游标结束的处理逻辑 DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = TRUE; OPEN c; read_main_loop: LOOP FETCH c INTO v_cid, v_eid; IF done THEN LEAVE read_main_loop; END IF; -- 获取产品名称 SELECT prname INTO v_getname FROM productdata WHERE prid = v_cid; -- 先插入产品名称到临时表 INSERT INTO esubmit(Name) VALUES(v_getname); -- 声明游标c1:遍历日期范围内的所有日期 DECLARE c1 CURSOR FOR SELECT DATE_ADD(v_startdate, INTERVAL n DAY) FROM ( SELECT 1 n UNION SELECT 2 UNION SELECT 3 UNION SELECT 4 UNION SELECT 5 UNION SELECT 6 ) nums WHERE DATE_ADD(v_startdate, INTERVAL n DAY) <= v_enddate; DECLARE CONTINUE HANDLER FOR NOT FOUND SET done_c1 = TRUE; OPEN c1; date_loop: LOOP FETCH c1 INTO v_setdate; IF done_c1 THEN LEAVE date_loop; END IF; -- 查询指定日期的统计数量 SELECT COUNT(pid) INTO v_setcnt FROM pr_status ps WHERE ps.pid = v_eid AND DATE(ps.statusdate) = v_setdate AND ps.stage BETWEEN '4' AND '5'; -- 更新临时表对应的日期列值 SET @update_sql = CONCAT('UPDATE esubmit SET `', v_setdate, '` = ', IFNULL(v_setcnt, 0), ' WHERE Name = ''', v_getname, ''''); PREPARE stmt FROM @update_sql; EXECUTE stmt; DEALLOCATE PREPARE stmt; END LOOP date_loop; CLOSE c1; -- 重置游标c1的结束状态 SET done_c1 = FALSE; END LOOP read_main_loop; CLOSE c; END$$ DELIMITER ;
额外说明
- 我调整了原代码中插入数据的逻辑,改为先插入产品名称,再逐个更新日期列的统计值,这更符合MySQL的语法规范
- 日期范围生成用了子查询生成数字序列,如果你需要支持更长的日期范围,可以扩展这个子查询,或者用MySQL 8.0+支持的递归CTE
- 处理了
COUNT()返回NULL的情况,用IFNULL()将空值转为0,避免数据显示异常 - 游标
c的查询逻辑是基于你的表结构推测的,你可以根据实际业务需求修改这个游标查询
内容的提问来源于stack exchange,提问作者Ganesan Perumalsamy
相关产品推荐
相关产品推荐

