При работе с регистрами накопления в системе «1С:Предприятие 8» разработчики иногда сталкиваются с ситуацией, которая на первый взгляд кажется мистической: один и тот же запрос к виртуальной таблице Остатки выдает принципиально разные результаты в зависимости от того, какие поля выбраны для отображения. Рассмотрим ситуацию, когда при добавлении поля даты в отчет остатки «разваливаются» на приход и расход, а при его удалении — схлопываются в ноль.
Разберем детально, как устроены механизмы платформы, почему это происходит и как правильно проектировать регистры, чтобы избегать подобных проблем.
Прежде всего, важно понимать, что виртуальная таблица Остатки не существует в базе данных в физическом виде. Это программный механизм платформы 1С, который при выполнении запроса «на лету» генерирует сложную конструкцию на языке SQL для получения итоговых данных. Проанализируем, как именно платформа формирует этот запрос.
Когда мы обращаемся к виртуальной таблице, 1С выполняет следующие действия:
ВЫБРАТЬ вашего запроса. Все измерения регистра, которые вы в ней указали, автоматически становятся полями группировки в итоговом SQL-запросе (конструкция GROUP BY).КоличествоОстаток) суммируются в разрезе выбранных измерений.Рассмотрим на примере автора темы. В регистре есть три измерения: ДатаОкончание, ИзделиеКлиента и Контрагент. В базу записаны два движения:
Проанализируем ситуацию №1: в запросе выбрано поле ДатаОкончание. Платформа видит, что измерения различаются датой. СУБД получает две разные строки, так как комбинации «Вася-Ботинки-10.05» и «Вася-Ботинки-11.05» уникальны. В итоге мы видим +1 и -1.
Проанализируем ситуацию №2: поле ДатаОкончание исключено из запроса. Платформа 1С оптимизирует SQL-запрос и убирает это поле из группировки GROUP BY. Теперь для СУБД обе записи имеют одинаковый набор ключевых полей: «Вася-Ботинки». Система складывает ресурсы (+1 + (-1)) и получает 0. Это и есть стандартное поведение виртуальных таблиц: агрегация производится только по тем измерениям, которые указаны в запросе.
В приведенном примере ДатаОкончание является измерением регистра. Выясним причину, по которой опытные разработчики крайне не рекомендуют использовать дату (особенно со временем) в качестве измерения.
1. Раздувание таблицы итогов. Платформа 1С хранит итоги в разрезе каждой уникальной комбинации измерений. Если в качестве измерения используется дата, то каждая новая секунда или день создает новую запись в физической таблице итогов. Это приводит к экспоненциальному росту размера базы данных.
2. Падение производительности. При записи нового документа системе приходится пересчитывать итоги. Если измерений слишком много и они уникальны (как дата), индекс таблицы итогов становится огромным и фрагментированным. Запись документов начинает замедляться, а отчеты — «тормозить».
3. Ошибки в остатках. Как мы увидели выше, если в приходе и расходе даты в измерениях не совпадут хотя бы на секунду, товар никогда не «спишется» в ноль по этим измерениям, хотя общий остаток по номенклатуре может быть нулевым.
Решение: Если вам нужно хранить срок годности или дату окончания, лучше использовать их как Реквизит регистра, если по ним не требуется получать остатки в разрезе каждой даты. Если же разрез по датам критически необходим (например, для партионного учета по срокам), используйте тип Дата без времени (приведенную к началу дня) или специальный справочник.
Проанализируем ситуацию в контексте СКД, так как автор использовал именно её. У СКД есть механизм «Автозаполнение», который вносит свои коррективы в итоговый запрос.
В СКД запрос к виртуальной таблице может выглядеть так:
ВЫБРАТЬ
ОстаткиИзделийОстатки.Контрагент КАК Контрагент,
ОстаткиИзделийОстатки.ИзделиеКлиента КАК ИзделиеКлиента,
ОстаткиИзделийОстатки.КоличествоОстаток КАК КоличествоОстаток,
ОстаткиИзделийОстатки.ДатаОкончание КАК ДатаОкончание
ИЗ
РегистрНакопления.ОстаткиИзделий.Остатки КАК ОстаткиИзделийОстатки
Если пользователь в настройках отчета на вкладке «Выбранные поля» уберет галочку с поля ДатаОкончание, СКД (благодаря механизму компоновки) может полностью исключить это поле из SQL-запроса к базе данных. В этот момент и происходит «схлопывание» остатков. Это не ошибка СКД, а её интеллектуальная функция оптимизации: зачем нагружать сервер группировкой по полю, которое не нужно выводить?
Выясним еще один важный технический момент, влияющий на получение остатков. Посмотрим на два варианта написания запроса.
Вариант 1 (Медленный):
ВЫБРАТЬ
Остатки.КоличествоОстаток
ИЗ
РегистрНакопления.Товары.Остатки() КАК Остатки
ГДЕ
Остатки.Контрагент = &Контрагент
В этом случае система сначала получит из базы все остатки по всем контрагентам, сформирует временную таблицу, и только потом отфильтрует нужного «Васю». Это крайне неэффективно.
Вариант 2 (Правильный):
ВЫБРАТЬ
Остатки.КоличествоОстаток
ИЗ
РегистрНакопления.Товары.Остатки(, Контрагент = &Контрагент) КАК Остатки
Здесь фильтрация передается напрямую в параметры виртуальной таблицы. СУБД на самом низком уровне отберет только нужные записи из таблиц итогов. Скорость выполнения такого запроса будет в десятки раз выше.
Чтобы избежать «мистического» поведения остатков, придерживаемся следующих правил:
Остатки суммирует ресурсы только в разрезе полей, указанных в ВЫБРАТЬ.Остатки(...), а не в секции ГДЕ.Рассмотренная ситуация — отличный пример того, как важно понимать внутреннюю архитектуру 1С. Виртуальные таблицы — это мощный инструмент, но их работа напрямую зависит от того, как вы описываете структуру данных и составляете запросы.