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

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

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

Глубинная причина возникновения ошибки

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

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

  1. Идентификация строк на клиенте — сервер должен однозначно понимать, на какую именно запись указывает курсор пользователя.
  2. Порционная подгрузка данных — при прокрутке списка СУБД делает выборку порциями, ориентируясь на уникальный ключ.
  3. Выполнение стандартных команд — открытие формы объекта, установка пометки на удаление, изменение статуса проведения документа.

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

Почему ошибка возникает нерегулярно и только у части пользователей?

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

  1. Влияние пользовательских настроек и отборов: Если у пользователя на форме установлен жесткий отбор по конкретному складу, запрос динамического списка выбирает остатки только по этому складу. В таком случае дублирование физически невозможно, и список открывается без ошибок. Если же отбор по складу сброшен, запрос начинает считывать остатки по всей компании, что приводит к появлению дублей и падению формы.
  2. Права доступа и механизм RLS (Row-Level Security): Разные права доступа могут напрямую влиять на воспроизведение ошибки. Например, кладовщик видит остатки только своего подразделения (благодаря настроенным ограничениям доступа на уровне записей), поэтому у него дубли не возникают. Администратор или менеджер с полными правами видит всю картину в целом — система возвращает дублирующиеся строки документов, вызывая падение интерфейса.
  3. Ввод параметров (например, даты): Когда пользователь начинает вводить дату в поле параметра, динамический список инициирует немедленное обновление данных на сервере. Если дата введена частично (например, только первые цифры дня), виртуальная таблица остатков может пересчитаться на некорректный период, зацепив избыточные архивные остатки на других складах, что мгновенно провоцирует появление дублирующих строк.

Рассмотрим пошагово все доступные методы исправления данной ошибки.

Решение 1. Группировка данных и использование агрегатных функций

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

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


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

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

Решение 2. Исправление ошибки «Поле не найдено "Склад.Наименование"» после группировки

Часто после внедрения группировки разработчики сталкиваются со следующей проблемой: запрос успешно отрабатывает в консоли, но при открытии формы 1С выдает ошибку: «Поле не найдено "Склад.Наименование"». Выясним причину этой ошибки.

До внедрения группировки поле Склад было обычным ссылочным полем. Платформа 1С автоматически позволяет обращаться к его реквизитам «через точку» (например, Склад.Наименование или Склад.Код). Это обращение могло использоваться в:

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

Для исправления этой ошибки выполните следующие действия:

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

ВЫБРАТЬ
    Упаковка.Ссылка КАК Ссылка,
    МАКСИМУМ(СкладПачкиОстатки.Склад.Наименование) КАК СкладНаименование
ИЗ
    Документ.Упаковка КАК Упаковка
    ...

Решение 3. Использование составного ключа динамического списка

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

Рассмотрим, как настроить составной ключ:

  1. Откройте свойства вашего динамического списка в конфигураторе.
  2. Перейдите к свойству «Ключ свойства» (или откройте настройки колонок динамического списка).
  3. Укажите в качестве ключевых полей одновременно два поля: Ссылка и Склад.

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

Решение 4. Отключение свойства «Основная таблица»

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

Как только мы убираем привязку к основной таблице, платформа 1С снимает требование уникальности поля Ссылка в выборке. Запрос будет возвращать абсолютно любые данные с любым количеством дублирующихся ссылок без каких-либо системных ошибок.

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

Решение 5 (Best Practice). Использование обработчика «ПриПолученииДанныхНаСервере»

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

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

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

Шаг 1. Упростим запрос динамического списка, убрав из него левое соединение с регистром остатков. В списке оставим только поля документа и пустое типизированное поле для склада:


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

Шаг 2. Настроим обработчик события динамического списка ПриПолученииДанныхНаСервере на форме списка:


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

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

Решение 6. Вынос остатков на отдельную панель формы

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

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


&НаКлиенте
Процедура СписокПриАктивизацииСтроки(Элемент)
    // Отключаем предыдущий вызов обработчика ожидания
    ОтключитьОбработчикСобытия("ПоказатьОстаткиНаСервере");
    // Подключаем обработчик с задержкой 0.2 секунды
    ПодключитьОбработчикСобытия("ПоказатьОстаткиНаСервере", 0.2, Истина);
КонецПроцедуры

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

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

← На главную