Как вывести колонку «Склад» в справочнике Номенклатура без ошибки дублирования ключей

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

При разработке в среде 1С программисты часто сталкиваются с задачей отображения остатков или мест хранения непосредственно в форме списка справочника Номенклатура. Однако при попытке добавить поле «Склад» через запрос динамического списка возникает критическая ошибка: «Обнаружено дублирование ключевых значений в колонках ссылка». После этого список становится пустым.

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

Почему возникает ошибка дублирования?

Разберем первопричину проблемы. Динамический список в 1С (объект ДинамическийСписок) при установленном свойстве ОсновнаяТаблица (например, Справочник.Номенклатура) жестко контролирует уникальность ссылок. Система ожидает, что каждой строке в списке соответствует одна уникальная ссылка на объект базы данных.

Когда мы добавляем в запрос левое соединение с регистром остатков или таблицей складов, одна и та же номенклатура может присутствовать на нескольких складах. В результате выполнения SQL-запроса на уровне СУБД формируется несколько строк для одной ссылки Номенклатура. Платформа 1С, получив такой результат, не может сопоставить «размноженные» строки с основной таблицей и выдает ошибку дублирования ключей. Помните: динамический список — это не отчет, а инструмент для выбора и навигации по объектам.

Способ 1. Вывод детализации в отдельную таблицу (Master-Detail)

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

Рассмотрим пошаговую реализацию этого метода:

  1. Добавим на форму справочника Номенклатура новую таблицу (не динамический список, а обычную таблицу формы). Назовем ее СписокСкладов.
  2. Создадим реквизит формы с типом ТаблицаЗначений, содержащий колонки Склад и Остаток.
  3. Для основного динамического списка (Список) создадим обработчик события ПриАктивизацииСтроки.

Теперь реализуем программный код для связи таблиц:


&НаКлиенте
Процедура СписокПриАктивизацииСтроки(Элемент)
    
    ТекущаяСтрока = Элементы.Список.ТекущаяСтрока;
    
    Если ТекущаяСтрока <> Неопределено Тогда
        // Вызываем серверную процедуру для получения данных
        ОбновитьДанныеПоСкладам(ТекущаяСтрока);
    КонецЕсли;
    
КонецПроцедуры

&НаСервере
Процедура ОбновитьДанныеПоСкладам(СсылкаНаНоменклатуру)
    
    // Используем запрос к виртуальной таблице остатков
    Запрос = Новый Запрос;
    Запрос.Текст = 
    "ВЫБРАТЬ
    |	ТоварыНаСкладахОстатки.Склад КАК Склад,
    |	ТоварыНаСкладахОстатки.В НаличииОстаток КАК Остаток
    |ИЗ
    |	РегистрНакопления.ТоварыНаСкладах.Остатки(, Номенклатура = &Номенклатура) КАК ТоварыНаСкладахОстатки";
    
    Запрос.УстановитьПараметр("Номенклатура", СсылкаНаНоменклатуру);
    
    РезультатЗапроса = Запрос.Выполнить();
    // Загружаем результат в таблицу на форме
    ОбъектТаблица = РезультатЗапроса.Выгрузить();
    ЭтаФорма.СписокСкладов.Загрузить(ОбъектТаблица);
    
КонецПроцедуры

Этот метод позволяет избежать дублей и не замедляет прокрутку основного списка, так как тяжелые запросы выполняются только для одного выбранного товара.

Способ 2. Использование нескольких фиксированных колонок

Выясним ситуацию, когда заказчик хочет видеть остатки по конкретным, заранее известным складам (например, «Основной склад» и «Торговый зал»). В этом случае мы можем модифицировать запрос динамического списка, используя серию левых соединений с условием по конкретному складу.

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


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

Важный нюанс: Чтобы этот запрос не вызывал дублирования, условия по складу (&Склад1, &Склад2) должны быть переданы именно в параметры виртуальной таблицы остатков. Это гарантирует, что для каждой номенклатуры из регистра вернется не более одной записи на каждое соединение.

Способ 3. Отказ от «Основной таблицы»

Разберем радикальный способ решения проблемы. Если вы снимете флаг ОсновнаяТаблица в настройках динамического списка, платформа перестанет проверять уникальность ссылок. Это позволит выводить одну и ту же номенклатуру несколько раз (по строке на каждый склад).

Однако у этого метода есть существенные недостатки:

Способ 4. Агрегация данных в строку

Если задача стоит лишь «показать, на каких складах есть товар», можно использовать механизм агрегации. Поскольку в языке запросов 1С нет встроенной функции объединения строк, это можно реализовать через вычисляемые поля в Системе компоновки данных (СКД), на которой базируется динамический список.

Проанализируем алгоритм:

  1. В настройках динамического списка открываем вкладку «Вычисляемые поля».
  2. Создаем поле ВсеСклады.
  3. В выражении используем функцию общего модуля, которая по ссылке номенклатуры будет собирать строку названий складов через запятую.

Пример функции в общем модуле (с флагом Сервер):


Функция ПолучитьСписокСкладовСтрокой(НоменклатураСсылка) Экспорт
    
    Запрос = Новый Запрос;
    Запрос.Текст = "ВЫБРАТЬ РАЗЛИЧНЫЕ Склад.Наименование ИЗ РегистрНакопления.ТоварыНаСкладах.Остатки(, Номенклатура = &Номенклатура)";
    Запрос.УстановитьПараметр("Номенклатура", НоменклатураСсылка);
    
    Выборка = Запрос.Выполнить().Выбрать();
    Результат = "";
    
    Пока Выборка.Следующий() Цикл
        Результат = Результат + ?(Результат = "", "", ", ") + Выборка.Наименование;
    КонецЦикла;
    
    Возвращает Результат;
КонецФункции

Внимание: Использование функций в каждой строке списка может существенно замедлить работу интерфейса при быстрой прокрутке. Используйте этот метод только при небольшом количестве данных.

Рекомендации по оптимизации

Рассмотрим финальные советы, которые помогут избежать ошибок при работе с динамическими списками:

Таким образом, самым стабильным и «красивым» решением для 1С:Розница является Способ №1 (отдельная таблица остатков). Он полностью соответствует стандартам разработки 1С и не вызывает конфликтов с платформенными механизмами контроля уникальности ключей.

← На главную