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