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