MySQL -> цикл по таблице, запуск хранимой процедуры для каждой записи

9

У меня есть база данных с «книгами» (рассказами для детей), и было бы чрезвычайно информативно иметь количество слов для каждого слова в книгах.

Я выяснил, как получить количество слов для каждого слова, используя:

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

Который прекрасно работает для подсчета слов. НО это требует, чтобы я просмотрел каждую книгу, вытащил каждое слово и провел через эту функцию (я сохранил его как хранимую процедуру).

У меня есть таблица, которая содержит каждое слово, без дубликатов.

Мой вопрос: есть ли способ сделать какой-то цикл «для каждого» в таблице «Слова», используя мою хранимую процедуру?

то есть. передайте хранимой процедуре идентификатор книги и слово и запишите результат. Делать каждое слово, для каждой книги. Таким образом, экономя мне много ручного времени ... Это то, что я должен делать со стороны БД? Должен ли я попробовать это с PHP вместо этого?

Честно говоря, любой вклад очень ценится!

Майкл Макдональд
источник
1
Вы можете создать таблицу из (всех) слов, анализируя книги. Тогда это станет одним из избранных, соединяющих книги со словами. Там петли не нужны.
jkavalik
Некоторые задачи лучше выполнять на реальном языке программирования, а не на SQL. В PHP это может быть что-то вроде count(explode(' ', $pageText))+1. Или что-то более сложное для обработки нескольких пробелов между словами, возможно, с участиемpreg_replace('/\s+/', ' ', $pageText)
Рик Джеймс
Для Perl это может быть как коротко 1+split(/\s+/, $pageText). 1, потому что количество пробелов, а не слов.
Рик Джеймс

Ответы:

14

Создайте вторую процедуру, которая использует два вложенных курсора.

Курсоры в хранимых процедурах позволяют вам делать что-то совсем не похожее на SQL: выполнять итерацию по результирующему набору по одной строке за раз, помещая значения выбранных столбцов в переменные и работая с ними.

Их легко неправильно использовать, поскольку SQL, будучи декларативным, а не процедурным, обычно не должен нуждаться в операциях типа «для каждого», но в этом случае он выглядит как действительное приложение.

Как только вы освоите их, курсоры станут проще, но они требуют структурированного подхода в своем поддерживающем коде, который не всегда интуитивен.

Недавно я предоставил некоторый довольно стандартный «шаблонный» код для работы с курсором для вызова хранимой процедуры в ответе о переполнении стека , и я буду очень заимствовать из этого ответа ниже.


Использование курсора требует некоторого стандартного стандартного кода для его окружения.

Вы SELECTхотите передать значения, откуда бы вы их ни получали (это может быть временная таблица, базовая таблица или представление и могут включать в себя вызовы хранимых функций), а затем вызывать вашу процедуру existinf с этими значениями.

Вот синтаксически правильный пример необходимого кода с комментариями, объясняющими, что делает каждый компонент.

В этом примере используются 2 столбца для передачи 2 значений вызываемой процедуре.

Обратите внимание, что события здесь происходят в определенном порядке по определенной причине. Переменные должны быть объявлены в первую очередь, курсоры должны быть объявлены до их обработчиков продолжения, и циклы должны следовать за всеми этими вещами.

Вы не можете делать вещи не по порядку, поэтому, когда вы вкладываете один курсор в другой, вы должны сбросить область действия процедуры, вложив дополнительный код внутри BEGIN... ENDблоков внутри тела процедуры; например, если вам нужен второй курсор внутри цикла, вы просто объявите его внутри цикла, внутри другого 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 ;
Майкл - sqlbot
источник
Фантастический ответ, чрезвычайно информативный! Еще не получил его, но с предоставленными ресурсами, я уверен, я могу заставить работать курсоры! Спасибо!
Майкл Макдональд
это было прекрасно! использование repeat / while заставило мой proc срабатывать дважды для последней записи, что потребовало дополнительных проверок, но это решило эту проблему.
Ник М,
закрыть курсор1; отсутствует ОТКРЫТО - ЗАКРЫТЬ собираются вместе для курсоров
Мисс Фелисия А Ковач
2
Курсоры @MissFeliciaAKovacs могут существовать только в области блока BEGIN/ ENDи неявно закрываются, когда выпадают из области видимости ... поэтому закрывать курсоры не обязательно. На практике я считаю это ненужным и не включаю его, но для полноты я добавил CLOSEутверждение в ответ.
Майкл - sqlbot