В процессе разработки и сопровождения конфигураций на платформе 1С:Предприятие 8.3 программисты часто сталкиваются с системной ошибкой: «Обнаружено дублирование ключевых значений в колонках: Ссылка». Данная проблема обычно проявляется в управляемых формах при работе с динамическими списками документов или справочников. Давайте подробно разберем, почему возникает эта ошибка, проанализируем механизмы работы платформы и рассмотрим все возможные способы её решения — от простых и быстрых до профессиональных архитектурных подходов.
Для начала выясним причину такого поведения платформы. Динамический список (объект ДинамическийСписок) предназначен для порционного считывания данных из базы данных и отображения их на клиенте. Когда у динамического списка заполнено свойство «Основная таблица» (например, Документ.Упаковка), платформа накладывает жесткое ограничение: поле Ссылка (уникальный идентификатор записи) в результирующей выборке запроса должно быть абсолютно уникальным.
Уникальность ключевого поля необходима платформе для выполнения следующих задач:
Если в поисковом запросе динамического списка используется левое соединение (ЛЕВОЕ СОЕДИНЕНИЕ) основной таблицы с подчиненной (например, с виртуальной таблицей остатков регистра накопления), то при наличии у одного документа остатков на нескольких разных складах СУБД вернет несколько строк для одного и того же документа. Поле Ссылка продублируется ровно столько раз, на скольких складах числится товар. Как только платформа обнаружит дубли ключа в полученной выборке, она немедленно заблокирует отображение данных и выдаст ошибку дублирования ключевых значений.
Многие разработчики обращают внимание на то, что ошибка может вести себя хаотично: появляться при вводе параметров (например, даты), возникать только у определенных пользователей или пропадать после перезапуска сеанса. Разберем эти особенности подробнее:
Рассмотрим пошагово все доступные методы исправления данной ошибки.
Если нам необходимо выводить склад в динамическом списке, но при этом мы хотим избежать дублирования строк документов, мы можем сгруппировать результат запроса. Для полей документа мы применим стандартную группировку, а для поля склада — агрегатную функцию МАКСИМУМ или МИНИМУМ. В этом случае документ всегда будет выводиться в списке в единственном экземпляре, а в колонке склада отобразится одно из значений (например, склад с наибольшим наименованием).
Проанализируем пример такого запроса:
ВЫБРАТЬ
Упаковка.Ссылка КАК Ссылка,
Упаковка.НомерПачки КАК НомерПачки,
Упаковка.ЗаказХвосты КАК ЗаказХвосты,
Упаковка.ТолщинаЛиста КАК ТолщинаЛиста,
Упаковка.Размер1 КАК Размер1,
Упаковка.Размер2 КАК Размер2,
МАКСИМУМ(СкладПачкиОстатки.Склад) КАК Склад
ИЗ
Документ.Упаковка КАК Упаковка
ЛЕВОЕ СОЕДИНЕНИЕ РегистрНакопления.СкладПачки.Остатки(&Период, ) КАК СкладПачкиОстатки
ПО Упаковка.Ссылка = СкладПачкиОстатки.Пачка
СГРУППИРОВАТЬ ПО
Упаковка.Ссылка,
Упаковка.НомерПачки,
Упаковка.ЗаказХвосты,
Упаковка.ТолщинаЛиста,
Упаковка.Размер1,
Упаковка.Размер2
Важное примечание: Обратите внимание, что поле СкладПачкиОстатки.Склад ни в коем случае нельзя добавлять в секцию СГРУППИРОВАТЬ ПО. Если вы совершите эту популярную ошибку и сгруппируете результат по складу, СУБД все равно вернет отдельные строки для каждого склада, и дублирование ключей не исчезнет.
Часто после внедрения группировки разработчики сталкиваются со следующей проблемой: запрос успешно отрабатывает в консоли, но при открытии формы 1С выдает ошибку: «Поле не найдено "Склад.Наименование"». Выясним причину этой ошибки.
До внедрения группировки поле Склад было обычным ссылочным полем. Платформа 1С автоматически позволяет обращаться к его реквизитам «через точку» (например, Склад.Наименование или Склад.Код). Это обращение могло использоваться в:
Когда мы применили агрегатную функцию МАКСИМУМ(Склад), результирующее поле перестало быть прямой ссылкой в контексте компоновки данных динамического списка. Платформа больше не может динамически разыменовать это поле через точку, так как результат функции вычисляется на уровне СУБД, а метаданные списка теряют исходную связь.
Для исправления этой ошибки выполните следующие действия:
Склад.Наименование, удалите или перепишите их.
ВЫБРАТЬ
Упаковка.Ссылка КАК Ссылка,
МАКСИМУМ(СкладПачкиОстатки.Склад.Наименование) КАК СкладНаименование
ИЗ
Документ.Упаковка КАК Упаковка
...
Если для бизнес-процессов компании критически важно видеть документ в разрезе каждого склада отдельными строками прямо в списке, мы можем использовать механизм составного ключа. В этом случае динамический список будет идентифицировать каждую строку не по одной лишь ссылке документа, а по комбинации полей.
Рассмотрим, как настроить составной ключ:
Ссылка и Склад.После этой настройки платформа перестанет выдавать ошибку дублирования, так как комбинация полей «Документ + Склад» будет абсолютно уникальной для каждой строки списка. Однако учитывайте, что при позиционировании на таких строках стандартные команды (например, изменение документа) будут ссылаться на один и тот же физический документ.
Если форма динамического списка используется исключительно в режиме просмотра, отбора или подбора данных, и пользователям не требуется изменять документы непосредственно из этого списка, самым простым и радикальным решением будет очистка свойства «Основная таблица» в настройках динамического списка.
Как только мы убираем привязку к основной таблице, платформа 1С снимает требование уникальности поля Ссылка в выборке. Запрос будет возвращать абсолютно любые данные с любым количеством дублирующихся ссылок без каких-либо системных ошибок.
Минус данного подхода: список потеряет возможность динамического считывания порциями (данные будут выбираться полностью, что может снизить производительность на больших базах данных) и пропадет стандартная контекстная функциональность создания/удаления документов.
Для высоконагруженных и крупных систем самым профессиональным, производительным и безопасным решением является полный отказ от левых соединений с регистрами остатков в основном запросе динамического списка. Вместо этого мы вынесем получение остатков в событие формы ПриПолученииДанныхНаСервере.
Этот метод гарантирует максимальное быстродействие, так как запрос к регистру остатков будет выполняться исключительно для тех строк списка, которые физически отображаются на экране пользователя в текущий момент времени (обычно не более 30-40 строк), а дублирование ссылок в основном списке станет принципиально невозможным.
Рассмотрим пошаговую реализацию этого метода:
Шаг 1. Упростим запрос динамического списка, убрав из него левое соединение с регистром остатков. В списке оставим только поля документа и пустое типизированное поле для склада:
ВЫБРАТЬ
Упаковка.Ссылка КАК Ссылка,
Упаковка.НомерПачки КАК НомерПачки,
Упаковка.ЗаказХвосты КАК ЗаказХвосты,
ВЫРАЗИТЬ(НЕОПРЕДЕЛЕНО КАК Справочник.Склады) КАК Склад
ИЗ
Документ.Упаковка КАК Упаковка
Шаг 2. Настроим обработчик события динамического списка ПриПолученииДанныхНаСервере на форме списка:
&НаСервере
Процедура СписокПриПолученииДанныхНаСервере(ИмяОформления, ОформляемыеСтроки)
// Соберем массив уникальных ссылок документов, отображаемых на экране в данный момент
МассивСсылок = Новый Массив;
Для Каждого КлючИЗначение Из ОформляемыеСтроки Цикл
МассивСсылок.Добавить(КлючИЗначение.Ключ);
КонецЦикла;
Если МассивСсылок.Количество() = 0 Тогда
Возврат;
КонецЕсли;
// Получим актуальные остатки только по отображаемым документам
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| СкладПачкиОстатки.Пачка КАК Ссылка,
| СкладПачкиОстатки.Склад КАК Склад
|ИЗ
| РегистрНакопления.СкладПачки.Остатки(, Пачка В (&МассивСсылок)) КАК СкладПачкиОстатки";
Запрос.УстановитьПараметр("МассивСсылок", МассивСсылок);
ТаблицаОстатков = Запрос.Выполнить().Выгрузить();
// Заполним виртуальную колонку "Склад" в оформлении строк
Для Каждого КлючИЗначение Из ОформляемыеСтроки Цикл
ДокументСсылка = КлючИЗначение.Ключ;
ОформлениеСтроки = КлючИЗначение.Значение;
НайденныеСтроки = ТаблицаОстатков.НайтиСтроки(Новый Структура("Ссылка", ДокументСсылка));
Если НайденныеСтроки.Количество() > 0 Тогда
// Если остатки есть на нескольких складах, сгруппируем их в одну строку через запятую
СтрокаСкладов = "";
Для Каждого СтрокаОстатка Из НайденныеСтроки Цикл
СтрокаСкладов = СтрокаСкладов + ?(ЗначениеЗаполнено(СтрокаСкладов), ", ", "") + Строка(СтрокаОстатка.Склад);
КонецЦикла;
// Записываем итоговый текст в отображение ячейки на форме
ОформлениеСтроки.Данные["Склад"] = НайденныеСтроки[0].Склад; // Для корректного типа данных
ОформлениеСтроки.Ячейки.Склад.ОтображатьТекст = Истина;
ОформлениеСтроки.Ячейки.Склад.Текст = СтрокаСкладов;
КонецЕсли;
КонецЦикла;
КонецПроцедуры
Данное решение является наиболее производительным стандартом разработки под платформу 1С:Предприятие 8.3. Оно полностью нивелирует риск возникновения ошибок дублирования ключей и обеспечивает мгновенный отклик интерфейса даже при работе с миллионными таблицами БД.
Если вывод складов непосредственно в списке не является строго обязательным требованием эргономики, мы можем полностью убрать колонку остатков из динамического списка и вынести детальную информацию по складам на отдельную вспомогательную панель (например, в небольшую таблицу в нижней части формы).
Для этого мы задействуем событие списка ПриАктивизацииСтроки с подключением обработчика ожидания, чтобы избежать чрезмерной нагрузки на базу данных при быстрой прокрутке списка клавишами:
&НаКлиенте
Процедура СписокПриАктивизацииСтроки(Элемент)
// Отключаем предыдущий вызов обработчика ожидания
ОтключитьОбработчикСобытия("ПоказатьОстаткиНаСервере");
// Подключаем обработчик с задержкой 0.2 секунды
ПодключитьОбработчикСобытия("ПоказатьОстаткиНаСервере", 0.2, Истина);
КонецПроцедуры
&НаКлиенте
Процедура ПоказатьОстаткиНаСервере()
ТекущиеДанные = Элементы.Список.ТекущиеДанные;
Если ТекущиеДанные = Неопределено Тогда
ОстаткиПоСкладам.Очистить();
Возврат;
КонецЕсли;
// Вызываем серверный метод для заполнения таблицы остатков на форме
ПолучитьОстаткиПоПачкеНаСервере(ТекущиеДанные.Ссылка);
КонецПроцедуры
Этот интерактивный подход позволяет пользователям видеть детальную расшифровку по всем складам для выбранного документа, сохраняя при этом идеальную скорость работы и стабильность динамического списка без каких-либо системных сбоев.