MySQL -> Passa attraverso una tabella, eseguendo una procedura memorizzata su ogni voce


9

Ho un database con "libri" (racconti per bambini) e sarebbe estremamente istruttivo avere conteggi di ogni parola nei libri.

Ho capito come ottenere il conteggio delle parole per ogni parola usando:

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

Che funziona meravigliosamente per contare le parole. MA mi richiede di sfogliare ogni libro, di ottenere ogni parola e di eseguirlo attraverso quella funzione (l'ho salvato come Stored Procedure.)

Ho una tabella che contiene ogni parola, senza duplicati.

La mia domanda: c'è un modo in cui posso fare una specie di ciclo "per ogni" sulla tabella di parole usando la mia procedura memorizzata?

vale a dire. passare la procedura memorizzata un ID libro e una parola e registrare il risultato. Facendo OGNI parola, per OGNI libro. In questo modo mi risparmia MOLTO tempo manuale ... È qualcosa che dovrei fare anche dal lato DB? Dovrei provare con PHP invece?

Sinceramente ogni input è molto apprezzato!


1
È possibile creare una tabella di (tutte) le parole analizzando i libri. Quindi diventerebbe uno di quelli che uniscono libri alle parole. Non sono necessari loop lì.
jkavalik,

Alcune attività vengono eseguite meglio in un vero linguaggio di programmazione, non in SQL. In PHP potrebbe essere qualcosa di simile count(explode(' ', $pageText))+1. O qualcosa di più complesso per gestire più spazi tra le parole, forse coinvolgendopreg_replace('/\s+/', ' ', $pageText)
Rick James,

Per Perl, potrebbe essere breve 1+split(/\s+/, $pageText). Il 1 è perché il conteggio è di spazi, non di parole.
Rick James,

Risposte:


14

Creare una seconda procedura che utilizza due cursori nidificati.

I cursori nelle procedure memorizzate consentono di eseguire operazioni molto diverse da quelle di tipo SQL: eseguire l'iterazione di un set di risultati una riga alla volta, inserendo i valori della colonna selezionata in variabili e eseguendo le operazioni con essi.

Sono facilmente utilizzati in modo improprio, poiché SQL, essendo dichiarativo piuttosto che procedurale, di solito non dovrebbe necessitare di operazioni "per ogni" tipo, ma in questo caso sembra un'applicazione valida.

Una volta capito, i cursori sono facili, ma richiedono un approccio strutturato nel loro codice di supporto che non è sempre intuitivo.

Recentemente ho fornito un codice "boilerplate" abbastanza standard per lavorare con un cursore per chiamare una procedura memorizzata in una risposta su Stack Overflow , e prenderò molto in prestito da quella risposta, di seguito.


L'uso di un cursore richiede un codice standard per il boilerplate per circondarlo.

I SELECTvalori che si desidera passare, da qualunque punto li si ottengano (che potrebbero essere una tabella temporanea, una tabella di base o una vista e che possono includere chiamate a funzioni memorizzate) e quindi chiamare la propria procedura esistente con tali valori.

Ecco un esempio sintatticamente valido del codice necessario, con commenti per spiegare cosa sta facendo ciascun componente.

In questo esempio vengono utilizzate 2 colonne per passare 2 valori alla procedura chiamata.

Si noti che gli eventi che si verificano qui sono in un ordine specifico per un motivo. Le variabili devono essere dichiarate per prime, i cursori devono essere dichiarati prima dei loro gestori continui e i loop devono seguire tutte queste cose.

Non puoi fare le cose in modo disordinato, quindi quando annidi un cursore all'interno di un altro, devi resettare l'ambito della procedura annidando il codice aggiuntivo all'interno di BEGIN... ENDblocchi all'interno del corpo della procedura; per esempio, se avessi bisogno di un secondo cursore all'interno del loop, lo dichiareresti semplicemente all'interno del loop, all'interno di un altro BEGIN... ENDblocco.

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 ;

Risposta fantastica, estremamente istruttiva! Non l'ho ancora abbattuto, ma con le risorse fornite sono sicuro di riuscire a far funzionare i cursori! Grazie!
Michael MacDonald,

è stato fantastico! l'uso di repeat / while ha provocato il doppio avvio del mio proc per l'ultimo record, richiedendo quindi ulteriori controlli, ma questo risolve il problema.
Nick M,

chiudi cursore1; Manca OPEN - CLOSE stanno andando insieme per i cursori
Miss Felicia A Kovacs

2
I cursori di @MissFeliciaAKovacs possono esistere solo nell'ambito di un blocco BEGIN/ ENDe sono implicitamente chiusi quando cadono dal campo di applicazione ... quindi non è strettamente necessario chiudere i cursori. In pratica, lo considero non necessario e non lo includo, ma per completezza ho aggiunto la CLOSEdichiarazione alla risposta.
Michael - sqlbot,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.