Как устранить дублирование строк в динамическом списке при работе с дополнительными сведениями?

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

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

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

Причина возникновения проблемы

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

Способ 1. Использование группировки и агрегатных функций

Это самый популярный и надежный метод «схлопывания» строк. Идея заключается в том, чтобы сгруппировать результат по ссылке объекта, а значения свойств распределить по колонкам с помощью конструкции ВЫБОР и агрегатной функции МАКСИМУМ. Рассмотрим пример запроса, где нам нужно вывести три свойства: "Распечатано", "Выдано на склад" и "Отдано клиенту".

Разберем по шагам создание такого запроса:


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

Почему это работает:

  1. Конструкция ВЫБОР проверяет конкретный вид свойства. Если текущая запись регистра соответствует нужному свойству, берется его значение, иначе — NULL.
  2. Функция МАКСИМУМ среди всех значений (значение свойства и куча NULL) выбирает именно заполненное значение.
  3. СГРУППИРОВАТЬ ПО объединяет все записи по одному объекту в одну единственную строку.

Способ 2. Несколько левых соединений

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


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

Этот метод часто оказывается производительнее группировки на больших таблицах, так как СУБД проще оптимизировать соединение по конкретному значению индекса (Объект + Свойство). Однако он менее удобен, если свойств много (запрос становится "громоздким").

Способ 3. Штатный механизм характеристик

Платформа 1С предоставляет мощный инструмент под названием "Характеристики". Если правильно настроить его в метаданных (План видов характеристик + Регистр сведений), то в динамическом списке дополнительные сведения появятся автоматически как доступные поля.

Посмотрим на алгоритм настройки:

  1. В объекте метаданных (например, документе) на закладке "Характеристики" укажите таблицу видов характеристик и таблицу значений (регистр доп. сведений).
  2. В настройках динамического списка на форме в конфигураторе проверьте, что характеристики подтянулись.
  3. Теперь пользователь может самостоятельно добавить эти поля через "Изменить форму", а разработчик — вытащить их программно.

Преимущество этого способа в том, что платформа сама формирует оптимальный запрос и гарантирует отсутствие дублей строк.

Способ 4. Обработка данных в событии ПриПолученииДанныхНаСервере

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

В этом случае мы можем поступить следующим образом:

  1. Добавляем в динамический список текстовую колонку, не связанную с данными запроса (пустую).
  2. Используем обработчик события ПриПолученииДанныхНаСервере.
  3. Внутри обработчика получаем массив ссылок объектов, которые в данный момент отображаются на экране.
  4. Одним запросом получаем все нужные данные из регистра по этому массиву ссылок.
  5. Программно обходим коллекцию ОформленияСтрок и заполняем текстовую колонку сконкатенированными данными.

Рассмотрим пример кода для такого случая:


&НаСервере
Процедура СписокПриПолученииДанныхНаСервере(ИмяЭлемента, Настройки, Строки)
    
    МассивСсылок = Строки.ПолучитьКлючи();
    // Выполняем запрос к регистру по массиву ссылок
    Запрос = Новый Запрос;
    Запрос.Текст = "..."; // Запрос для получения данных
    Запрос.УстановитьПараметр("МассивСсылок", МассивСсылок);
    
    Результат = Запрос.Выполнить().Выбрать();
    
    // Заполняем данные в строках оформления
    Для каждого СтрокаСписка Из Строки Цикл
        // Логика поиска данных и установки текста в колонку
        СтрокаСписка.Значение.Данные["ИмяКолонки"] = "СформированнаяСтрока";
    КонецЦикла;
    
КонецПроцедуры

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

Рекомендации по оптимизации

Независимо от выбранного способа, помните о следующих правилах:

Проанализировав все варианты, мы видим, что для простых случаев лучше всего подходит Способ 1 (Группировка) или Способ 3 (Характеристики). Они позволяют избежать дублирования строк и корректно вывести все необходимые данные объекта в одну строку динамического списка. При необходимости можно изучить руководство по различным способам добавления полей в динамические списки.

← На главную