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

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

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

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

---

Почему возникает эта ошибка? Выясняем причину

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

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

Давайте проанализируем реальный запрос, из-за которого возникла проблема у автора темы на форуме:


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

Обратим внимание на левое соединение с виртуальной таблицей остатков и оборотов регистра ЗапасыНаСкладах. В группировку запроса добавлено поле ЗапасыНаСкладахОстаткиИОбороты.Характеристика.

Если в базе данных по одной и той же партии были сделаны движения с разными характеристиками номенклатуры (например, пользователи ошибочно или намеренно оприходовали товар по одной партии, но указали разные характеристики), левое соединение вернет две строки для одной партии. Из-за наличия разных характеристик группировка не схлопнет эти строки в одну, и в результирующей выборке появятся две записи с одинаковым значением в колонке Ссылка. Платформа обнаружит этот дубль и заблокирует отображение списка.

---

Вариант решения №1: Агрегирование данных в запросе

Если пользователям не требуется видеть каждую характеристику отдельной строкой в списке партий, самым простым и правильным решением будет свернуть дублирующие строки с помощью агрегатных функций СУБД.

Для этого нам необходимо убрать проблемные поля из секции СГРУППИРОВАТЬ ПО и обернуть их в агрегатные функции в секции ВЫБРАТЬ.

Разберем по шагам, как изменить запрос:

  1. Найдем в списке выбора поля, которые вызывают размножение строк. В нашем примере это ЗапасыНаСкладахОстаткиИОбороты.Характеристика и поле файлов НаличиеФайлов.ЕстьФайлы.
  2. В секции ВЫБРАТЬ применим к ним функции МАКСИМУМ:

// Вместо:
// ЗапасыНаСкладахОстаткиИОбороты.Характеристика КАК Характеристика,
// Пишем:
МАКСИМУМ(ЗапасыНаСкладахОстаткиИОбороты.Характеристика) КАК Характеристика,
  1. Уберем эти поля из блока СГРУППИРОВАТЬ ПО. После этого СУБД принудительно объединит все записи с одинаковой ссылкой в одну строку, выбрав максимальное значение характеристики (или пустое значение, если движений не было). Ошибка исчезнет.

---

Вариант решения №2: Настройка составного ключа строки (Платформа 8.3.14+)

Что делать, если пользователям жизненно важно видеть одну и ту же партию в разрезе разных характеристик отдельными строками в списке? Начиная с версии платформы 1С 8.3.14, мы можем научить динамический список работать с неуникальными ссылками с помощью настройки составного ключа строки.

Выполним следующие действия в конфигураторе:

  1. Откроем свойства динамического списка на форме.
  2. Полностью очистим свойство «Основная таблица». Помните: если это свойство заполнено, платформа жестко привязывается к уникальности поля Ссылка основной таблицы и игнорирует ручные настройки ключей.
  3. Установим свойство «Вид ключа» в значение ВидКлючаДинамическогоСписка.КлючСтроки.
  4. В свойстве «Поля ключа» укажем массив колонок, совокупность которых гарантирует уникальность каждой строки. В нашем случае это будут два поля: Ссылка и Характеристика.

Важный нюанс: поля, которые входят в составной ключ строки, никогда не должны принимать значения NULL или Неопределено. Если у нас используется левое соединение, и характеристика у партии может отсутствовать, обязательно обработайте это поле в запросе через ЕСТЬNULL:


ЕСТЬNULL(ЗапасыНаСкладахОстаткиИОбороты.Характеристика, ЗНАЧЕНИЕ(Справочник.ХарактеристикиНоменклатуры.ПустаяСсылка)) КАК Характеристика

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

---

Вариант решения №3: Оптимальный вывод через событие «ПриПолученииДанныхНаСервере»

Использование тяжелых левых соединений с регистрами остатков непосредственно в запросе динамического списка — это классическая ошибка, которая сильно снижает производительность системы. Базе данных приходится выполнять сложные соединения для огромного массива записей при каждой прокрутке списка.

Гораздо более профессиональное решение — убрать левые соединения из запроса списка и получать дополнительную информацию (наличие файлов, характеристики, остатки) динамически, только для тех строк, которые реально отображаются на экране пользователя (обычно это 30-40 строк).

Разберем, как это реализовать:

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

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

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

---

Алгоритм быстрой локализации проблемы (поиск битых данных)

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

Выполним следующий алгоритм локализации:

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

ВЫБРАТЬ
    ВТ_Результат.Ссылка КАК ПроблемнаяСсылка,
    КОЛИЧЕСТВО(1) КАК КоличествоДублей
ПОМЕСТИТЬ ВТ_Дубли
ИЗ
    (&ТекстЗапросаДинамическогоСписка) КАК ВТ_Результат
СГРУППИРОВАТЬ ПО
    ВТ_Результат.Ссылка
ИМЕЮЩИЕ
    КОЛИЧЕСТВО(1) > 1
;
ВЫБРАТЬ
    ВТ_Дубли.ПроблемнаяСсылка,
    ВТ_Дубли.КоличествоДублей
ИЗ
    ВТ_Дубли КАК ВТ_Дубли

Запустив этот запрос, мы мгновенно получим список конкретных документов или элементов справочников (партий), из-за которых ломается отображение формы. Далее разработчик или администратор может зайти в эти объекты и исправить неверно заполненные реквизиты или движения.

← На главную