В практике разработки на платформе 1С часто возникает задача построения отчета, который показывает движения и остатки товаров или денежных средств не в целом по регистру, а только по конкретным хозяйственным операциям. Например, когда из «товарооборота» нужно исключить внутренние перемещения, возвраты или списания, оставив только чистые закупки и продажи. Стандартные виртуальные таблицы регистра накопления (ОстаткиИОбороты) для этой цели не подходят напрямую, так как они рассчитывают остаток на основании всех движений, записанных в базу. Если мы просто наложим фильтр по регистратору в параметрах виртуальной таблицы, начальный остаток все равно будет учитывать «неугодные» документы, так как он рассчитан системой заранее.
В данной статье мы разберем несколько способов решения этой задачи: от ручного расчета в запросе до использования мощных инструментов Системы компоновки данных (СКД), а также проанализируем технические нюансы, влияющие на производительность.
Разберем ситуацию, когда нам необходимо получить данные напрямую из основной таблицы регистра. Поскольку виртуальные таблицы нам не помогают «очистить» остаток, мы будем суммировать обороты самостоятельно. Основная идея заключается в том, чтобы выбрать все движения нужных документов от начала времен и рассчитать остаток для каждой строки как сумму всех предшествующих оборотов.
Рассмотрим пошагово алгоритм реализации такого запроса:
РегистрНакопления.ТоварыНаСкладе. Здесь важно установить фильтр по типам документов через оператор ССЫЛКА или ТИПЗНАЧЕНИЯ.Пример программного кода для реализации этой логики:
// Шаг 1: Собираем обороты по нужным регистраторам
ВЫБРАТЬ
НАЧАЛОПЕРИОДА(Товары.Период, МЕСЯЦ) КАК Период,
Товары.Склад КАК Склад,
Товары.Номенклатура КАК Номенклатура,
СУММА(ВЫБОР КОГДА Товары.ВидДвижения = ЗНАЧЕНИЕ(ВидДвиженияНакопления.Приход) ТОГДА Товары.Количество ИНАЧЕ 0 КОНЕЦ) КАК КоличествоПриход,
СУММА(ВЫБОР КОГДА Товары.ВидДвижения = ЗНАЧЕНИЕ(ВидДвиженияНакопления.Расход) ТОГДА Товары.Количество ИНАЧЕ 0 КОНЕЦ) КАК КоличествоРасход,
СУММА(ВЫБОР КОГДА Товары.ВидДвижения = ЗНАЧЕНИЕ(ВидДвиженияНакопления.Приход) ТОГДА Товары.Количество ИНАЧЕ -Товары.Количество КОНЕЦ) КАК КоличествоОборот
ПОМЕСТИТЬ ВтВсеОбороты
ИЗ
РегистрНакопления.ТоварыНаСкладе КАК Товары
ГДЕ
(Товары.Регистратор ССЫЛКА Документ.ПриобретениеТоваровУслуг
ИЛИ Товары.Регистратор ССЫЛКА Документ.РеализацияТоваровУслуг)
СГРУППИРОВАТЬ ПО
НАЧАЛОПЕРИОДА(Товары.Период, МЕСЯЦ),
Товары.Склад,
Товары.Номенклатура;
////////////////////////////////////////////////////////////////
// Шаг 2: Расчет остатков через соединение
ВЫБРАТЬ
ТекОбороты.Период КАК Период,
ТекОбороты.Склад КАК Склад,
ТекОбороты.Номенклатура КАК Номенклатура,
ЕСТЬNULL(СУММА(ПрошлыеОбороты.КоличествоОборот), 0) КАК КоличествоНачальныйОстаток,
ТекОбороты.КоличествоПриход КАК КоличествоПриход,
ТекОбороты.КоличествоРасход КАК КоличествоРасход,
ЕСТЬNULL(СУММА(ПрошлыеОбороты.КоличествоОборот), 0) + ТекОбороты.КоличествоОборот КАК КоличествоКонечныйОстаток
ИЗ
ВтВсеОбороты КАК ТекОбороты
ЛЕВОЕ СОЕДИНЕНИЕ ВтВсеОбороты КАК ПрошлыеОбороты
ПО ТекОбороты.Период > ПрошлыеОбороты.Период
И ТекОбороты.Склад = ПрошлыеОбороты.Склад
И ТекОбороты.Номенклатура = ПрошлыеОбороты.Номенклатура
ГДЕ
ТекОбороты.Период МЕЖДУ &НачалоПериода И &КонецПериода
СГРУППИРОВАТЬ ПО
ТекОбороты.Период, ТекОбороты.Склад, ТекОбороты.Номенклатура, ТекОбороты.КоличествоОборот, ТекОбороты.КоличествоПриход, ТекОбороты.КоличествоРасход;
Важно: Этот метод довольно ресурсозатратный. При большом количестве записей соединение «многие ко многим» по условию ТекОбороты.Период > ПрошлыеОбороты.Период может привести к существенному замедлению работы базы данных. Проанализируем ситуацию: если в базе 1000 периодов, для последней записи СУБД придется обработать 1000 строк для расчета суммы, что дает квадратичную зависимость производительности.
Проанализируем более современный и эффективный подход. Если вы строите отчет на Системе компоновки данных, вам не нужно заставлять SQL-сервер выполнять сложные соединения. Платформа 1С имеет встроенные механизмы для вычисления итогов нарастающим итогом.
Рассмотрим алгоритм действий в СКД:
Период, Номенклатура, Склад и КоличествоОборот (где приход с плюсом, а расход с минусом).НачальныйОстаток пропишем выражение:
ВычислитьВыражение("Сумма(КоличествоОборот)", "Период", "Группировка", "Первая", "Предыдущая").КонечныйОстаток пропишем:
ВычислитьВыражение("Сумма(КоличествоОборот)", "Период", "Группировка", "Первая", "Текущая").Почему это лучше? В данном случае 1С просто перебирает результат запроса один раз и последовательно складывает числа в памяти. Это работает в десятки и сотни раз быстрее, чем самосоединение таблиц в запросе.
Иногда данных настолько много, что читать физическую таблицу «с начала времен» невозможно. В этом случае мы можем применить математическую хитрость. Разберем её по шагам:
Остатки. Этот остаток включает в себя все документы.Таким образом, мы получим виртуальный начальный остаток, как если бы «нежелательных» документов никогда не существовало, но при этом нам не пришлось сканировать миллионы записей по нужным нам ПТУ и РТУ за 10 лет.
Проанализируем важные детали, о которых часто забывают разработчики при работе с физическими таблицами регистра накопления:
1. Момент времени вместо Даты. Если в одну и ту же секунду произошло несколько движений (например, поступление и продажа), использование поля Период (дата) в запросах может привести к хаосу в остатках. Для корректной сортировки всегда используйте МоментВремени. В запросе к физической таблице это комбинация периода и ссылки на регистратор.
2. Индексация. При фильтрации по полю Регистратор, помните, что это составное поле. Поиск по нему может быть медленным. Если отчет строится часто, убедитесь, что в регистре стоят индексы по измерениям, которые вы используете в условии ГДЕ.
3. Проблема отрицательных остатков. Это методологический риск. Если вы исключаете из отчета документ «Перемещение», по которому товар пришел на склад, а документ «Реализация» (который вы оставили) этот товар продает — в отчете вы увидите отрицательный остаток. Выясним причину: физически товар был, но ваш отчет его «не видит». Чтобы избежать недоумения пользователей, рекомендуем добавлять техническую колонку «Прочие движения», где будут суммироваться все исключенные документы. Это позволит выйти на реальный конечный остаток, сохранив наглядность по основным операциям.
4. Разделение итогов. Если в конфигурации включен режим разделения итогов для регистра, прямой запрос к физической таблице может вернуть данные, которые еще не «утряслись» в итоговых таблицах СУБД. Однако для целей управленческого отчета за прошлые периоды это, как правило, не критично.
Подводя итог, можно сказать, что получение «виртуальных» остатков — задача выполнимая. Самым надежным и быстрым способом на текущий момент является использование СКД с функциями накопления итогов. Это сохраняет чистоту кода и обеспечивает высокую скорость формирования отчета даже на больших объемах данных.