如何循环遍历表中的所有行?(MySQL)

121

我有一个表A,它有一个主键ID。

现在我想遍历A中的所有行。

我找到了类似'for each record in A'的东西,但这似乎不是在MySQL中的正确方法。

问题是,对于每一行,我想取一个字段并将其转换,插入另一个表中,然后更新一些行的字段。我可以将select部分和insert放在一个语句中,但我不知道如何将update也放在其中。所以我想循环。而且出于练习,我不想使用除MySQL之外的任何东西。

编辑

我会欣赏一个例子。

并且这个解决方案不需要放入一个过程中。

编辑2

好的,考虑这种情况:

表A和B,每个都有字段ID和VAL。

现在这是我想做的伪代码:

for(each row in A as rowA)
{
  insert into B(ID, VAL) values(rowA[ID], rowA[VAL]);
}

使用循环将A的内容复制到B。

(这只是一个简化的例子,当然你不会为此使用循环。)


嗨Rafeel,对我来说它给出了错误: My SQL> for (each row in wp_mobiune_ecommune_user as x){ insert into wp_mobiune_ecommune_user (gender_new) values (x[gender])};RROR 1064(42000):您的SQL语法有误;请检查与您的MySQL服务器版本相对应的手册以获取正确的语法,在第1行附近使用“for(each row in wp_mobiune_ecommune_user as x){insert into wp_mobiune_ecommune”的语法。 - dinesh kandpal
5个回答

175

因为循环的建议意味着需要一种过程类型的解决方案。这是我的解决方案。

任何作用于表中单个记录的查询都可以被包装在一个过程中,使其遍历表中的每一行,如下所示:

首先删除具有相同名称的任何现有过程,并更改分隔符,以便您的SQL不会尝试将每行作为您尝试编写过程而运行。

DROP PROCEDURE IF EXISTS ROWPERROW;
DELIMITER ;;

那么这里是按照您的示例进行操作的步骤(为了清晰起见使用table_A和table_B)

CREATE PROCEDURE ROWPERROW()
BEGIN
DECLARE n INT DEFAULT 0;
DECLARE i INT DEFAULT 0;
SELECT COUNT(*) FROM table_A INTO n;
SET i=0;
WHILE i<n DO 
  INSERT INTO table_B(ID, VAL) SELECT (ID, VAL) FROM table_A LIMIT i,1;
  SET i = i + 1;
END WHILE;
End;
;;

那么不要忘记重置分隔符

DELIMITER ;

并运行新的过程

CALL ROWPERROW();

在这个例子中,你可以随意更改“INSERT INTO”行,我只是从你的请求示例中复制过来。

请仔细注意,“INSERT INTO”行与问题中的行相同。根据此答案的评论,您需要确保您的查询在运行的任何SQL版本中语法正确。

在ID字段递增且从1开始的简单情况下,示例中的行可能会变为:

INSERT INTO table_B(ID, VAL) VALUES(ID, VAL) FROM table_A WHERE ID=i;

用以下内容替换“SELECT COUNT”行

SET n=10;

只会让您测试table_A表中前10条记录的查询。

最后一件事,这个过程很容易嵌套在不同的表之间,并且是我能够在一个表格上执行一个进程的唯一方法,该表从父表的每一行动态插入不同数量的记录到新表格中。

如果您需要更快的运行速度,那么可以尝试使其基于设置,否则这样就可以了。您还可以以游标形式重写上述内容,但这可能不会提高性能。例如:

DROP PROCEDURE IF EXISTS cursor_ROWPERROW;
DELIMITER ;;

CREATE PROCEDURE cursor_ROWPERROW()
BEGIN
  DECLARE cursor_ID INT;
  DECLARE cursor_VAL VARCHAR;
  DECLARE done INT DEFAULT FALSE;
  DECLARE cursor_i CURSOR FOR SELECT ID,VAL FROM table_A;
  DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = TRUE;
  OPEN cursor_i;
  read_loop: LOOP
    FETCH cursor_i INTO cursor_ID, cursor_VAL;
    IF done THEN
      LEAVE read_loop;
    END IF;
    INSERT INTO table_B(ID, VAL) VALUES(cursor_ID, cursor_VAL);
  END LOOP;
  CLOSE cursor_i;
END;
;;

记得将要使用的变量声明为与查询表中的变量类型相同。

我的建议是,如果可以,请选择基于集合的查询,只有在必要时才使用简单循环或光标。


2
在table_A中,使用INSERT INTO table_B(ID, VAL) VALUES(ID, VAL) FROM table_A LIMIT i,1;会出现语法错误。 - Jonathan
1
在 cursor_i 声明之后似乎缺少“DECLARE done INT DEFAULT FALSE;”。 - ErikL
1
当done属性被设置为true时,我应该将其放在哪里?还是它是由MySQL游标自动使用的保留字? - IgniteCoders
1
INSERT INTO调用在SQL 5.5中会抛出语法错误,正确的调用方式是INSERT INTO table_B(ID, VAL) SELECT (ID, VAL) FROM table_A WHERE ID=i; - Ambar
这将需要一生的时间,因此尝试通过更改限制和增量来增加批处理大小为1000:LIMIT i,1000;set i = i + 1000; - Alan Deep
显示剩余4条评论

26

你应该真正使用基于集合的解决方案,其中涉及两个查询(基本插入):

INSERT INTO TableB (Id2Column, Column33, Column44)
SELECT id, column1, column2 FROM TableA

UPDATE TableA SET column1 = column2 * column3

关于您的变换:

INSERT INTO TableB (Id2Column, Column33, Column44)
SELECT 
    id, 
    column1 * column4 * 100, 
    (column2 / column12) 
FROM TableA

UPDATE TableA SET column1 = column2 * column3

如果您的转换比这个更复杂,并涉及多个表格,请发布另一个问题并提供详细信息。


+1 for the example. 虽然我怀疑这在我的情况下不适用,因为之后的更新与插入相关,特别是选择的行导致了哪个具体的插入。这就是为什么我建议使用循环,这样我可以为每一行单独执行选择/插入/更新操作。 - Raffael
2
@Raffael1984:编辑您的问题以添加“特定行导致特定插入”的条件,我们可以帮助解决。您真的不想走游标/循环路线-这非常低效。 - Raj More
哦,你知道我很乐意采用基于集合的查询方式!但效率在这里并不是动力,因为我的问题更多是学术兴趣。这个表格足够小,我可以手动完成。我真的很希望有一个循环版本。如果提供更多细节以允许某人用基于集合的方式帮助我,我可能会再次发布该问题。 - Raffael
同意@RajMore的观点,游标/循环效率低下,以下是两个关于游标性能的参考链接:
  1. http://rpbouman.blogspot.tw/2006/09/refactoring-mysql-cursors.html
  2. https://dev59.com/8GbWa4cB1Zd3GeqPU0Xv
- Browny Lin
@RajMore,你能帮我解决我的问题吗? - Nurav

4

游标在这里是一种选择,但通常不被赞成,因为它们经常不能最好地利用查询引擎。考虑调查“基于SET的查询”,看看是否可以在不使用游标的情况下实现您想要做的事情。


我找不到有关基于集合的查询的太多信息,但我猜您的意思是选择进入某种语句。问题是我还需要执行更新操作。 - Raffael

2

我在mysql触发器中使用了Mr Purple的示例,就像这样:

begin
DECLARE n INT DEFAULT 0;
DECLARE i INT DEFAULT 0;
Select COUNT(*) from user where deleted_at is null INTO n;
SET i=0;
WHILE i<n DO 
  INSERT INTO user_notification(notification_id,status,userId)values(new.notification_id,1,(Select userId FROM user LIMIT i,1)) ;
  SET i = i + 1;
END WHILE;
end

2
请问您能否添加一些关于您的解决方案如何工作的信息? - TheTanic

-33
    Use this:

    $stmt = $user->runQuery("SELECT * FROM tbl WHERE ID=:id");
    $stmt->bindparam(":id",$id);
    $stmt->execute();

        $stmt->bindColumn("a_b",$xx);
        $stmt->bindColumn("c_d",$yy);


    while($rows = $stmt->fetch(PDO::FETCH_BOUND))
    {
        //---insert into new tble
    }   

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接