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