MySQL -> Recorrer una tabla, ejecutando un procedimiento almacenado en cada entrada

9

Tengo una base de datos con 'libros' (cuentos para niños) y sería extremadamente informativo contar los recuentos de cada palabra en los libros.

Descubrí cómo obtener el recuento de palabras para cada palabra usando:

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

Lo que funciona maravillosamente para contar las palabras. PERO requiere que revise cada libro, saque cada palabra y la ejecute a través de esa función (lo tengo guardado como un procedimiento almacenado).

Tengo una tabla que contiene cada palabra, sin duplicados.

Mi pregunta: ¿hay alguna manera de hacer algún tipo de "para cada" bucle en la tabla de palabras usando mi procedimiento almacenado?

es decir. pasar el procedimiento almacenado una identificación del libro y una palabra y registrar el resultado. Haciendo CADA palabra, por CADA libro. Por lo tanto, me ahorra MUCHO tiempo manual ... ¿Es esto algo que incluso debería estar haciendo desde el lado de la base de datos? ¿Debería intentarlo con PHP?

Sinceramente, cualquier aportación es muy apreciada.

Michael MacDonald
fuente
1
Puede crear una tabla de (todas) palabras analizando los libros. Entonces se convertiría en un selecto libro de unión de palabras. No se necesitan bucles allí.
jkavalik
Algunas tareas se realizan mejor en un lenguaje de programación real, no en SQL. En PHP podría ser algo así count(explode(' ', $pageText))+1. O algo más complejo para manejar múltiples espacios entre palabras, quizás involucrandopreg_replace('/\s+/', ' ', $pageText)
Rick James
Para Perl, podría ser tan corto como 1+split(/\s+/, $pageText). El 1 se debe a que el conteo es de espacios, no de palabras.
Rick James

Respuestas:

14

Cree un segundo procedimiento que use dos cursores anidados.

Los cursores en los procedimientos almacenados le permiten hacer cosas muy distintas a las de SQL: iterar a través de un conjunto de resultados una fila a la vez, poner los valores de columna seleccionados en variables y hacer cosas con ellos.

Se usan mal fácilmente, ya que SQL, al ser declarativo en lugar de procesal, generalmente no debería necesitar operaciones de tipo "para cada", pero en este caso, parece una aplicación válida.

Una vez que los domine, los cursores son fáciles, pero requieren un enfoque estructurado en su código de soporte que no siempre es intuitivo.

Recientemente proporcioné un código "estándar" bastante estándar para trabajar con un cursor para llamar a un procedimiento almacenado en una respuesta en Desbordamiento de pila , y me prestaré mucho de esa respuesta, a continuación.


El uso de un cursor requiere un código estándar estándar para rodearlo.

Usted SELECTlos valores que desea pasar, desde donde los obtenga (que podría ser una tabla temporal, una tabla base o una vista, y puede incluir llamadas a funciones almacenadas) y luego llame a su procedimiento existente con esos valores.

Aquí hay un ejemplo sintácticamente válido del código necesario, con comentarios para explicar lo que está haciendo cada componente.

Este ejemplo usa 2 columnas para pasar 2 valores al procedimiento llamado.

Tenga en cuenta que hay eventos que suceden aquí están en un orden específico por una razón. Las variables deben declararse primero, los cursores deben declararse antes que sus manejadores continuos, y los bucles deben seguir todas esas cosas.

No puede hacer las cosas fuera de orden, por lo que cuando anida un cursor dentro de otro, debe restablecer el alcance del procedimiento anidando código adicional dentro de BEGIN... ENDbloques dentro del cuerpo del procedimiento; por ejemplo, si necesitaras un segundo cursor dentro del ciclo, lo declararías dentro del ciclo, dentro de otro BEGIN... ENDbloque.

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 ;
Michael - sqlbot
fuente
¡Fantástica respuesta, extremadamente informativa! Todavía no lo he entendido, pero con los recursos proporcionados, ¡estoy seguro de que puedo hacer que los cursores funcionen! ¡Gracias!
Michael MacDonald
esto fue genial! El uso de repetir / while hizo que mi proceso se disparara dos veces para el último registro, por lo que se requieren verificaciones adicionales, pero esto resuelve ese problema.
Nick M
cerrar cursor1; falta ABRIR - CERRAR van juntos por los cursores
Miss Felicia A Kovacs
2
Los cursores @MissFeliciaAKovacs solo pueden existir dentro del alcance de un bloque BEGIN/ END, y se cierran implícitamente cuando quedan fuera del alcance ... por lo que no es estrictamente necesario cerrar los cursores. Como práctica, lo considero innecesario y no lo incluyo, pero para completar, he agregado la CLOSEdeclaración a la respuesta.
Michael - sqlbot