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

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

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

Разберем причину возникновения ошибки

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


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

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

Решение 1: Использование параметров виртуальных таблиц

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

  1. В тексте запроса динамического списка добавим параметр в виртуальную таблицу: РегистрСведений.ЦеныНоменклатуры.СрезПоследних(&НаДату, ВидЦены = &ВидЦены).
  2. В коде формы, в процедуре ПриСозданииНаСервере, установим значение параметра по умолчанию:

&НаСервере
Процедура ПриСозданииНаСервере(Отказ, СтандартнаяОбработка)
    Список.Параметры.УстановитьЗначениеПараметра("ВидЦены", ПредопределенноеЗначение("Справочник.ВидыЦен.Розничная"));
    Список.Параметры.УстановитьЗначениеПараметра("НаДату", ТекущаяДата());
КонецПроцедуры

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

Решение 2: Агрегация и группировка данных

Если нам необходимо вывести, например, общий остаток по всем складам, а не детализацию, мы должны сгруппировать результат. Рассмотрим пример запроса с группировкой:


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

Использование СУММА для остатков или МАКСИМУМ для цен гарантирует, что на одну ссылку в итоговой таблице придется ровно одна строка данных. Это «железный» способ избежать ошибки дублирования, однако помните, что группировка в динамических списках на больших объемах данных может замедлять работу СУБД.

Решение 3: Программное заполнение данных через СписокПриПолученииДанныхНаСервере

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

Для этого:

  1. Убираем соединение с регистром цен из основного запроса динамического списка.
  2. Добавляем в список произвольную колонку Цена (не связанную с данными).
  3. Используем событие СписокПриПолученииДанныхНаСервере:

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

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

Почему не стоит использовать РАЗЛИЧНЫЕ и временные таблицы?

В процессе обсуждения часто звучат советы использовать ключевое слово РАЗЛИЧНЫЕ (DISTINCT) или ПОМЕСТИТЬ во временную таблицу. Выясним, почему в динамических списках это плохая практика:

Подведем итоги

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

← На главную