Как исправить ошибку "Обнаружено дублирование ключевых значений" в динамическом списке 1С

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

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

Проанализируем причину ошибки

Рассмотрим ситуацию по шагам. Ошибка дублирования в динамическом списке возникает из-за жесткого требования платформы: если для списка задана Основная таблица (например, Справочник.Номенклатура), то платформа ожидает строго одну строку выборки для каждой уникальной ссылки (ключа).

Когда мы выполняем запрос вида:


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

Мы предполагаем, что СрезПоследних вернет ровно одну цену для каждой номенклатуры. Однако на практике это часто не так. Виртуальная таблица может вернуть несколько строк на одну ссылку, если:

  1. В регистре есть дополнительные измерения, которые мы не учли в условии соединения. Например, Характеристика, Упаковка или Организация. Если для товара заведено пять характеристик с разными ценами, запрос вернет пять строк для одной номенклатуры.
  2. В базе присутствуют "грязные данные", например, записи с одинаковым периодом вплоть до секунды.

В результате на одну ссылку Ном.Ссылка генерируется несколько строк, и форма списка "падает" с ошибкой.

Решение 1: Учет всех измерений регистра

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


// Добавляем условие по характеристике в параметры виртуальной таблицы
ЛЕВОЕ СОЕДИНЕНИЕ РегистрСведений.ЦеныНоменклатуры.СрезПоследних(&Период, 
    ТипЦены = ЗНАЧЕНИЕ(Перечисление.ТипыЦен.Розничная) 
    И Характеристика = ЗНАЧЕНИЕ(Справочник.ХарактеристикиНоменклатуры.ПустаяСсылка)
) КАК Цены 

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

Решение 2: Использование подзапроса (Рекомендуемый вариант)

Если мы не можем гарантировать уникальность данных регистра по измерениям, самый надежный и "чистый" с точки зрения модели данных способ — это предварительно сгруппировать цены в подзапросе, а затем уже соединять результат со справочником. Таким образом мы гарантируем связь 1 к 1.

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


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

Этот метод предпочтителен: мы явно фиксируем кардинальность и не ломаемся при расширении измерений регистра в будущем.

Решение 3: Группировка основного запроса

Посмотрим на еще один работающий вариант через агрегатную функцию МАКСИМУМ. В этом случае мы группируем сам итоговый запрос по всем полям номенклатуры.


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

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

Альтернативное решение без изменения запроса

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

  1. Создаем на форме списка условное поле, например ЦенаОтображение (без привязки к реквизиту).
  2. Используем событие формы динамического списка ПриПолученииДанныхНаСервере.
  3. В обработчике этого события получаем массив ссылок, видимых на экране.
  4. Одним запросом к регистру получаем цены для этих ссылок и заполняем данные в коллекции строк.

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

Подведем итоги: При работе с левыми соединениями в динамических списках всегда контролируйте уникальность ключевого поля (ссылки). Если есть риск появления нескольких строк из присоединяемой таблицы, используйте группировки, подзапросы, либо выносите расчет данных в событие ПриПолученииДанныхНаСервере. И не забывайте проверять структуру регистров — чаще всего дубли появляются из-за неучтенных в запросе измерений.

← На главную