Comment puis-je parcourir toutes les lignes d'une table? (MySQL)

89

J'ai une table A et il y a un ID de clé primaire.

Maintenant, je veux parcourir toutes les lignes de A.

J'ai trouvé quelque chose comme «pour chaque enregistrement dans A», mais cela ne semble pas être la façon dont vous le faites dans MySQL.

La chose est que pour chaque ligne, je veux prendre un champ et le transformer, l'insérer dans une autre table, puis mettre à jour certains des champs de la ligne. Je peux mettre la partie de sélection et l'insert dans une seule instruction, mais je ne sais pas comment y mettre la mise à jour également. Alors je veux faire une boucle. Et pour la pratique, je ne veux pas utiliser autre chose que MySQL.

Éditer

J'apprécierais un exemple.

Et une solution qui n'a pas besoin d'être mise en procédure.

modifier 2

ok pensez à ce scénario:

Tableau A et B, chacun avec les champs ID et VAL.

Voici maintenant le pseudo code de ce que je veux faire:

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

copier essentiellement le contenu de A dans B en utilisant une boucle.

(ceci est juste un exemple simplifié, bien sûr vous n'utiliserez pas de boucle pour cela.)}

Raffael
la source
Salut Rafeel pour moi son erreur de donner comme: Mon SQL> pour (chaque ligne dans wp_mobiune_ecommune_user comme x) {insérer dans wp_mobiune_ecommune_user (gender_new) valeurs (x [gender])}; RROR 1064 (42000): Vous avez une erreur dans votre syntaxe SQL; vérifiez le manuel qui correspond à votre version de serveur MySQL pour la bonne syntaxe à utiliser near 'for (chaque ligne dans wp_mobiune_ecommune_user comme x) {insert into wp_mobiune_ecommune' à la ligne 1
dinesh kandpal

Réponses:

135

Puisque la suggestion d'une boucle implique la demande d'une solution de type procédure. Voici le mien.

Toute requête qui fonctionne sur n'importe quel enregistrement unique extrait d'une table peut être encapsulée dans une procédure pour la faire exécuter à travers chaque ligne d'une table comme ceci:

DROP PROCEDURE IF EXISTS ROWPERROW;
DELIMITER ;;

Ensuite, voici la procédure selon votre exemple (table_A et table_B utilisées pour plus de clarté)

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

Alors n'oubliez pas de réinitialiser le délimiteur

DELIMITER ;

Et lancez la nouvelle procédure

CALL ROWPERROW();

Vous pouvez faire ce que vous voulez à la ligne "INSERT INTO" que j'ai simplement copiée à partir de votre demande d'exemple.

Notez ATTENTIVEMENT que la ligne "INSERT INTO" utilisée ici reflète la ligne de la question. Selon les commentaires de cette réponse, vous devez vous assurer que votre requête est syntaxiquement correcte pour quelle que soit la version de SQL que vous exécutez.

Dans le cas simple où votre champ ID est incrémenté et commence à 1, la ligne de l'exemple pourrait devenir:

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

Remplacement de la ligne "SELECT COUNT" par

SET n=10;

Vous permettra de tester votre requête sur les 10 premiers enregistrements de table_A uniquement.

Une dernière chose. Ce processus est également très facile à imbriquer dans différentes tables et était le seul moyen pour moi d'effectuer un processus sur une table qui insérait dynamiquement différents nombres d'enregistrements dans une nouvelle table à partir de chaque ligne d'une table parent.

Si vous en avez besoin pour fonctionner plus rapidement, essayez de le définir en fonction, sinon c'est très bien. Vous pouvez également réécrire ce qui précède sous forme de curseur, mais cela n'améliorera peut-être pas les performances. par exemple:

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

N'oubliez pas de déclarer les variables que vous utiliserez du même type que celles des tables interrogées.

Mon conseil est d'aller avec des requêtes basées sur des ensembles lorsque vous le pouvez, et d'utiliser uniquement des boucles simples ou des curseurs si vous le devez.

Monsieur Violet
la source
2
INSERT INTO table_B (ID, VAL) VALUES (ID, VAL) FROM table_A LIMIT i, 1; donne une erreur de syntaxe.
Jonathan
1
Semble manquer «DECLARE done INT DEFAULT FALSE;» après la déclaration du
curseur_i
1
Où la propriété done est-elle définie sur true, je dois la placer ou est-ce qu'un mot réservé est utilisé automatiquement par le curseur mysql?
IgniteCoders
1
L'appel INSERT INTO génère une erreur de syntaxe à partir de SQL 5.5, l'appel correct est INSERT INTO table_B (ID, VAL) SELECT (ID, VAL) FROM table_A WHERE ID = i;
Ambar
Cela prendra toute une vie, alors essayez d'augmenter la taille du lot à 1000 en modifiant la limite et l'incrémentation: LIMIT i,1000;et set i = i + 1000;
Alan Deep
16

Vous devriez vraiment utiliser une solution basée sur un ensemble impliquant deux requêtes (insertion de base):

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

UPDATE TableA SET column1 = column2 * column3

Et pour votre transformation:

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

UPDATE TableA SET column1 = column2 * column3

Maintenant, si votre transformation est plus compliquée que cela et impliquait plusieurs tables, postez une autre question avec les détails.

Raj Plus
la source
+1 pour l'exemple. bien que je doute que cela soit applicable dans mon situ b / c, la mise à jour ultérieure est liée à l'insert et surtout aux lignes sélectionnées qui provoquent quel insert spécifique. C'est pourquoi j'ai suggéré une boucle, afin que je puisse effectuer une sélection / insertion / mise à jour pour chaque ligne individuellement.
Raffael
2
@ Raffael1984: modifiez votre question pour ajouter les conditions pour "des lignes spécifiques provoquant des insertions spécifiques" et nous pouvons vous aider. Vous ne voulez vraiment pas suivre la route curseur / boucle - c'est extrêmement inefficace.
Raj More
eh bien ... vous savez que je serais plus qu'heureux d'utiliser la méthode de requête basée sur les ensembles! mais l'efficience n'est pas une motivation ici car ma question est plus d'intérêt académique. la table est assez petite je pourrais le faire à la main. J'apprécierais vraiment une version en boucle. Je pourrais poster à nouveau ce problème en fournissant plus de détails pour permettre à quelqu'un de m'aider avec le style basé sur les ensembles.
Raffael
d'accord avec @RajMore, le curseur / boucle est inefficace, voici 2 liens sur les performances du curseur pour les références: 1. rpbouman.blogspot.tw/2006/09/refactoring-mysql-cursors.html 2. stackoverflow.com/questions/11549567/ …
Browny Lin
@RajMore Pouvez-vous m'aider dans ma question
Nurav
4

Les curseurs sont une option ici, mais ils sont généralement mal vus car ils n'utilisent souvent pas au mieux le moteur de requête. Envisagez d'étudier les «requêtes basées sur SET» pour voir si vous pouvez réaliser ce que vous voulez faire sans utiliser de CURSOR.

Ron Weston
la source
Je ne trouve pas beaucoup d'informations sur les requêtes basées sur des ensembles. mais je suppose que vous voulez dire une sorte de déclaration. le problème est que j'ai également besoin d'exécuter une mise à jour.
Raffael
0

L'exemple de Mr Purple que j'ai utilisé dans le trigger mysql comme ça

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
la source
2
Pouvez-vous s'il vous plaît ajouter quelques informations sur le fonctionnement de votre solution?
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
la source