¿Cómo puedo recorrer todas las filas de una tabla? (MySQL)

89

Tengo una tabla A y hay una ID de clave principal.

Ahora quiero pasar por todas las filas en A.

Encontré algo como 'para cada registro en A', pero parece que no es así como se hace en MySQL.

La cosa es que para cada fila quiero tomar un campo y transformarlo, insertarlo en otra tabla y luego actualizar algunos de los campos de la fila. Puedo poner la parte seleccionada y la inserción en una declaración, pero no sé cómo obtener la actualización allí también. Entonces quiero hacer un bucle. Y para practicar, no quiero usar nada más que MySQL.

editar

Agradecería un ejemplo.

Y una solución que no necesita ser puesta en un procedimiento.

editar 2

bien, piensa en este escenario:

Tabla A y B, cada una con los campos ID y VAL.

Ahora este es el pseudo código de lo que quiero hacer:

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

básicamente copiando el contenido de A en B usando un bucle.

(este es solo un ejemplo simplificado, por supuesto que no usaría un bucle para esto)}.

Raffael
fuente
Hola Rafeel, para mí está dando un error como: My SQL> for (cada fila en wp_mobiune_ecommune_user como x) {insertar en wp_mobiune_ecommune_user (gender_new) valores (x [gender])}; RROR 1064 (42000): Tiene un error en su sintaxis SQL; consulte el manual que corresponde a la versión de su servidor MySQL para conocer la sintaxis correcta para usar near 'para (cada fila en wp_mobiune_ecommune_user como x) {insertar en wp_mobiune_ecommune' en la línea 1
dinesh kandpal

Respuestas:

135

Dado que la sugerencia de un bucle implica la solicitud de una solución de tipo de procedimiento. Aquí esta el mio.

Cualquier consulta que funcione en un solo registro tomado de una tabla se puede incluir en un procedimiento para que se ejecute en cada fila de una tabla de la siguiente manera:

DROP PROCEDURE IF EXISTS ROWPERROW;
DELIMITER ;;

Luego, aquí está el procedimiento según su ejemplo (table_A y table_B se usan para mayor claridad)

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

Entonces no olvide restablecer el delimitador

DELIMITER ;

Y ejecuta el nuevo procedimiento

CALL ROWPERROW();

Puede hacer lo que quiera en la línea "INSERT INTO" que simplemente copié de su solicitud de ejemplo.

Tenga en cuenta CUIDADOSAMENTE que la línea "INSERT INTO" utilizada aquí refleja la línea de la pregunta. Según los comentarios a esta respuesta, debe asegurarse de que su consulta sea sintácticamente correcta para cualquier versión de SQL que esté ejecutando.

En el caso simple en el que su campo de ID se incrementa y comienza en 1, la línea del ejemplo podría convertirse en:

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

Reemplazando la línea "SELECT COUNT" por

SET n=10;

Le permitirá probar su consulta en los primeros 10 registros en table_A solamente.

Una última cosa. Este proceso también es muy fácil de anidar en diferentes tablas y era la única forma en que podía llevar a cabo un proceso en una tabla que insertaba dinámicamente diferentes números de registros en una nueva tabla de cada fila de una tabla principal.

Si necesita que se ejecute más rápido, intente configurarlo, si no, entonces está bien. También puede reescribir lo anterior en forma de cursor, pero es posible que no mejore el rendimiento. p.ej:

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

Recuerde declarar las variables que utilizará como del mismo tipo que las de las tablas consultadas.

Mi consejo es ir con consultas basadas en conjuntos cuando pueda, y solo use bucles o cursores simples si es necesario.

Señor púrpura
fuente
2
INSERT INTO table_B (ID, VAL) VALUES (ID, VAL) FROM table_A LIMIT i, 1; da un error de sintaxis.
Jonathan
1
Parece que falta 'DECLARE done INT DEFAULT FALSE;' después de la declaración cursor_i
ErikL
1
¿Dónde se establece la propiedad done en verdadera, debería colocarla o es una palabra reservada que el cursor de mysql usa automáticamente?
IgniteCoders
1
La llamada INSERT INTO arroja un error de sintaxis a partir de SQL 5.5, la llamada correcta es INSERT INTO table_B (ID, VAL) SELECT (ID, VAL) FROM table_A WHERE ID = i;
Ambar
Esto llevará toda la vida, así que intente aumentar el tamaño del lote a 1000 cambiando el límite y el incremento: LIMIT i,1000;y set i = i + 1000;
Alan Deep
16

Realmente debería usar una solución basada en conjuntos que involucre dos consultas (inserción básica):

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

UPDATE TableA SET column1 = column2 * column3

Y para tu transformación:

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

UPDATE TableA SET column1 = column2 * column3

Ahora, si su transformación es más complicada que eso e involucró varias tablas, publique otra pregunta con los detalles.

Raj Más
fuente
+1 para el ejemplo. aunque dudo que esto sea aplicable en mi sitio porque la actualización posterior está relacionada con la inserción y especialmente con las filas seleccionadas que causan qué inserción específica. Es por eso que sugerí un bucle, para poder realizar la selección / inserción / actualización para cada fila individualmente.
Raffael
2
@ Raffael1984: edite su pregunta para agregar las condiciones para "filas específicas que causan inserciones específicas" y podemos ayudar con eso. Realmente no desea ir por la ruta del cursor / bucle; es extremadamente ineficiente.
Raj More
bueno ... sabes que estaría más que feliz de seguir el camino de las consultas basadas en conjuntos. pero la eficiencia no es una motivación aquí ya que mi pregunta es más de interés académico. la mesa es lo suficientemente pequeña como para hacerlo a mano. Realmente agradecería una versión en bucle. Podría publicar ese problema nuevamente proporcionando más detalles para permitir que alguien me ayude con el estilo basado en conjuntos.
Raffael
de acuerdo con @RajMore, el cursor / bucle es ineficaz, a continuación se muestran 2 enlaces sobre el rendimiento del cursor para referencias: 1. rpbouman.blogspot.tw/2006/09/refactoring-mysql-cursors.html 2. stackoverflow.com/questions/11549567/ …
Browny Lin
@RajMore ¿Puedes ayudarme en mi pregunta?
Nurav
4

Los CURSORES son una opción aquí, pero generalmente están mal vistos ya que a menudo no hacen un mejor uso del motor de consultas. Considere investigar 'Consultas basadas en SET' para ver si puede lograr lo que desea hacer sin usar un CURSOR.

Ron Weston
fuente
No puedo encontrar mucha información sobre consultas basadas en conjuntos. pero supongo que te refieres a una especie de declaración seleccionada. el problema es que también necesito que se ejecute una actualización.
Raffael
0

El ejemplo de Mr Purple que usé en mysql trigger así,

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
fuente
2
¿Puede agregar alguna información sobre cómo funciona su solución?
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
fuente