Каталог решений - Что делать, если строк в документе больше 99’999?

Что делать, если строк в документе больше 99’999?

Что делать, если строк в документе больше 99’999?

В наличии

Решение не претендует на уникальность и, вероятно, имеет определенные изъяны. Готов обсудить, чтобы найти более элегантный вариант.

Категория:

Описание

Преамбула

  1. Программа для обработки сметной документации, ведения сметного документооборота и исполнительной документации;
  2. Имеем более 7 тысяч смет на одном объекте (а объектов не 1). В каждой из которых в среднем более 100 строк. В каждой строке при этом есть определенные ресурсы (материалы, трудозатраты, механизмы);
  3. Необходимо выполнить сводный расчет материалов по N количеству смет (в среднем более 100).

Сам расчет – это, свернутая таблица по материалам. И понятно, что в итоге мы имеем ну 1000-2000 разных материалов для выполнения работ. Но, эти пару тысяч материалов собираются путем агрегирования данных из нескольких сотен (и более) смет, т.е. нескольких тысяч сметных позиций, а также вложенных в них ресурсов.

Расчет выполняется в документе, поскольку результат должен быть сохранен, для дальнейшей отправки в обработку, тендер и т.д.

Для понимания пользователя, что расчет выполнен верно, требуется реализовать ряд возможностей:

  • из формы документа, при нажатии на свернутой позиции материалов, вывести данные: Смета — Позиция / Ресурс – Количество;
  • печать утвержденной формы расшифровки к данной материальной ведомости;
  • при объединении, перемещении и других операциях со строками все расшифровки должны переходить к новым строкам владельцам и т.д.

Табличная часть и 99’999 строк

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

В результате был придуман обходной путь:

  • регистр сведений, не подчиненный регистратору (сбор выполняется редко, при каждой перезаписи документа нет нужды перезаписывать регистр) с ведущим измерением ДокументСсылка.МатериальнаяВедомость (имя документа не важно);
  • на форме при создании на сервере записываем пустое значение во временное хранилище с указанием уникального идентификатора формы:
    • таким образом, получили постоянный адрес для хранения данных;
    • гарантировали жизнь данного адреса, до момента закрытия формы.
  • реализовали фоновое чтение данных из регистра по частям, с указанием условия какие данные запросил пользователь. При повторном чтении, обращения к БД не происходит, программа хранит информацию что данные по указанной строке прочитаны;
    • Маленькая деталь: чтение данных не обязательно делать при открытии, она может быть реализована порциями, согласно отбору, полученному на основании запроса пользователя.
  • перед записью на сервере, передаем адрес хранилища в модуль объекта документа, с целью создания записей в регистре сведений. Естественно, не производим запись, если в этом нет необходимости (например: повторный сбор данных сводной ведомости не производился).

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

Остается существенный вопрос, зачем мы храним данные расшифровки истории сбора сводной ведомости? Во-первых, периодически, в процессе формирования актов КС-2, нам необходимо выполнять чтение данных о реальной стоимости материалов согласно данной ведомости (она, к слову, подписывается с заказчиком). Наименования материалов могут меняться, строки объединяться, но в КС-2 мы должны показать информацию о сметном названии материала, позиции в данной ведомости и ценой согласованной с заказчиком (или субподрядчиком). Таким образом, регистр расшифровки выполняет не только роль хранения "истории", но и инструмента достаточно точного получения данных.

Реализация в коде

Модуль формы документа

&НаСервере
Процедура ПриСозданииНаСервере(Отказ, СтандартнаяОбработка)
    // АдресХранилища - реквизит формы, тип Строка, длина - 0
    ЭтотОбъект.АдресХранилища = ПоместитьВоВременноеХранилище(Неопределено, ЭтотОбъект.УникальныйИдентификатор);
КонецПроцедуры
 
&НаКлиенте
Процедура ПриОткрытии(Отказ)
    // запуск фоновой процедуры считывания данных во временное хранилище
    // может быть выполнен полностью, или частями, или по запросу пользователя
КонецПроцедуры
 
&НаСервере
Процедура ПередЗаписьюНаСервере(Отказ, ТекущийОбъект, ПараметрыЗаписи)
    // для записи данных из хранилища в регистр сведений
    // АдресХранилища - переменная модуля объекта (см. МодульОбъекта)
    ТекущийОбъект.АдресХранилища = ЭтотОбъект.АдресХранилища;  
КонецПроцедуры
 
