MySQL -> Durchlaufen Sie eine Tabelle und führen Sie für jeden Eintrag eine gespeicherte Prozedur aus

8

Ich habe eine Datenbank mit "Büchern" (Kurzgeschichten für Kinder) und es wäre äußerst informativ, die Anzahl der Wörter für jedes Wort in den Büchern zu haben.

Ich fand heraus, wie man die Wortzahl für jedes Wort erhält, indem man:

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

Was wunderbar funktioniert, um die Wörter zu zählen. ABER ich muss jedes Buch durchgehen, jedes Wort herausholen und es durch diese Funktion ausführen (ich habe es als gespeicherte Prozedur gespeichert).

Ich habe eine Tabelle, die jedes Wort enthält, ohne Duplikate.

Meine Frage: Gibt es eine Möglichkeit, mit meiner gespeicherten Prozedur eine Art "für jede" Schleife in der Words-Tabelle auszuführen?

dh. Übergeben Sie der gespeicherten Prozedur eine Buch-ID und ein Wort und zeichnen Sie das Ergebnis auf. JEDES Wort für JEDES Buch tun. Das spart mir viel manuelle Zeit ... Sollte ich das überhaupt von der DB-Seite aus tun? Sollte ich es stattdessen mit PHP versuchen?

Ehrlich gesagt wird jede Eingabe sehr geschätzt!

Michael MacDonald
quelle
1
Sie können eine Tabelle mit (allen) Wörtern erstellen, indem Sie die Bücher analysieren. Dann würde es eine Auswahl werden, die Bücher mit Wörtern verbindet. Dort werden keine Schleifen benötigt.
Jkavalik
Einige Aufgaben werden besser in einer echten Programmiersprache erledigt, nicht in SQL. In PHP könnte es so etwas sein count(explode(' ', $pageText))+1. Oder etwas komplexeres, um mehrere Leerzeichen zwischen Wörtern zu behandeln, vielleicht mitpreg_replace('/\s+/', ' ', $pageText)
Rick James
Für Perl könnte es so kurz sein wie 1+split(/\s+/, $pageText). Die 1 ist, weil die Anzahl aus Leerzeichen und nicht aus Wörtern besteht.
Rick James

Antworten:

14

Erstellen Sie eine zweite Prozedur, die zwei verschachtelte Cursor verwendet.

Mit Cursorn in gespeicherten Prozeduren können Sie eine sehr nicht SQL-ähnliche Aufgabe ausführen : eine Ergebnismenge zeilenweise durchlaufen, die ausgewählten Spaltenwerte in Variablen einfügen und damit arbeiten.

Sie können leicht missbraucht werden, da SQL, das eher deklarativ als prozedural ist, normalerweise nicht "für jede" Operation vom Typ "benötigt" sollte, aber in diesem Fall scheint es eine gültige Anwendung zu sein.

Sobald Sie den Dreh raus haben, sind Cursor einfach, aber sie erfordern einen strukturierten Ansatz in ihrem unterstützenden Code, der nicht immer intuitiv ist.

Ich habe kürzlich einen ziemlich standardmäßigen "Boilerplate" -Code für die Arbeit mit einem Cursor bereitgestellt, um eine gespeicherte Prozedur in einer Antwort auf Stack Overflow aufzurufen , und ich werde diese Antwort unten sehr stark ausleihen.


Die Verwendung eines Cursors erfordert einen Standard-Boilerplate-Code, um ihn zu umgeben.

Sie geben SELECTdie Werte an, die Sie übergeben möchten, von wo immer Sie sie erhalten (dies kann eine temporäre Tabelle, eine Basistabelle oder eine Ansicht sein und Aufrufe an gespeicherte Funktionen enthalten) und rufen dann Ihre existinf-Prozedur mit diesen Werten auf.

Hier ist ein syntaktisch gültiges Beispiel für den erforderlichen Code mit Kommentaren, um zu erklären, was jede Komponente tut.

In diesem Beispiel werden 2 Spalten verwendet, um 2 Werte an die aufgerufene Prozedur zu übergeben.

Beachten Sie, dass Ereignisse, die hier auftreten, aus einem bestimmten Grund in einer bestimmten Reihenfolge aufgeführt sind. Variablen müssen zuerst deklariert werden, Cursor müssen deklariert werden, bevor sie fortfahren, und Schleifen müssen all diesen Dingen folgen.

Sie können Dinge nicht in unregelmäßiger Reihenfolge ausführen. Wenn Sie also einen Cursor in einen anderen verschachteln, müssen Sie den Prozedurbereich zurücksetzen, indem Sie zusätzlichen Code in BEGIN... ENDBlöcke innerhalb des Prozedurkörpers verschachteln . Wenn Sie beispielsweise einen zweiten Cursor innerhalb der Schleife benötigen, deklarieren Sie ihn einfach innerhalb der Schleife, innerhalb eines anderen BEGIN... ENDBlocks.

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
quelle
Fantastische Antwort, äußerst informativ! Ich habe es noch nicht ganz verstanden, aber mit den bereitgestellten Ressourcen bin ich sicher, dass ich Cursor zum Laufen bringen kann! Vielen Dank!
Michael MacDonald
Das war großartig! Durch die Verwendung von repeat / while wurde mein Proc für den letzten Datensatz zweimal ausgelöst, sodass zusätzliche Überprüfungen erforderlich waren. Dies löst jedoch das Problem.
Nick M
Cursor schließen1; fehlt OPEN - CLOSE gehen zusammen für Cursor
Miss Felicia A Kovacs
2
@ MissFeliciaAKovacs-Cursor können nur im Bereich eines BEGIN/ END-Blocks vorhanden sein und werden implizit geschlossen, wenn sie außerhalb des Bereichs liegen. Daher ist das Schließen von Cursorn nicht unbedingt erforderlich. Aus praktischen Gründen halte ich es für unnötig und füge es nicht hinzu, aber der Vollständigkeit halber habe ich die CLOSEErklärung der Antwort hinzugefügt .
Michael - sqlbot