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

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

При разработке интерфейсов в 1С часто возникает задача вывести в форме списка справочника Номенклатура остатки товаров в разрезе нескольких складов в отдельных колонках. Если связать левым соединением справочник с виртуальной таблицей остатков регистров накопления и попытаться условно разнести остатки по складам через конструкцию ВЫБОР КОГДА, можно столкнуться с критической ошибкой: динамический список начинает сбоить или выдавать предупреждение о неуникальности ключа записи.

Рассмотрим по шагам, почему происходит эта ошибка и какие способы существуют для ее корректного решения в платформе 1С:Предприятие.

Выясним причину ошибки динамического списка

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

Когда одна и та же номенклатура имеет остатки одновременно на двух и более складах, виртуальная таблица РегистрНакопления.ОстаткиТовара.Остатки возвращает несколько строк для одной ссылки на номенклатуру. В результате левого соединения строка справочника Номенклатура дублируется. Платформа 1С не может однозначно идентифицировать строки списка по ссылке номенклатуры, из-за чего механизм считывания данных дает сбой.

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

Способ 1. Использование группировки и условной функции СУММА

Разберем наиболее популярное и прямое решение — использование секции СГРУППИРОВАТЬ ПО и агрегатного суммирования условных выражений. В отличие от возврата пустой строки или нулевых значений, конструкция СУММА(ВЫБОР КОГДА ... ИНАЧЕ 0 КОНЕЦ) корректно схлопывает записи по каждому товару.

Посмотрим на пример итогового запроса для динамического списка:


ВЫБРАТЬ РАЗРЕШЕННЫЕ
    Товары.Ссылка КАК Номенклатура,
    Товары.Наименование КАК Наименование,
    ЦеныЗакуп.Цена КАК Закуп,
    ЦеныПродажа.Цена КАК Продажа,
    СУММА(ВЫБОР
        КОГДА Остатки.Склад = &Склад1
            ТОГДА Остатки.ОстатокОстаток
        ИНАЧЕ 0
    КОНЕЦ) КАК Остаток1,
    СУММА(ВЫБОР
        КОГДА Остатки.Склад = &Склад2
            ТОГДА Остатки.ОстатокОстаток
        ИНАЧЕ 0
    КОНЕЦ) КАК Остаток2
ИЗ
    Справочник.Номенклатура КАК Товары
    ЛЕВОЕ СОЕДИНЕНИЕ РегистрНакопления.ОстаткиТовара.Остатки КАК Остатки
        ПО Остатки.Номенклатура = Товары.Ссылка
    ЛЕВОЕ СОЕДИНЕНИЕ РегистрСведений.ЦеныНоменклатурыЗакуп.СрезПоследних КАК ЦеныЗакуп
        ПО ЦеныЗакуп.Номенклатура = Товары.Ссылка
    ЛЕВОЕ СОЕДИНЕНИЕ РегистрСведений.ЦеныНоменклатурыПродажа.СрезПоследних КАК ЦеныПродажа
        ПО ЦеныПродажа.Номенклатура = Товары.Ссылка
СГРУППИРОВАТЬ ПО
    Товары.Ссылка,
    Товары.Наименование,
    ЦеныЗакуп.Цена,
    ЦеныПродажа.Цена

Важные нюансы этого подхода:

  1. В ветке ИНАЧЕ условной конструкции обязательно нужно возвращать численный 0, а не пустую строку "" или NULL, иначе функция СУММА не сможет обработать значение.
  2. В секцию СГРУППИРОВАТЬ ПО должны входить все выборки, не обернутые в агрегатные функции (ссылка номенклатуры, наименование, цены).

Способ 2. Использование вложенного запроса и объединения таблиц

Альтернативный подход заключается в предварительной подготовке таблицы остатков во вложенном запросе с использованием конструкции ОБЪЕДИНИТЬ ВСЕ и последующем соединении с основной таблицей. Этот метод бывает более удобен для чтения, когда логика получения остатков сложна.

Рассмотрим структуру такого запроса:


ВЫБРАТЬ
    Товары.Ссылка КАК Номенклатура,
    Товары.Наименование КАК Наименование,
    ЕСТЬNULL(СводныеОстатки.Остаток1, 0) КАК Остаток1,
    ЕСТЬNULL(СводныеОстатки.Остаток2, 0) КАК Остаток2
ИЗ
    Справочник.Номенклатура КАК Товары
    ЛЕВОЕ СОЕДИНЕНИЕ (
        ВЫБРАТЬ
            ВложенныйЗапрос.Номенклатура КАК Номенклатура,
            СУММА(ВложенныйЗапрос.Остаток1) КАК Остаток1,
            СУММА(ВложенныйЗапрос.Остаток2) КАК Остаток2
        ИЗ
            (ВЫБРАТЬ
                Остатки1.Номенклатура КАК Номенклатура,
                Остатки1.ОстатокОстаток КАК Остаток1,
                0 КАК Остаток2
            ИЗ
                РегистрНакопления.ОстаткиТовара.Остатки(, Склад = &Склад1) КАК Остатки1
            
            ОБЪЕДИНИТЬ ВСЕ
            
            ВЫБРАТЬ
                Остатки2.Номенклатура КАК Номенклатура,
                0,
                Остатки2.ОстатокОстаток
            ИЗ
                РегистрНакопления.ОстаткиТовара.Остатки(, Склад = &Склад2) КАК Остатки2) КАК ВложенныйЗапрос
        СГРУППИРОВАТЬ ПО
            ВложенныйЗапрос.Номенклатура
    ) КАК СводныеОстатки
        ПО СводныеОстатки.Номенклатура = Товары.Ссылка

Рекомендации по оптимизации и производительности

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

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