Довольно длительное время я сопровождал расчет бонусов в одной организации. Нюанс заключался в том, что алгоритмы и сами структуры данных для расчета постоянно менялись, порой кардинально. Чтобы анализировать данные за период, нужно было сохранять данные расчетов, т.к. воспроизвести их было не реально. Однако возник вопрос: в каком формате сохранять, если в таблицах постоянно добавлялись и удалялись колонки, кроме ключевых?
Решено было сохранять таблицы расчетов во внешние файлы, а затем их восстанавливать при необходимости.
Технология оказалась удачной, хочу поделиться опытом.
За каждый период формируется несколько таблиц, например ТЗРасчет, ТЗСтавки, ТЗТовары. В каждом из этих периодов у всех таблиц есть устойчивые ключевые колонки, но для расчетов могут добавляться и другие колонки, каждый раз разные.
Для целей анализа нужно сделать такую систему хранения расчетов, чтобы можно было извлекать сохраненные расчеты за некоторый промежуток времени и давать по ним сводную информацию.
На выходе такое решение выгодно даже для анализа текущего периода – отдельные расчеты сохранялись и затем по ним можно быстро получить сводную аналитику.
Справедливости ради нужно заметить, что сначала я пошел по другому пути – создал таблицу в базе данных, рассчитанную на хранение универсальных данных.
Что-то вроде таблицы с полями: IDТаблицы, IDПоля, Значение.
Но не получилось, возникли проблемы:
Такая методика не годилась.
Все таблицы расчетов решено было сохранить в каталог на сервере в виде отдельных файлов.
Т.к. расчеты делались за половинки месяцев, то для каждой половины месяца создавались подкаталоги с названиями вида YYYYMM01 и YYYYMM16. Можно было обойтись без деления на подкаталоги, просто указывать адрес для файла в его имени, но подкаталоги используются потому, что Windows плохо работает с каталогами с огромным количеством файлов.
В имя файла также добавлялся код подразделения, по которому производился расчет, т.к. каждый расчет шел по отдельному подразделению.
Вот пример файлов в каталоге:
В каждый файл сохраняется таблица через функцию ЗначениеВСтрокуВнутр, обратно разворачивается через ЗначениеИзСтрокиВнутр.
Один расчет по 40 подразделениям за полмесяца занимает 150 Мб:
Все расчеты за полгода занимают 7 Гб:
Вот как выглядит каталог с расчетами Sequoia View с расчетами за полгода:
Бэкапить такие файлы удобно, не нужно хранить лишнюю информацию в базе.
В разделе собран полезный для организации таких хранилищ код.
Библиотека для работа с Расчетами:
Функция ПолучитьКаталогСохраненияРасчетов(Дата) Экспорт
БазовыйКаталог = СокрЛП(Справочники.САПКонстантыЛокальные.КаталогСохраненияРасчетов.Значение);
Если БазовыйКаталог = "" Тогда
Возврат БазовыйКаталог;
КонецЕсли;
ПодКаталог = Формат(Дата, "ДФ=yyyyMMdd");
Возврат САП.СоздатьКаталогЛога(БазовыйКаталог, ПодКаталог, ложь);
КонецФункции
Функция ПолучитьИмяФайлаРасчетов(Магазин, Интервал, ИмяТаблицы) Экспорт
ИмяФайла = СокрЛП(Магазин.Код) + "_" + Формат(Интервал.ДатаНачала, "ДФ=dd_MM_yyyy") + "_" + Формат(Интервал.НомерПериода, "ЧН=0; ЧГ=") + "_" + ИмяТаблицы+".txt";
Возврат ИмяФайла;
КонецФункции
Функция ПолучитьИмяФайлаРасчетовПолное(Магазин, Интервал, ИмяТаблицы) Экспорт
Каталог = ПолучитьКаталогСохраненияРасчетов(Интервал.ДатаНачала);
Если НЕ ЗначениеЗаполнено(Каталог) Тогда
Возврат Неопределено;
КонецЕсли;
ИмяФайлаРасчетов = ПолучитьИмяФайлаРасчетов(Магазин, Интервал, ИмяТаблицы);
ИмяФайлаПолное = Каталог + "\" + ИмяФайлаРасчетов;
Возврат ИмяФайлаПолное;
КонецФункции
Функция СохранитьРасчет(Магазин, Интервал, ИмяТаблицы, Таблица) Экспорт
ИмяФайла = ПолучитьИмяФайлаРасчетовПолное(Магазин, Интервал, ИмяТаблицы);
Если ИмяФайла = Неопределено Тогда
Возврат Неопределено;
КонецЕсли;
Попытка
Значение = ЗначениеВСтрокуВнутр(Таблица);
Т = Новый ТекстовыйДокумент();
Т.УстановитьТекст(Значение);
Т.Записать(ИмяФайла, "UTF-8");
Возврат ИмяФайла;
Исключение
ОписаниеОшибки = ОписаниеОшибки();
//Если Найти(";ТЗСебестоимость;", ";" + ИмяТаблицы + ";") <> 0 Тогда
// Сообщить("Не смогли преобразовать таблицу для сохранения в файл: " + ИмяФайла + " " + ОписаниеОшибки, СтатусСообщения.Важное);
//Иначе
ВызватьИсключение "Не смогли преобразовать таблицу для сохранения в файл: " + ИмяФайла + " " + ОписаниеОшибки;
//КонецЕсли;
КонецПопытки;
КонецФункции
Функция ВосстановитьРасчет(Магазин, Интервал, ИмяТаблицы) Экспорт
ИмяФайла = ПолучитьИмяФайлаРасчетовПолное(Магазин, Интервал, ИмяТаблицы);
Если ИмяФайла = Неопределено Тогда
Возврат Неопределено;
КонецЕсли;
Попытка
Т = Новый ТекстовыйДокумент();
Т.Прочитать(ИмяФайла, "UTF-8");
Текст = Т.ПолучитьТекст();
Значение = ЗначениеИзСтрокиВнутр(Текст);
Исключение
Возврат Неопределено;
КонецПопытки;
Возврат Значение;
КонецФункции
Пример использования в расчетах:
Для Каждого Магазин из ВыбМагазины Цикл
Для Каждого Интервал Из ВыбИнтервалы Цикл
#Если Клиент Тогда
Состояние("Бонусы. Магазин: " + Магазин + "Интервал: " + Интервал);
#КонецЕсли
ТЗСтавки = ВосстановитьРасчет(Магазин, Интервал, "ТЗСтавки");
Если ТЗСтавки = Неопределено Тогда
Сообщить("Не найден расчет ТЗСтавки: " + " Магазин: " + Магазин + "Интервал: " + Интервал);
Продолжить;
КонецЕсли;
Для Каждого Стр ИЗ ТЗСтавки Цикл
КонецЦикла;
ТЗПозиции = ВосстановитьРасчет(Магазин, Интервал, "ТЗПозиции");
Если ТЗПозиции = Неопределено Тогда
Сообщить("Не найден расчет ТЗПозиции: " + " Магазин: " + Магазин + "Интервал: " + Интервал);
Продолжить;
КонецЕсли;
Для Каждого Стр ИЗ ТЗПозиции Цикл
КонецЦикла;
КонецЦикла;
КонецЦикла;