Como posso percorrer todas as linhas de uma tabela? (MySQL)

88

Eu tenho uma tabela A e há um ID de chave primária.

Agora quero percorrer todas as linhas em A.

Eu encontrei algo como 'para cada registro em A', mas parece não ser assim que você faz no MySQL.

A questão é que para cada linha eu quero pegar um campo e transformá-lo, inseri-lo em outra tabela e então atualizar alguns dos campos da linha. Posso colocar a parte select e a insert em uma instrução, mas não sei como fazer a atualização lá também. Então, eu quero fazer um loop. E para praticar, não quero usar nada além do MySQL.

editar

Eu apreciaria um exemplo.

E uma solução que não precisa ser colocada em procedimento.

editar 2

ok pense neste cenário:

Tabela A e B, cada um com os campos ID e VAL.

Este é o pseudocódigo para o que desejo fazer:

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

basicamente copiando o conteúdo de A para B usando um loop.

(este é apenas um exemplo simplificado, é claro que você não usaria um loop para isso.)}

Raffael
fonte
Oi Rafeel, para mim está dando erro como: My SQL> for (cada linha em wp_mobiune_ecommune_user como x) {inserir em wp_mobiune_ecommune_user (gender_new) values ​​(x [gender])}; RROR 1064 (42000): Você tem um erro na sintaxe SQL; verifique o manual que corresponde à versão do seu servidor MySQL para a sintaxe correta a ser usada próximo a 'for (cada linha em wp_mobiune_ecommune_user como x) {inserir em wp_mobiune_ecommune' na linha 1
dinesh kandpal

Respostas:

134

Já a sugestão de um loop implica na solicitação de uma solução do tipo procedimento. Aqui é minha.

Qualquer consulta que funcione em qualquer registro único obtido de uma tabela pode ser envolvida em um procedimento para fazê-la funcionar em cada linha de uma tabela, assim:

DROP PROCEDURE IF EXISTS ROWPERROW;
DELIMITER ;;

Então aqui está o procedimento de acordo com o seu exemplo (table_A e table_B usadas para maior clareza)

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;
;;

Então não se esqueça de redefinir o delimitador

DELIMITER ;

E execute o novo procedimento

CALL ROWPERROW();

Você pode fazer o que quiser na linha "INSERT INTO" que eu simplesmente copiei de sua solicitação de exemplo.

Observe CUIDADOSAMENTE que a linha "INSERT INTO" usada aqui reflete a linha da pergunta. De acordo com os comentários a esta resposta, você precisa garantir que sua consulta esteja sintaticamente correta para qualquer versão do SQL que você esteja executando.

No caso simples em que seu campo de ID é incrementado e começa em 1, a linha no exemplo pode se tornar:

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

Substituindo a linha "SELECT COUNT" por

SET n=10;

Permitirá que você teste sua consulta nos primeiros 10 registros da tabela_A apenas.

Uma última coisa. Esse processo também é muito fácil de aninhar em tabelas diferentes e foi a única maneira de realizar um processo em uma tabela que inseriu dinamicamente diferentes números de registros em uma nova tabela a partir de cada linha de uma tabela pai.

Se você precisa que ele seja executado mais rápido, tente torná-lo baseado em conjuntos; Você também pode reescrever o texto acima na forma de cursor, mas pode não melhorar o desempenho. por exemplo:

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;
;;

Lembre-se de declarar as variáveis ​​que você usará como sendo do mesmo tipo das tabelas consultadas.

Meu conselho é ir com consultas baseadas em conjunto quando você puder, e usar apenas loops ou cursores simples se for necessário.

Sr. Roxo
fonte
2
INSERT INTO table_B (ID, VAL) VALORES (ID, VAL) FROM table_A LIMIT i, 1; dá um erro de sintaxe.
Jonathan
1
Parece faltar 'DECLARE done INT DEFAULT FALSE;' após a declaração cursor_i
ErikL
1
Onde a propriedade done definida como true, devo colocá-la ou é uma palavra reservada que é usada automaticamente pelo cursor mysql?
IgniteCoders
1
A chamada INSERT INTO lança um erro de sintaxe como do SQL 5.5, a chamada correta é INSERT INTO tabela_B (ID, VAL) SELECT (ID, VAL) FROM tabela_A WHERE ID = i;
Ambar
Isso levará uma vida inteira, então tente aumentar o tamanho do lote para 1000 alterando o limite e o incremento: LIMIT i,1000;e set i = i + 1000;
Alan Deep,
16

Você realmente deve usar uma solução baseada em conjunto envolvendo duas consultas (inserção básica):

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

UPDATE TableA SET column1 = column2 * column3

E para sua transformação:

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

UPDATE TableA SET column1 = column2 * column3

Agora, se sua transformação é mais complicada do que isso e envolve várias tabelas, poste outra pergunta com os detalhes.

Raj More
fonte
1 para o exemplo. embora eu duvide que isso seja aplicável no meu local b / c a atualização posterior está relacionada à inserção e, especialmente, quais linhas selecionadas causam que inserção específica. É por isso que sugeri um loop, para que eu possa selecionar / inserir / atualizar para cada linha individualmente.
Raffael
2
@ Raffael1984: edite sua pergunta para adicionar as condições para "linhas específicas que causam inserções específicas" e podemos ajudar com isso. Você realmente não quer seguir a rota do cursor / loop - é extremamente ineficiente.
Raj More
bem ... você sabe que ficaria mais do que feliz em seguir o método de consulta baseada em conjunto! mas a eficiência não é motivação aqui, pois minha pergunta é mais de interesse acadêmico. a mesa é pequena o suficiente para que eu possa fazer isso manualmente. Eu realmente apreciaria uma versão em loop. Eu posso postar esse problema novamente fornecendo mais detalhes para permitir que alguém me ajude com o estilo baseado em conjuntos.
Raffael
concordo com @RajMore, cursor / loop é ineficiente, abaixo estão 2 links sobre o desempenho do cursor para referências: 1. rpbouman.blogspot.tw/2006/09/refactoring-mysql-cursors.html 2. stackoverflow.com/questions/11549567/ …
Browny Lin
@RajMore Você pode me ajudar na minha pergunta
Nurav
4

CURSORES são uma opção aqui, mas geralmente desaprovados, pois geralmente não fazem o melhor uso do mecanismo de consulta. Considere investigar 'Consultas baseadas em SET' para ver se você pode alcançar o que deseja sem usar um CURSOR.

Ron Weston
fonte
Não consigo encontrar muitas informações sobre consultas baseadas em conjuntos. mas eu acho que você quer dizer um tipo de declaração selecione. o problema é que também preciso executar uma atualização.
Raffael
0

O exemplo do Sr. Purple que usei no gatilho do mysql assim,

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
Erkan RUA
fonte
2
Você pode adicionar algumas informações sobre como sua solução funciona?
TheTanic
-24
    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
    }   
utee
fonte