Официальная возможность получить лицензионный софт бесплатно.
Giveaway of the Day
Это не реклама!

Щелкните для получения прогноза по Биробиджану


пятница, 8 апреля 2016 г.

OpenOffice и курсоры MySQL

Не очень разработчики ООО (да и 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, например, выбрать в поиске только одну строку, запомнить ее ключевое поле, выбрать еще строку, в которой ключевое поле отличается.

Комментариев нет:

Отправить комментарий

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