&НаСервереБезКонтекста
Функция ПолучитьДанныеИзХранилищаНаСервере(знач АдресХранилища, знач НастройкаОтбора)
    // пример получения данных из хранилища
    Если НЕ ЭтоАдресВременногоХранилища(АдресХранилища) Тогда
        Возврат;
    КонецЕсли;
    
    ТаблицаДанных = ПолучитьИзВременногоХранилища(АдресХранилища);
    Если НЕ ТипЗнч(ТаблицаДанных) = Тип("ТаблицаЗначений") Тогда
        // здесь можно разместить процедуру считывания данных согласно настройкам отбора
        Возврат;
    КонецЕсли;
     
    КопияТаблицы = ТаблицаДанных.Скопировать(НастройкаОтбора);
    Возврат ОбщегоНазначения.ТаблицаЗначенийВМассив(КопияТаблицы);
КонецФункции

Модуль объекта документа

// обязательная переменная, для передачи данных между формой и модулем объекта
Перем АдресХранилища Экспорт;
 
Процедура ПриЗаписи(Отказ)
    Если ОбменДанными.Загрузка Тогда
        Возврат;
    КонецЕсли;
     
    ЗаписатьДанныеВременногоХранилища(Отказ);
КонецПроцедуры
 
Процедура ЗаписатьДанныеВременногоХранилища(Отказ)
    Если НЕ ЭтоАдресВременногоХранилища(АдресХранилища) Тогда
        Возврат;
    КонецЕсли;
     
    ТаблицаДанных = ПолучитьИзВременногоХранилища(АдресХранилища);
    Если НЕ ТипЗнч(ТаблицаДанных) = Тип("ТаблицаЗначений") Тогда
        Возврат;
    КонецЕсли;

    // дополнительно можно проверить, что таблица данных получена путем чтения
    // а не повторного заполнения пользователем
 
    Попытка
        // обратите внимание, у регистра сведений есть Измерение - ДокументСсылка
        // рекомендуется делать данное измерение ведущим, чтобы при удалении документа записи очищались
        // при этом сам регистр не подчинен регистратору, иначе при повторной записи данные будут очищаться
        БлокировкаДанных = Новый БлокировкаДанных;
        ЭлементБлокировки = БлокировкаДанных.Добавить("РегистрСведений.ХранениеДанныхДокумента");
        ЭлементБлокировки.УстановитьЗначение("ДокументСсылка", ЭтотОбъект.Ссылка);
        БлокировкаДанных.Заблокировать();
         
        НаборЗаписей = РегистрыСведений.ХранениеДанныхДокумента.СоздатьНаборЗаписей();
        НаборЗаписей.Отбор.ДокументСсылка.Установить(ЭтотОбъект.Ссылка);
        Для Каждого СтрокаЗаписи Из ТаблицаДанных Цикл
            ЗаписьРегистра = НаборЗаписей.Добавить();
            ЗаполнитьЗначенияСвойств(ЗаписьРегистра, СтрокаЗаписи);
            ЗаписьРегистра.ДокументСсылка = ЭтотОбъект.Ссылка;
        КонецЦикла;
        НаборЗаписей.Записать(Истина);
    Исключение
        ТекстОшибки = ПодробноеПредставлениеОшибки(ИнформацияОбОшибке());
        ОбщегоНазначенияКлиентСервер.СообщитьПользователю(НСтр("ru='При записи доп. сведений произошла ошибка: '") + Символы.ПС + ТекстОшибки,,,, Отказ);
    КонецПопытки;
КонецПроцедуры
 
Процедура ОчиститьРегистрСведенийХранениеДанныхДокумента()
     
    НаборЗаписей = РегистрыСведений.ХранениеДанныхДокумента.СоздатьНаборЗаписей();
    НаборЗаписей.Отбор.ДокументСсылка.Установить(ЭтотОбъект.Ссылка);
    НаборЗаписей.Прочитать();
    НаборЗаписей.Очистить();
    НаборЗаписей.Записать(Истина);
     
КонецПроцедуры

Ну вот как то так. 

Постскриптум

Мы понимаем, что могли бы использовать что-нибудь "стильное, модное, молодежное", внешнюю базу данных и может быть даже NoSQL. Но, у нас просто было мало времени на подумать и еще меньше времени на реализовать. Как всегда результат нужен был "здесь и вчера". Решение было создано очень быстро, запущено в работу и проходит анализ на предмет скрытых просчетов. Более того, не у всех заказчиков возможно использование сторонних БД, поэтому данный вопрос будет скорее всего повторно обсуждаться гораздо позже.

Повторюсь, решение возможно не уникальное и не лишенное недостатков. Поэтому прошу направить на путь истинный, если имеются дельные предложения.

has been added to your cart:
Оформление заказа