При работе с динамическими списками в 1С:Предприятие 8.3 программисты часто сталкиваются с неприятной ситуацией: форма списка открывается без ошибок, данные отображаются корректно, но при попытке прокрутить список вниз или применить отбор возникает критическая ошибка: «Обнаружено дублирование ключевых значений в колонках: "Ссылка"». Рассмотрим подробнее, почему это происходит и как правильно изменить запрос, чтобы избавиться от этой проблемы раз и навсегда.
Динамический список (ДС) работает на базе Системы компоновки данных (СКД). Его главная особенность — порционное считывание данных. Для того чтобы система могла корректно отображать строки, кэшировать их и сопоставлять при прокрутке, ей необходим уникальный ключ для каждой строки. По умолчанию в качестве такого ключа используется поле Ссылка (основная таблица списка).
Проблема заключается в том, что ваш запрос на уровне базы данных (SQL) может возвращать несколько строк с одинаковым значением Ссылка. Даже если при запуске консоли запросов вам кажется, что дублей нет, они могут появиться при определенных условиях (например, когда у одного заказа появляется две оплаты или две реализации). Как только СКД при прокрутке обнаруживает в выборке две разные строки с одним и тем же идентификатором Ссылка, она выдает ошибку, так как не понимает, какую именно строку нужно отобразить в данной позиции списка.
Давайте разберем типичный пример запроса, который приводит к подобной ошибке (аналогичный приведенному в обсуждении темы). Рассмотрим фрагмент соединений:
ЛЕВОЕ СОЕДИНЕНИЕ Документ.ПоступлениеБезналичныхДенежныхСредств.РасшифровкаПлатежа КАК ПБДС
ПО ДокументЗаказКлиента.Ссылка = ПБДС.ОснованиеПлатежа И (ПБДС.Ссылка.Проведен)
ЛЕВОЕ СОЕДИНЕНИЕ Документ.РеализацияТоваровУслуг КАК РеализацияТоваровУслуг
ПО ДокументЗаказКлиента.Ссылка = РеализацияТоваровУслуг.ЗаказКлиента И (РеализацияТоваровУслуг.Ссылка.Проведен)
В чем здесь скрытая угроза? Проанализируем связи:
РасшифровкаПлатежа — это связь «один ко многим». Если один ЗаказКлиента указан в нескольких строках одного платежного поручения или оплачен несколькими разными документами, запрос вернет столько строк, сколько найдено совпадений в табличной части. Поле Ссылка заказа при этом продублируется.РеализацияТоваровУслуг. Если по заказу сделано две частичные отгрузки, в результате запроса появятся две строки на один заказ.Важно понимать: использование ВЫБРАТЬ РАЗЛИЧНЫЕ помогает только в том случае, если все поля в выбираемых строках идентичны. Если же в одной строке сумма оплаты 100 рублей, а во второй 200, то строки будут считаться различными, и Ссылка заказа все равно продублируется.
Самый правильный и надежный способ — гарантировать, что для каждой ссылки основного документа будет возвращена ровно одна строка. Для этого мы должны использовать СГРУППИРОВАТЬ ПО. Рассмотрим, как переписать блок с оплатами и реализациями:
ВЫБРАТЬ
ДокументЗаказКлиента.Ссылка КАК Ссылка,
МАКСИМУМ(ЕСТЬNULL(ПБДС.Ссылка.Проведен, ЛОЖЬ)) КАК ЕстьОплата,
СУММА(ЕСТЬNULL(ПБДС.Сумма, 0)) КАК СуммаОплат,
МАКСИМУМ(ЕСТЬNULL(РеализацияТоваровУслуг.Проведен, ЛОЖЬ)) КАК Отгружен
ИЗ
Документ.ЗаказКлиента КАК ДокументЗаказКлиента
ЛЕВОЕ СОЕДИНЕНИЕ Документ.ПоступлениеБезналичныхДенежныхСредств.РасшифровкаПлатежа КАК ПБДС
ПО ДокументЗаказКлиента.Ссылка = ПБДС.ОснованиеПлатежа И ПБДС.Ссылка.Проведен
ЛЕВОЕ СОЕДИНЕНИЕ Документ.РеализацияТоваровУслуг КАК РеализацияТоваровУслуг
ПО ДокументЗаказКлиента.Ссылка = РеализацияТоваровУслуг.ЗаказКлиента И РеализацияТоваровУслуг.Проведен
СГРУППИРОВАТЬ ПО
ДокументЗаказКлиента.Ссылка
В данном примере мы используем МАКСИМУМ для булевых полей (если есть хоть одна проведенная оплата, значение будет ИСТИНА) и СУММА для числовых показателей. Теперь на каждый заказ приходится ровно одна строка, и ошибка в динамическом списке пропадет.
Если нам нужно просто проверить наличие факта (например, «Оплачено» или «Отгружено»), эффективнее использовать коррелированные подзапросы или оператор В (ВЫБРАТЬ...). Это избавит запрос от тяжелых соединений «один ко многим».
Посмотрим на пример проверки отгрузки:
ВЫБРАТЬ
Заказ.Ссылка,
ВЫБОР
КОГДА Заказ.Ссылка В (ВЫБРАТЬ РАЗЛИЧНЫЕ Т.ЗаказКлиента ИЗ Документ.РеализацияТоваровУслуг КАК Т ГДЕ Т.Проведен)
ТОГДА ИСТИНА
ИНАЧЕ ЛОЖЬ
КОНЕЦ КАК Отгружен
ИЗ
Документ.ЗаказКлиента КАК Заказ
Такой подход работает быстрее в динамических списках, так как база данных оптимизирует проверку существования записи (EXISTS) лучше, чем полное соединение таблиц.
Соединение с дополнительными реквизитами в динамическом списке — ресурсоемкая задача. Чтобы избежать дублей, проанализируем ситуацию: нам нужно получить значение конкретного свойства. Вместо соединения с табличной частью документа напрямую, лучше использовать соединение с фильтром по конкретному свойству:
ЛЕВОЕ СОЕДИНЕНИЕ Документ.ЗаказКлиента.ДополнительныеРеквизиты КАК ДопРеквизит
ПО ДокументЗаказКлиента.Ссылка = ДопРеквизит.Ссылка
И ДопРеквизит.Свойство = &КонкретноеСвойствоСсылка
Обратите внимание, что лучше передавать &КонкретноеСвойствоСсылка как параметр (ссылку на элемент справочника ДополнительныеСведенияИРеквизиты), а не искать по наименованию ("Результат отправлен на почту"). Поиск по строке ("Наименование") крайне медленный и менее надежный.
Помимо исправления самого текста запроса, обратим внимание на настройки в конфигураторе (или программно в расширении):
ОсновнаяТаблица (например, Документ.ЗаказКлиента). Это позволяет платформе автоматически подтягивать стандартные команды и правильно определять ключевое поле.ВЫБРАТЬ РАЗЛИЧНЫЕ. Если вы увидите в результате две строки с одинаковым номером заказа — это и есть причина вашей ошибки.Резюме: Если вы столкнулись с ошибкой дублирования ключей в ДС, первым делом ищите в тексте запроса ЛЕВОЕ СОЕДИНЕНИЕ с табличными частями или другими документами. Заменяйте их на группировки (GROUP BY) или проверки существования через подзапросы. Помните, что для динамического списка поле Ссылка должно быть абсолютно уникальным в рамках результирующей таблицы.