Как правильно спроектировать хранение данных в 1С: что записывать в базу, а что рассчитывать на лету?

Программист 1С v8.3 (Управляемые формы) Управленческий учет IT и автоматизация бизнеса
← На главную

В процессе проектирования метаданных в платформе 1С:Предприятие перед каждым разработчиком неизбежно встает классический архитектурный вопрос: какие данные необходимо физически записывать в таблицы базы данных СУБД, а какие следует вычислять программно в момент обращения? Этот вопрос часто становится предметом жарких споров между разработчиками, привыкшими к канонической нормализации реляционных баз данных (например, специалистами по C++ или веб-разработчиками), и практикующими архитекторами 1С, которые руководствуются соображениями финансовой точности и производительности крупных учетных систем.

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

  1. Первичные данные (Блок А): номенклатура, физическое количество, базовая цена, процент скидки, цена себестоимости.
  2. Прямые расчетные данные (Блок Б): конечная сумма строки, сумма скидки в денежном выражении, общая сумма себестоимости строки.
  3. Косвенные расчетные данные (Блок В): доля строки в общем объеме документа (процент от общей суммы), отклонение от максимальной стоимости позиции и другие аналитические коэффициенты.

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

Архитектурный баланс: нормализация против денормализации

С точки зрения классической теории баз данных и нормальных форм (вплоть до шестой нормальной формы), хранение избыточных, вычисляемых значений является грубой ошибкой проектирования. Действительно, зачем хранить поле Сумма, если его можно в любой момент получить простым перемножением по формуле Количество * Цена? Чем меньше данных мы храним, тем меньше весит база данных и тем быстрее выполняются операции записи (INSERT/UPDATE).

Однако в реальных учетных системах чистая нормализация вступает в жесткое противоречие с производительностью на чтение (SELECT) и бизнес-логикой. В 1С мы постоянно сталкиваемся со следующими факторами:

  1. Нагрузка на СУБД при построении отчетов: если бы система рассчитывала суммы на лету при каждом формировании оборотно-сальдовой ведомости или анализа продаж по миллионам строк, процессоры сервера СУБД мгновенно уходили бы в стопроцентную нагрузку.
  2. Разделение концепций OLTP и OLAP: документы в 1С выступают в роли транзакционной системы (OLTP), фиксируя факт хозяйственной операции, а регистры накопления и бухгалтерии — в роли аналитического хранилища (OLAP). Для быстрой выборки данных аналитика должна быть максимально подготовленной и сохраненной.

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

Почему прямые расчетные данные (Блок Б) обязательно нужно хранить в базе?

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

Во-первых, это проблема финансовой точности и округления (так называемая «проблема копеек»). Финансовые расчеты в бухгалтерском и налоговом учете требуют строгого соответствия законодательству и первичным бумажным документам. Рассмотрим простой математический пример:

Допустим, мы продаем 3 единицы товара на общую сумму 10 рублей. Если мы попытаемся рассчитать цену за единицу, мы получим периодическую дробь: 10 / 3 = 3.333333.... В базе данных цена будет округлена до двух знаков после запятой и составит 3.33 рубля. Если бы мы не хранили итоговую сумму в базе, а рассчитывали ее динамически при открытии документа или формировании отчета, система выполнила бы расчет: 3.33 * 3 = 9.99. В итоге мы теряем одну копейку, баланс не сходится, а налоговая отчетность становится некорректной. Хранение рассчитанной и округленной суммы (10.00 рублей) гарантирует стабильность учета вне зависимости от округления цены.

Во-вторых, это историческая неизменность данных. Документ фиксирует коммерческие условия сделки на конкретный момент времени. Правила расчета скидок, налоговые ставки и алгоритмы ценообразования могут меняться разработчиками многократно в течение года. Если мы изменим формулу расчета скидки в конфигурации, все исторические документы (созданные месяц или год назад) при динамическом расчете изменят свои итоговые суммы. Это недопустимо. Сумма, зафиксированная в документе продажи, должна оставаться железобетонно неизменной. Единственный способ гарантировать это — записать результат расчета в базу данных в момент проведения документа.

В-третьих, это удобство и скорость построения запросов. Любой отчет в 1С (на базе Системы компоновки данных) должен работать максимально быстро. Если СУБД потребуется выполнять сложные математические расчеты с плавающей точкой в теле каждого запроса, производительность упадет в геометрической прогрессии. Считывание готового значения из индексированного поля базы данных происходит практически мгновенно.

Косвенные расчетные данные (Блок В) — почему их хранение вредно?

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

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

  1. Зависимость от других строк: значение Блока В для конкретной строки жестко привязано к состоянию всех остальных строк документа. Если пользователь добавляет, удаляет или изменяет хотя бы одну строку в табличной части, нам придется программно пересчитывать и перезаписывать показатели Блока В во всех остальных строках документа. Это приводит к избыточной нагрузке на сервер приложений и СУБД в процессе интерактивной работы пользователя.
  2. Проблема блокировок и транзакций: при записи документа СУБД блокирует таблицы. Чем больше объем записываемых данных и чем дольше выполняются предварительные расчеты внутри транзакции записи, тем выше вероятность возникновения взаимных блокировок (Deadlocks) в многопользовательском режиме.
  3. Бесполезность для аналитики: доля строки в документе крайне редко используется для сквозного многомерного анализа в отчетах. Как правило, этот показатель носит исключительно информационный характер для оператора, заполняющего документ здесь и сейчас.

