Не очень разработчики ООО (да и LO) любят MySQL. Самое наглядное проявление этого - полное отсутствие поиска, не говоря уже о замене, в редакторе скриптов. Да-да, в редакторе запросов в принципе нет такой возможности. Уже смешно.
Про неумение LO работать с теми скриптами, с которыми OOO справляется влёт, я уже молчу.
Но выяснилась, причем совершенно в неподходящий момент, неожиданная бяка. Понадобилось использовать курсоры в хранимой процедуре, чтобы не плодить множество вложенных select-ов. Но, как это иногда бывает, MySQL server gone away из-за таймаута. (это в ООО, а LO модча показывал пустое окно вместо результатов запроса). Методом тыка было выяснено, что сервер "уходит" на команде open cursor.
Почему я решил, что виноват офис? Да потому что из-за неудобства IDE в этом офисе, для написания и отладки запросов я использую MySQL Workbench, и в ней всё работало отлично, сервер никуда не пропадал и результаты возвращались как положено.
Печально, но выяснилось, что использовать курсоры нельзя. Пришлось, благо, что хранимые процедуры/функции пишутся на более "стандартном" диалекте, близком к обычным ЯВУ, лепить циклы опроса и совершать прочие некрасивые действия, чтобы имитировать функциональность курсоров.
Суть задачи:
Есть система инвентаризации ИТ OCS-NG Inventory. Ее БД хранится в виде множества связанных таблиц, где основная таблица ocsweb.hardware, а большинство остальных таблиц связано с ней соотношением одно-ко-многим, то есть, для каждой записи в hardware может быть несколько записей в других таблицах.
В частности, конкретно в моем случае, для каждого компьютера может быть несколько записей в таблице "Мониторы". Действительно, ведь можно подключить несколько мониторов к одной системе. Но в ежеквартальном отчете все мониторы одного компа должны попасть в одну ячейку электронной таблицы.
group_concat, конечно, справляется, но... Но надо сначала обработать отдельные поля этих записей. Например, без "драйверов" старые мониторы LG не сообщают о себе некоторую информацию. А другие наоборот, сообщают всё подряд, включая серийный номер и дату производства. Если тупо слепить вместе все поля, то может получиться некрасиво.
Допустим, один монитор сообщил о себе всё, и group_concat может сделать примерно такое: "модель (производитель), sn: номер". Но в случае старого монитора такая красота превратится в уродливое " (проихводитель), sn: " или вообще в " ( ), sn: ", за что я уже получал (и справедливо) от начальства.
Стало быть, надо обработать возможные ситуации разной заполненности полей и в зависимости от этого сформировать итоговую строку так, чтобы она оставалась аккуратной. Учитывая слабую гибкость того, что можно вписать внутрь select-a. сей процесс превращается в тяжеловесную конструкцию со множеством скобок и вложенных операторов. Потому мне и понадобилась хранимая функция, в которой всё то же самое можно проделать намного элегантнее.
Так как записей в связанной таблице может быть несколько, то надо обрабатывать их одну за другой, объединяя результаты в итоговую строчку. Как это выглядит с курсором?
declare (переменные для курсора)
declare (переменная-счетчик)(количество_записей)
declare (курсор)
declare (переменная результат)
open курсор
счетчик=1
результат=''
количество записей=select count(*) from таблица where такая-то-связь
label: loop
fetch курсор into переменные курсора
обработать_переменные_курсора
# счетчик дописывается в итоговую строку, если мониторов больше одного
результат=concat(результат, обработанные_переменные_курсора)
счетчик++
end loop
return результат
fetch возвращает по одной записи за раз и можно спокойно двигаться по связанной таблице. Ну да, у этих курсоров есть свои ограничения, но в своей задаче я в эти ограничения не упираюсь. А вот select возвращает набор записей, который штатно нельзя сохранить в переменной. И для "последовательного" доступа к записям в этом наборе надо извращаться примерно так:
select MANUFACTURER,CAPTION,DESCRIPTION,SERIAL
from (select (@mwr:=@mwr+1) kak,MANUFACTURER,CAPTION,DESCRIPTION,SERIAL
from (select @mwr:=0) rt, monitors where hardware_id=gmhwid) pp
where kak=RowNum into gmM, gmC, gmD, gmS;
То есть, использовать три select-a вместо одного простого fetch.
Внутренний
select - старый трюк для добавления порядковых номеров к возвращаемым записям - из псевдо-таблицы выбирается
поле, увеличивающееся при каждой выборке.
Средний
select выполняет собственно выборку из таблицы мониторов. Возвращается набор пронумерованных записей
Внешний
select собственно и имитирует работу fetch-a, выбирая из
среднего запись, соответствующую
номеру итерации.
Наверное, можно поиграться с limit, например, выбрать в поиске только одну строку, запомнить ее ключевое поле, выбрать еще строку, в которой ключевое поле отличается.