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

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

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

Выясним причину ошибки

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

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

Способ 1. Соединение с фильтрацией по конкретному виду контактной информации

Если в форме списка требуется вывести один конкретный реквизит (например, только телефон контрагента), разберем, как ограничить соединение условием по виду контакта. Добавим проверку вида непосредственно в секцию связи таблиц:


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

Если нужно вывести и телефон, и адрес, соединяем таблицу контактной информации повторно с уникальным псевдонимом для каждого вида данных:


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

Способ 2. Использование сгруппированного вложенного запроса (гарантия уникальности)

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

Так как в динамических списках запрещено использовать пакетные запросы и временные таблицы (конструкция ПОМЕСТИТЬ не поддерживается), сгруппируем данные через вложенный подзапрос с агрегатной функцией МАКСИМУМ:


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

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

Способ 3. Получение данных в событии ПриПолученииДанныхНаСервере

Если сложные соединения во вложенных запросах существенно замедляют открывание списка или прокрутку журнала, получение дополнительных данных можно вынести из текста запроса в код модуля формы:

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

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

Важно: При использовании обработчика ПриПолученииДанныхНаСервере стандартный платформенный отбор и сортировка по вычисляемым колонкам будут недоступны, однако производительность отображения списка будет максимальной.

← На главную