Как же правильно работать с Блоком В? Для этого в платформе 1С:Предприятие существуют два стандартных механизма:

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

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

Особый случай: расчет и хранение себестоимости

Себестоимость (входящая в Блок А как нормативный показатель и в Блок Б как сумма себестоимости строки) заслуживает отдельного глубокого рассмотрения. В современных типовых конфигурациях 1С (таких как 1С:ERP, 1С:Комплексная автоматизация или 1С:Управление торговлей) разработчики полностью отказались от хранения себестоимости в самом документе продажи (например, в документе РеализацияТоваровУслуг).

Рассмотрим, почему это было сделано:

  1. Нарушение хронологической последовательности: в реальном бизнесе документы практически никогда не вводятся в систему в строгом хронологическом порядке. Корректировки поступлений, возвраты, исправления ошибок прошлых периодов происходят постоянно. Если рассчитывать и записывать себестоимость непосредственно в документ продажи в момент его проведения, то любое перепроведение документов задним числом приведет к искажению финансового результата.
  2. Метод отложенного расчета: современная методология учета в 1С предполагает разделение оперативного проведения документов и расчета финансового результата. При проведении продажи система делает движения по регистрам только в количественном выражении (или по предварительной себестоимости). Точный расчет себестоимости выполняется один раз в конце отчетного периода (месяца) регламентным документом РасчетСебестоимостиТоваров.
  3. Использование регистров накопления: рассчитанная себестоимость записывается не в табличную часть документа продажи, а в специализированный регистр накопления (например, ВыручкаИСебестоимостьПродаж). Документ продажи выступает лишь в роли регистратора сделки, а вся тяжелая аналитика хранится и обрабатывается в специализированных регистрах.

Реализация расчета сумм на практике: как организовать код

Для того чтобы избежать дублирования алгоритмов расчета сумм в различных модулях системы (на форме документа, при загрузке из внешних источников через API, при групповой обработке), необходимо выносить всю расчетную логику в модуль объекта или специализированные общие модули.

Проанализируем пример правильной организации кода в платформе 1С. Создадим в модуле объекта документа процедуру расчета сумм строки табличной части:


// Процедура рассчитывает зависимые показатели строки табличной части
Процедура РассчитатьСуммуСтрокиТабличнойЧасти(СтрокаТЧ) Экспорт
    
    // Рассчитываем сумму без скидки
    СуммаБезСкидки = СтрокаТЧ.Количество * СтрокаТЧ.Цена;
    
    // Рассчитываем сумму скидки с округлением до копеек
    СтрокаТЧ.СуммаСкидки = Окр(СуммаБезСкидки * (СтрокаТЧ.ПроцентСкидки / 100), 2);
    
    // Конечная сумма строки, подлежащая сохранению в БД
    СтрокаТЧ.Сумма = СуммаБезСкидки - СтрокаТЧ.СуммаСкидки;
    
КонецПроцедуры

Теперь обратим внимание на то, как этот код вызывается на форме документа. Для обеспечения мгновенной обратной связи пользователю при изменении количества или цены мы вызываем этот расчет на клиенте через серверный контекст формы:


&НаКлиенте
Процедура ТоварыКоличествоПриИзменении(Элемент)
    
    ТекущаяСтрока = Элементы.Товары.ТекущиеДанные;
    Если ТекущаяСтрока <> Неопределено Тогда
        // Вызываем расчет на сервере без контекста для экономии трафика
        РассчитатьСуммуНаСервере(ТекущаяСтрока.НомерСтроки - 1);
    КонецЕсли;
    
КонецПроцедуры

&НаСервереБезКонтекста
Процедура РассчитатьСуммуНаСервере(ИндексСтроки)
    // Обрабатываем данные через объект документа
    // Этот метод гарантирует идентичность расчета при интерактивном вводе и программном заполнении
КонецПроцедуры

При программном заполнении документа из внешних систем (например, при чтении файлов Excel или через HTTP-сервисы) разработчику достаточно заполнить первичные реквизиты (Блок А) и вызвать метод объекта:


НовыйДокумент = Документы.РеализацияТоваровУслуг.СоздатьДокумент();
// ... Заполнение шапки документа ...

Для Каждого СтрокаДанных Из ВнешниеДанные Цикл
    НоваяСтрока = НовыйДокумент.Товары.Добавить();
    ЗаполнитьЗначенияСвойств(НоваяСтрока, СтрокаДанных); // Заполняем Блок А
    
    // Рассчитываем Блок Б программно перед записью
    НовыйДокумент.РассчитатьСуммуСтрокиТабличнойЧасти(НоваяСтрока);
КонецЦикла;

НовыйДокумент.Записать(РежимЗаписиДокумента.Проведение);

Этот подход позволяет строго соблюдать принцип единственной ответственности (Single Responsibility Principle) и гарантирует, что формулы расчета сумм и скидок будут находиться в одном месте, исключая рассинхронизацию данных в базе.

Резюме: чеклист для проектирования структуры базы данных в 1С

Подводя итог нашей дискуссии, сформулируем краткие и понятные правила для разработчиков по организации хранения данных:

← На главную