Статистика на большой базе. Замена стат. формы табличной формой
Работа библиотеки тесно связана с ведением статистики. Количество выданных книг, посещений, регистраций/перерегистраций, что и кому выдавалось, где и когда, и много других вопросов статистического характера регулярно задают инженерам. С большинством вопросов статистики можно справиться с помощью механизма стат. форм. Однако, когда база читателей переваливает где-то за 60-80 тысяч читателей, статистика начинает формироваться слишком долго. Иной раз отчеты так и не формируются, потому как АРМы отключаются по таймауту. Проблема в том, что модуль формирование стат. отчетов для анализа каждой записи выполняет несколько запросов:
1. чтение всей записи целиком
2. расформатирование прочитанной записи
3. обработка полученных результатов
И все эти действия выполняются в режиме общения АРМа с TCP/IP сервером Ирбиса. В итоге, получается достаточно длительная работа. Оптимизировать работу модуля стат. отчетов не представляется возможным. Однако! Появилась идея решить задачу получения статистических отчетов другим способом – с помощью режима печати табличных форм.
Для начала немного о том, как работают стат. формы.
Стат. формы строятся вокруг двух форматов и двух справочников – для вертикали и горизонтали. Справочники описывают значения и формируют матрицу, в которую будут выводиться данные: размеры матрицы данных равняются по вертикали количеству значений в справочнике вертикали и по горизонтали, аналогично, количеству значений горизонтали. Для наглядности, пример:
Справочник вертикали:
1
Значение 1
2
Значение 2
3
Значение 3
*****
Справочник горизонтали:
А
Значение 1
Б
Значение 2
В
Значение 3
Г
Значение 4
*****
Тогда матрица будет следующей: 3х4
1:А 1:Б 1:В 1:Г
2:А 2:Б 2:В 2:Г
3:А 3:Б 3:В 3:Г
С матрицей определились, теперь определяемся с заполнением матрицы данными. У нас есть два формата, которые отрабатывают на записи и формируют данные для вертикали и горизонтали. Данные, которые формируют форматы, должны соответствовать значениям из справочников. Отдельно хочу обратить на это внимание, потому как это частая ошибка. В некоторых стат. формах встречается ситуация, когда не выполняют контроль формируемых форматами значений для вертикали и горизонтали, в итоге получаются термины, которых нет в справочниках вертикали и горизонтали, а это дает в итоге нестыковку в распределении данных.
Вернемся к нашим форматам. Форматы вертикали и горизонтали формируют термины, которые объединяются попарно и представляют собой координаты, по которым ищутся ячейки матрицы. Пара терминов вертикали и горизонтали идентифицируют конкретную ячейку в матрице, и значение в этой ячейке увеличивается на единицу.
Например, на записи отработали форматы вертикали и горизонтали
Справочник вертикали:
чз
Читальный зал
аб.1
Абонемент 1
аб.2
Абонемент 2
аб.ин.
Абонемент иностр.
аб.муз.
Абонемент музык.
*****
Значения формата вертикали:
чз
чз
аб.1
аб.2
аб.2
аб.1
аб.муз.
Справочник горизонтали:
01
январь
02
февраль
03
март
…
10
октябрь
11
ноябрь
12
Декабрь
*****
Значения формата горизонтали:
04
06
08
09
09
09
09
То есть, имеет следующие пары координат:
(чз:04)(чз:06) (аб.1:08)(аб.2:09)(аб.2:09)(аб.1:09)(аб.муз:09)
При таких справочниках и сформированных форматами данных матрица будет заполнена следующим образом:
<code>
01 02 03 04 05 06 07 08 09 10 11 12
чз 1 1
аб.1 1 1
аб.2 2
аб.ин.
аб.муз 1
</code>
Отрабатывая на каждой следующей записи, значения в матрице все время пополняются: каждый раз, когда координаты указывают на какую-то ячейку в матрице, ее значение увеличивается на единицу.
Отдельно стоит обратить внимание на ситуацию, когда количество значений по вертикали и горизонтали не совпадает. Это типичная ситуация для стат.форм, в которых анализ выполняется по полям с разным количеством повторений. Например, распределение количества зарегистрированных читателей по кафедрам выдачи и категориям. В записи может быть сколько угодно повторений поля 51 с данными о дате и месте регистрации (абонементах), но может быть одна категория. В тех случая, когда количество терминов для вертикали и горизонтали не совпадают, выполняется следующее правило: сделать пары координат для всех терминов, для которых есть значения вертикали и горизонтали, а когда останутся термины только для одной размерности, добавлять им в пару ПОСЛЕДНИЙ существующий термин из первой размерности. Например:
Значения вертикали:
Аб1
Аб2
Чз
Аб1
Значения горизонтали:
школьник
Сформированные пары координат:
(Аб1:школьник)
(Аб2:школьник)
(Чз:школьник)
(Аб1:школьник)
Как уже говорилось в начале, стат. формы на очень больших базах (60-80 тысяч записей и более) начинают работать чудовищно медленно, что и вызвало необходимость искать альтернативный способ получения статистических данных.
Ключевой момент ускорения формирования стат. форм – это передача выполнения всех операция на сервер. Так как вклиниться в работу сервера нет возможности, то нужно использовать штатные методы для обхода записей и расформатирования их. В связи с этим было принято решения получать стат. отчеты в режиме печати выходных табличных форм. В принципе, если задаться такой целью, то реализовать статистическую форму можно вообще полностью штатным функционалом печатной формы. Для этого существует механизм суммирования данных – секция SUM в файле TBU, в которой можно ввести форматы для подсчета данных, а в конце работы формы вывести все данные. В том случае, если у вас формируется небольшое количество данные, то есть размерность вертикали и горизонтали не большая, допустим по 4-5 значений, то настроить это еще реально: нужно написать 16-25 форматов, а потом в области итогов описать таблицу, где в каждую ячейку будут выводиться эти данные. Но для больших стат. отчетов, а так же для таких, которые периодически приходится редактировать. это уже становится крайне утомительной задачей.
Что нужно, что бы в режиме печатной табличной форме получить статистику? Не так уж и много:
• форматы для вертикали и горизонтали
• буфер, в которой после статистической обработки каждой записи будут записываться итоги
• механизм для чтения/записи в буфер данных
• механизм формирования итоговой таблицы с данными
Форматы для вертикали и горизонтали уже есть – они у нас в стат. формах были созданы и там использовались, принципиально что-то в них переделывать смысла и надобности нет. Единственное что пришлось изменить – метку модельного поля, так как в стат. формах она могла быть любой, и там часто использовались метки 999, 1000, 1001 и любые другие, а в режиме печати табличных форм модельное поле, которое формируется на основе данных из опросного листа и передается в запись для запуска механизм фильтрации данных, может быть только с меткой 991. Форматы вертикали и горизонтали должны выполняться на самой записи, по этому их целесообразно поместить в файл [TABNAME].PFT. Там же поместим механизм, который обработанные данные текущей записи будет складывать в буфер и передавать из записи в запись.
Механизм буфера построен на глобальных переменных. Для нас главным оказывается наличие двух их свойств: то, что эти переменные можно передавать из записи в запись, и то, что мы их можем модифицировать.
В принципе, если очень задаться целью и не бояться большого количества вложенных циклов, которые в языке манипулирования данными реализуются с помощью вложенных форматов, то можно обойтись вообще одними только штатными средствами – файлами таблицы и форматами. Я решил несколько упростить код за счет того, что вынес функции работы с буферов во внешнюю библиотеку (array_dll.dll). В этой библиотеке реализовано несколько функций:
GetCall – получение данных из ячейки с заданными координатами;
AddArrayValue – установка значений ячеек (передается три массива: массив индексов вертикали, массив индексов горизонтали, массив значений);
FindIndex – определение индекса (номера) для вертикали или горизонтали по значению, которое было сформировано форматами вертикали и горизонтали (выполняется по справочнику)
Для наглядности я дальше буду оперировать названием файлов для печатной формы exp_stat. Среди них:
exp_stat.hdr – определяет наличие опросного листа и заголовка над таблицей
exp_stat.pft – формат, который выполняется на каждой записи
exp_stat.tbu – описание самой RTF-страницы и шаблона таблицы, так же именно в этом файле происходит суммирование данных и вывод итоговой сумму, а в нашем случае – самой таблицы
exp_stat.wss – файл опросного листа подполей, который используется для передачи параметров в стат. форму; этот файл описан в файле
exp_stat.hdr
exp_stat_h.pft – файл заголовка таблицы; этот файл описан в файле exp_stat.hdr
exp_stat_row.pft – формат, используемый стат. формой для заполнения данных строки в таблице
exp_stat_summ_col.pft – формат, используемый стат. формой для подсчета итогов в столбце
И так, создаем печатную форму для получения статистики по регистрации/перерегистрации читателей.
В файле exp_stat.pft помещается почти без изменений формат вертикали и горизонтали из старой стат. формы. Единственное изменение – это замена номеров меток модельного поля на 991. В глобальных переменных G100 и G200 находятся загруженные справочники вертикали и горизонтали. Они нам нужны для определения координат. Значения терминов вертикали записываются в глобальную переменную G101, значения терминов горизонтали – в глобальную переменную G201. На этом моменте у нас есть сформированные термины и нам нужно их преобразовать в координаты и занести в массив, который передается из записи в запись в глобальной переменной с меткой G1. После того, как мы загрузили в глобальные переменные G100/G200 справочники вретикали и горизонтали, а в глобальные переменные G101/G201 значения, которые сформировали форматы вертикали и горизонтали, мы можем определить координаты терминов. Для этого служит функция FindIndex из библиотеки array_dll. В результате у нас в глобальных переменных G102/G202 записываются уже числовые координаты ячеек, значения которых нужно увеличить. Изменение данных в массиве буфера выполняется с помощью функции AddArrayValue из библиотеки array_dll. В эту функцию передается два массива координат и текущее значение массива буфера (G1). Функция возвращает многострочный текст, который является измененным массивом буфера с данными из всех предыдущих и текущей записей.
/*список значений вертикали
&uf('+7W100#'(&unifor('+5Tkv_STF.mnu')/)),
&uf('+7W200#'(&unifor('+5Tvozrast_STF.mnu')/)),
/*список значений горизонтали
/*Получаем значения распределений по вертикали
&uf('+7w101#'
(if p(v51) then
if (v51^*>=&uf('Av991^a#1')) and (v51^*<=&uf('Av991^b#1')) then
if (&uf('kkv_STF.mnu!'v51^c)<>'') then v51^c else '*' fi/
fi
fi),
(if p(v52) then
if (v52^*>=&uf('Av991^a#1')) and (v52^*<=&uf('Av991^b#1')) then
if (&uf('kkv_STF.mnu!'v52^c)<>'') then v52^c else '*' fi/
fi
fi)
),
/*Получаем значения распределений по горизонтали
&uf('+7w201#'
if p(v21) then
&uf('+7W300#',&uf('3')),
if (val(&uf('+95',v21))=4) or (val(&uf('+95',v21))=8) then
&uf('+7W301#',F(val(g300.4)-val(v21.4),0,0)),
if (v21*4<>'') then
if val(g300*4)<val(v21*4) then
&uf('+7W301#',F(val(g301)-1,0,0))
fi
fi,
if (val(g301)<=6) then '1' fi,
if (val(g301)>=7) and (val(g301)<=14) then '2' fi,
if (val(g301)>=15) and (val(g301)<=30) then '3' fi,
if (val(g301)>=31) and (val(g301)<=60) then '4' fi,
if (val(g301)>=61) then '5' fi,
else
'*'
fi,
else
'*'
fi
),
/*Внесение данных в массив происходит только тогда, когда есть и значения вертикали, и значения горизонтали
if s(g101)<>'' and s(g201)<>'' then
/*Список индексов вертикали
&uf('+7w102#'&uf('+8array_dll,FindIndex,'(|$|g101|$!|),/,(|$|g100|$!|))),
/*Список индексов горизонтали
&uf('+7w202#'&uf('+8array_dll,FindIndex,'(|$|g201|$!|),/,(|$|g200|$!|))),
if s(g102)<>'' and s(g202)<>'' then
/*Формирование массива со статистическими данными
&uf('+7w1#'&uf('+8array_dll,AddArrayValue,'(|$|g102|$!|)/(|$|g202|$!|)/(g1/))),
fi,
fi,
На самом деле, для получения статистики уже вот этого хватит. Можно остановится на том, что в exp_stat.tbu в секции [Sum] вывести на печать полученный массив. Но это будет малоинформативно. По этому, как раз в этом файле и в этой секции начинается самая интересная часть – нам нужно нарисовать таблицы, заполнить данные шапки вертикали и горизонтали, внести данные из массива буфера и заполнить итоги. Так как там уже будут предполагаться вложенные циклы, то для решения этих задач используются вложенные форматы (exp_stat_row, exp_stat_summ_col.pft)
Еще один момент, который пришлось ввести в файл exp_stat.tbu. Дело в том, что в секции [Sum] не работают обычные для языка форматирования комментирования - /*. При их использовании форматы перестают работать. В качестве комментариев используется глобальная переменная 123. Так что если вы видите что-то вот такое «&uf('+7w123#________», то это не полезный функционал, а пояснение того, что будет далее.
Что происходит в этом файле:
1. Формируется и записывается в глобальную переменную G501 шапка для строки таблицы. При желании в этой части можно менять размер первой левой ячейки, в которой выводится расшифровка значений горизонтали, а так же жирность и видимость линий. Ширина всех ячеек в переменной части рассчитывается автоматически: из размера таблицы на всю страницу вычитается размер первой ячейки и делится на количество значений по горизонтали + 1 ячейку (для итогов).
2. Выводится шапка таблицы: в начале описание ее тегов из переменной G501, а потом данные из справочника горизонтали
3. Цикл по значениям переменной G100 выводит строки таблицы. Каждая итерация цикла – это одна строка. В этой части выводятся описания строки таблицы, а значения выводятся с помощью вложенного формата exp_stat_row, в котором происходит перебор значений и разделение их тегами. В конце строки выводятся суммарные данные для текущей строки
4. После вывода всех данных по массиву буфера, формируется еще одна строка, в которую выводятся итоговые данные для всех столбцов. Для этого используется вложенный формат exp_stat_summ_col
[FormatCode]
WIN
*****
[Sum]
'1'
#
&uf('+7W100#'(&unifor('+5Tkv_STF.mnu')/)),
&uf('+7W200#'(&unifor('+5Tvozrast_STF.mnu')/)),
&uf('+7w123#________Подсчитываем кол-во значений по горизонтали________'),
&uf('+7w502#'f(rsum((if g200<>'' then '1;'fi)),0,0)),
&uf('+7w123#________Формируем описание строки таблицы________'),
&uf('+7w501#'
&uf('+7w123#________Общая часть и первая колонка________'),
'\trowd\trgaph108\trleft-108'
'\trbrdrl\brdrs\brdrw10'
'\trbrdrt\brdrs\brdrw10'
'\trbrdrr\brdrs\brdrw10'
'\trbrdrb\brdrs\brdrw10'
'\trpaddl108\trpaddr108\trpaddfl3\trpaddfr3'
'\clbrdrl\brdrw10\brdrs'
'\clbrdrt\brdrw10\brdrs'
'\clbrdrr\brdrw10\brdrs'
'\clbrdrb\brdrw10\brdrs \cellx2127'
&uf('+7w123#________значение отступа границы текущей ячейки от левого края________'),
&uf('+7w123#________2127 - ширина первой колонки с данными вертикали, приблизительно 4см ________'),
&uf('+7w503#2127'),
&uf('+7w123#________ширина ячейки________') ,
&uf('+7w504#'f((13608 - 2127)/ (val(&uf('ag502#1')) + 1) ,0,0)),
(if g200<>'' then
&uf('+7w503#'f(rsum(&uf('ag503#1')';'&uf('ag504#1')),0,0))
'\clbrdrl\brdrw10\brdrs'
'\clbrdrt\brdrw10\brdrs'
'\clbrdrr\brdrw10\brdrs'
'\clbrdrb\brdrw10\brdrs \cellx'&uf('ag503#1')
fi)
&uf('+7w123#________Ячейка под итоги в строке________'),
&uf('+7w503#'f(rsum(&uf('ag503#1')';'&uf('ag504#1')),0,0))
'\clbrdrl\brdrw10\brdrs'
'\clbrdrt\brdrw10\brdrs'
'\clbrdrr\brdrw10\brdrs'
'\clbrdrb\brdrw10\brdrs \cellx'&uf('ag503#1')
'\pard\intbl\nowidctlpar '
),
&uf('+7w123#________Выводим шапку таблицы________'),
&uf('ag501#1')
'\cell '
(if g200<>'' then
&uf('kvozrast_STF.mnu!'g200),
'\cell '
fi),
'\bВСЕГО\b0\cell\row'
'\pard\nowidctlpar'
&uf('+7w105#0'),
(if g100<>'' then
&uf('+7w105#'f(rsum(&uf('ag105#1'),';1;'),0,0)),
&uf('+7w123#________Выводим описание строки таблицы________'),
&uf('ag501#1')
&uf('+7w123#________Выводим данные строки________'),
&uf('kkv_STF.mnu!'g100),
'\cell '
&uf('+7w500#'g1),
&uf('6exp_stat_row'),
&uf('+7w123#________Выводим итоги строки________'),
if rsum(&uf('ag500#1'))>0 then f(rsum(&uf('ag500#1')),0,0) else '0' fi,
'\cell\row'
fi/),
&uf('+7w123#________Выводим Итоги по столбцам________'),
&uf('+7w105#0'),
&uf('+7w506#0'),
&uf('+7w123#________Выводим описание строки таблицы________'),
&uf('+7w123#________Выводим данные строки________'),
'\bВСЕГО:\b0',
'\cell '
(if g200<>'' then
&uf('6exp_stat_summ_col'),
'\cell '
&uf('+7w105#'f(rsum(&uf('ag105#1'),';1;'),0,0)),
fi/),
&uf('ag507#1')'\cell\row'
'\pard\nowidctlpar\par'
*****
[Tab]
\paperw16839\paperh11907\margl1701\margr850\margt1134\margb1134
{\b\fs\fs24\qc
[Header]
\b0 }
}
Остается сказать только про шапку таблицы. Это файл exp_stat_h.pft, в котором можно организовать вывод данных из опросного листа, название таблицы и прочее.
На что хотелось бы обратить внимание – получившееся решение на самом деле достаточно универсально. В этой таблице есть только три файла, которые нужно модифицировать для получения новых стат. отчетов: в шапке exp_stat_h.pft внести заголовки и название таблицы, в файле теле exp_stat.pft внести (скопировать!) форматы горизонтали и вертикали из старых стат. форм, в файле exp_stat.tbu заменить названия справочников вертикали и горизонтали.
По поводу скорости работы хочу привести пример. Обычная стат. форма на базе читателей в 110 000 человек не справляется с выдачей результатов за 2 часа. В режиме печати табличной формы нужные результаты выдаются где-то за 20 минут на все той же базе в 110 000 читателей.
Редактировано 2 раз. Последний раз 10.11.2019 14:19 пользователем Gena.