MySQL -> Passa por uma tabela, executando um procedimento armazenado em cada entrada

9

Eu tenho um banco de dados com 'livros' (contos para crianças) e seria extremamente informativo ter contagens de palavras de cada palavra nos livros.

Eu descobri como obter a contagem de palavras para cada palavra usando:

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

O que funciona maravilhosamente para contar as palavras. MAS exige que eu leia cada livro, divulgue cada palavra e execute-a através dessa função (eu a salvei como Procedimento Armazenado).

Eu tenho uma tabela que contém cada palavra, sem duplicatas.

Minha pergunta: existe uma maneira de fazer algum tipo de loop "para cada" na tabela Palavras usando meu procedimento armazenado?

ie passe ao procedimento armazenado um ID e uma palavra do livro e registre o resultado. Fazendo cada palavra, para cada livro. Assim, poupando muito tempo manual ... Isso é algo que eu deveria estar fazendo do lado do banco de dados? Em vez disso, devo tentar com PHP?

Honestamente, qualquer contribuição é muito apreciada!

Michael MacDonald
fonte
11
Você pode criar uma tabela de (todas) palavras analisando os livros. Então se tornaria um seleto juntando livros a palavras. Não são necessários loops lá.
Jkavalik 15/05
Algumas tarefas são melhor executadas em uma linguagem de programação real, não em SQL. No PHP, pode ser algo parecido count(explode(' ', $pageText))+1. Ou algo mais complexo para lidar com vários espaços entre as palavras, talvez envolvendopreg_replace('/\s+/', ' ', $pageText)
Rick James
Para Perl, pode ser tão curto quanto 1+split(/\s+/, $pageText). O 1 é porque a contagem é de espaços, não de palavras.
Rick James

Respostas:

14

Crie um segundo procedimento que use dois cursores aninhados.

Os cursores nos procedimentos armazenados permitem que você faça algo não muito semelhante ao SQL: itere através de um conjunto de resultados uma linha por vez, colocando os valores da coluna selecionada em variáveis ​​e fazendo coisas com eles.

Eles são facilmente mal utilizados, já que o SQL, sendo declarativo e não processual, geralmente não deve precisar "para cada" operações do tipo, mas nesse caso, parece um aplicativo válido.

Quando você pega o jeito, os cursores são fáceis, mas exigem uma abordagem estruturada no código de suporte que nem sempre é intuitiva.

Recentemente, forneci algum código "padrão padrão" para trabalhar com um cursor para chamar um procedimento armazenado em uma resposta no Stack Overflow , e emprestarei muito fortemente dessa resposta, abaixo.


O uso de um cursor requer algum código padrão para cercá-lo.

Você tem SELECTos valores que deseja transmitir, de onde quer que os esteja adquirindo (que pode ser uma tabela temporária, tabela base ou exibição e pode incluir chamadas para funções armazenadas) e, em seguida, chame seu procedimento existente com esses valores.

Aqui está um exemplo sintaticamente válido do código necessário, com comentários para explicar o que cada componente está fazendo.

Este exemplo usa 2 colunas para passar 2 valores para o procedimento chamado.

Observe que existem eventos que acontecem aqui em uma ordem específica por um motivo. As variáveis ​​precisam ser declaradas primeiro, os cursores devem ser declarados antes dos manipuladores de continuar e os loops precisam seguir todas essas coisas.

Você não pode fazer coisas fora de ordem; portanto, quando aninha um cursor dentro de outro, é necessário redefinir o escopo do procedimento, aninhando código adicional dentro de BEGIN... ENDblocos dentro do corpo do procedimento; por exemplo, se você precisasse de um segundo cursor dentro do loop, basta declará-lo dentro do loop, dentro de outro bloco 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 ;
Michael - sqlbot
fonte
Resposta fantástica, extremamente informativa! Ainda não consegui entender, mas com os recursos fornecidos, tenho certeza de que posso usar os cursores! Obrigado!
Michael MacDonald
isso foi ótimo! o uso de repetição / enquanto fazia meu proc disparar duas vezes para o último registro, exigindo verificações adicionais, mas isso resolve esse problema.
Nick M
fechar cursor1; está faltando OPEN - CLOSE estão indo juntos para os cursores
Miss Felicia A Kovacs
2
Os cursores @MissFeliciaAKovacs podem existir apenas no escopo de um bloco BEGIN/ ENDe são implicitamente fechados quando ficam fora do escopo ... portanto, o fechamento dos cursores não é estritamente necessário. Por uma questão de prática, considero-o desnecessário e não o incluo, mas, para completar, adicionei a CLOSEdeclaração à resposta.
Michael - sqlbot