web-dev-qa-db-fra.com

MySQL -> Boucle dans une table, exécutant une procédure stockée sur chaque entrée

J'ai une base de données avec des "livres" (nouvelles pour les enfants) et il serait extrêmement instructif d'avoir le nombre de mots de chaque mot dans les livres.

J'ai compris comment obtenir le nombre de mots pour chaque mot en utilisant:

SELECT SUM
( 
    ROUND
    ( 
        (LENGTH(pageText) - LENGTH (REPLACE (pageText, "Word", "")))
        /LENGTH("Word")
    )
) FROM pages WHERE bookID = id;

Ce qui fonctionne à merveille pour compter les mots. MAIS cela me demande de parcourir chaque livre, de sortir chaque mot et de l'exécuter via cette fonction (je l'ai enregistré en tant que procédure stockée.)

J'ai un tableau qui contient chaque mot, sans doublons.

Ma question: existe-t-il un moyen de faire une sorte de boucle "pour chaque" sur la table Words en utilisant ma procédure stockée?

c'est à dire. passez la procédure stockée un ID de livre et un mot et enregistrez le résultat. Faire CHAQUE mot, pour CHAQUE livre. Cela me fait gagner BEAUCOUP de temps manuel ... Est-ce quelque chose que je devrais même faire du côté DB? Dois-je essayer avec PHP à la place?

Honnêtement, toute contribution est grandement appréciée!

8

Créez une deuxième procédure qui utilise deux curseurs imbriqués.

Les curseurs dans les procédures stockées vous permettent de faire quelque chose de très peu semblable à SQL: parcourir un jeu de résultats une ligne à la fois, placer les valeurs de colonne sélectionnées dans des variables et faire des choses avec.

Ils sont facilement utilisés à mauvais escient, car SQL, étant déclaratif plutôt que procédural, ne devrait généralement pas avoir besoin d'opérations de type "pour chaque", mais dans ce cas, cela semble être une application valide.

Une fois que vous les maîtrisez, les curseurs sont faciles, mais ils nécessitent une approche structurée dans leur code de prise en charge qui n'est pas toujours intuitive.

J'ai récemment fourni un code "standard" assez standard pour travailler avec un curseur pour appeler une procédure stockée dans une réponse sur Stack Overflow , et j'emprunterai très largement cette réponse ci-dessous.


L'utilisation d'un curseur nécessite un code standard standard pour l'entourer.

Vous SELECT les valeurs que vous souhaitez transmettre, d'où que vous les obteniez (qui peut être une table temporaire, une table de base ou une vue, et peut inclure des appels à des fonctions stockées), puis appelez votre procédure existinf avec ces valeurs.

Voici un exemple syntaxiquement valide du code nécessaire, avec des commentaires pour expliquer ce que fait chaque composant.

Cet exemple utilise 2 colonnes pour passer 2 valeurs à la procédure appelée.

Notez que les événements qui se produisent ici sont dans un ordre spécifique pour une raison. Les variables doivent être déclarées en premier, les curseurs doivent être déclarés avant leurs gestionnaires continus et les boucles doivent suivre toutes ces choses.

Vous ne pouvez pas faire les choses dans le désordre, donc lorsque vous imbriquez un curseur à l'intérieur d'un autre, vous devez réinitialiser la portée de la procédure en imbriquant du code supplémentaire à l'intérieur des blocs BEGIN ... END dans le organe de procédure; par exemple, si vous aviez besoin d'un deuxième curseur à l'intérieur de la boucle, il vous suffit de le déclarer à l'intérieur de la boucle, à l'intérieur d'un autre bloc BEGIN ... END.

DELIMITER $$

DROP PROCEDURE IF EXISTS `my_proc` $$
CREATE PROCEDURE `my_proc`(arg1 INT) -- 1 input argument; you might need more or fewer
BEGIN

-- declare the program variables where we'll hold the values we're sending into the procedure;
-- declare as many of them as there are input arguments to the second procedure,
-- with appropriate data types.

DECLARE val1 INT DEFAULT NULL;
DECLARE val2 INT DEFAULT NULL;

-- we need a boolean variable to tell us when the cursor is out of data

DECLARE done TINYINT DEFAULT FALSE;

-- declare a cursor to select the desired columns from the desired source table1
-- the input argument (which you might or might not need) is used in this example for row selection

DECLARE cursor1 -- cursor1 is an arbitrary label, an identifier for the cursor
 CURSOR FOR
 SELECT t1.c1, 
        t1.c2
   FROM table1 t1
  WHERE c3 = arg1; 

-- this fancy spacing is of course not required; all of this could go on the same line.

-- a cursor that runs out of data throws an exception; we need to catch this.
-- when the NOT FOUND condition fires, "done" -- which defaults to FALSE -- will be set to true,
-- and since this is a CONTINUE handler, execution continues with the next statement.   

DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = TRUE;

-- open the cursor

OPEN cursor1;

my_loop: -- loops have to have an arbitrary label; it's used to leave the loop
LOOP

  -- read the values from the next row that is available in the cursor

  FETCH NEXT FROM cursor1 INTO val1, val2;

  IF done THEN -- this will be true when we are out of rows to read, so we go to the statement after END LOOP.
    LEAVE my_loop; 
  ELSE -- val1 and val2 will be the next values from c1 and c2 in table t1, 
       -- so now we call the procedure with them for this "row"
    CALL the_other_procedure(val1,val2);
    -- maybe do more stuff here
  END IF;
END LOOP;

-- execution continues here when LEAVE my_loop is encountered;
-- you might have more things you want to do here

-- the cursor is implicitly closed when it goes out of scope, or can be explicitly closed if desired

CLOSE cursor1;

END $$

DELIMITER ;
14
Michael - sqlbot