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

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

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

Выясним причину: почему «размножаются» строки?

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

Проанализируем ситуацию: в исходном запросе автора происходит ЛЕВОЕ СОЕДИНЕНИЕ со вложенными запросами к табличной части Документ.УниверсальныйСервис.Картриджи. Рассмотрим типичный сценарий:

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

Диагностика проблемы: как найти «виновника» дублей

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


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

Выполнив такой запрос в консоли, мы сразу увидим список ссылок, которые «раздваиваются». Это поможет нам сузить область поиска до конкретных документов или записей в базе данных.

Решение №1: Группировка во вложенных запросах

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

Для корректного получения последней даты обслуживания нам нужно группировать данные только по ключевому полю связи — оборудованию. Рассмотрим правильную структуру подзапроса:


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

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

Решение №2: Использование временных таблиц (Best Practice)

Профессиональная разработка на 1С предполагает отказ от сложных вложенных запросов в пользу временных таблиц. Это не только повышает читаемость кода, но и значительно ускоряет работу СУБД, так как оптимизатору проще построить план выполнения. Разберем, как переписать проблемный запрос с использованием МЕНЕДЖЕР ВРЕМЕННЫХ ТАБЛИЦ:


// 1. Сначала выбираем уникальные данные по обслуживанию во временную таблицу
ВЫБРАТЬ
    ТЧ.ОборудованиеКонтрагентов КАК ОборудованиеКонтрагентов,
    МАКСИМУМ(ТЧ.Ссылка.ДатаЗакрытия) КАК ДатаЗакрытия
ПОМЕСТИТЬ ВТ_ПоследнееТО
ИЗ
    Документ.УниверсальныйСервис.Картриджи КАК ТЧ
ГДЕ
    ТЧ.Ссылка.Проведен = ИСТИНА
СГРУППИРОВАТЬ ПО
    ТЧ.ОборудованиеКонтрагентов
;

// 2. Основной запрос соединяем с подготовленной временной таблицей
ВЫБРАТЬ
    Оборудование.Ссылка,
    Оборудование.Наименование,
    ЕСТЬNULL(ВТ.ДатаЗакрытия, ДАТАВРЕМЯ(1,1,1)) КАК ПоследняяДатаТО
ИЗ
    Справочник.пмОборудованиеКонтрагентов КАК Оборудование
    ЛЕВОЕ СОЕДИНЕНИЕ ВТ_ПоследнееТО КАК ВТ
    ПО Оборудование.Ссылка = ВТ.ОборудованиеКонтрагентов

Использование ПОМЕСТИТЬ позволяет нам быть уверенными, что в таблице ВТ_ПоследнееТО поле ОборудованиеКонтрагентов является уникальным. Следовательно, соединение с основной таблицей никогда не приведет к дублированию строк.

Дополнительные риски: Вычисления в полях выбора

В коде автора присутствуют сложные математические формулы, например расчет периодичности обслуживания: КоличествоЧасов / ЧасовВДень * (7 / ДнейВНеделю). Проанализируем ситуацию с точки зрения надежности:

Резюме для разработчика

Чтобы навсегда забыть об ошибке дублирования ключевых значений, следуйте этим правилам: Во-первых, при соединении с табличными частями документов всегда предварительно сворачивайте данные до уникального ключа связи. Во-вторых, отдавайте предпочтение временным таблицам — в них гораздо проще контролировать уникальность записей. В-третьих, если вы используете динамический список, убедитесь, что в поле «Основная таблица» указан именно тот объект, ссылки которого вы выбираете в качестве первого поля запроса.

← На главную