Как вывести данные из табличной части в динамический список 1С и избежать ошибки 'Недопустимое значение'?

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

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

Анализ исходного кода и причин ошибки

Рассмотрим типичную ситуацию. Программист пытается в расширении добавить в форму списка справочника колонку с адресом электронной почты, который хранится в табличной части КонтактнаяИнформация. Для этого пишется следующий код:

В процедуре ПриСозданииНаСервере программно добавляется поле:


&НаСервере
Процедура Расш1_ПриСозданииНаСервереПосле(Отказ, СтандартнаяОбработка)
    // ... прочий код настройки списка ...
    
    НовыйЭлемент = Элементы.Вставить("ПочтАдрес", Тип("ПолеФормы"), Элементы.Список, Элементы.КодФИАС);
    НовыйЭлемент.Вид = ВидПоляФормы.ПолеНадписи;
    НовыйЭлемент.ПутьКДанным = "Список.КонтактнаяИнформация.АдресЭП"; // <- Здесь возникает ошибка
    НовыйЭлемент.Заголовок = "Почт. адрес";
КонецПроцедуры

А текст запроса для динамического списка формируется так:


Функция ТекстЗапросаСписка()
    Возврат
    "ВЫБРАТЬ
    |    КВП_Здания.Ссылка КАК Ссылка,
    |    КВП_Здания.Наименование КАК Наименование,
    |    // ... другие поля ...
    |    КВП_Здания.КонтактнаяИнформация.(
    |        АдресЭП = (ВЫРАЗИТЬ("""" КАК СТРОКА(100)))
    |    ) КАК КонтактнаяИнформация
    |ИЗ
    |    Справочник.КВП_Здания КАК КВП_Здания";
КонецФункции

Проблема кроется именно в том, как формируется запрос. Конструкция Таблица.(Поле) в языке запросов 1С не создает простое поле, доступное по пути Список.ИмяТаблицы.ИмяПоля. Вместо этого она создает в результате запроса поле типа ТаблицаЗначений. Динамический список не умеет напрямую обращаться к данным внутри такой вложенной таблицы через ПутьКДанным. Это и является прямой причиной ошибки "Недопустимое значение".

Попытка исправить ситуацию через простое соединение с табличной частью также приводит к проблемам:


ВЫБРАТЬ
    // ... поля ...
    КВП_ЗданияКонтактнаяИнформация.АдресЭП КАК АдресЭП
ИЗ
    Справочник.КВП_Здания КАК КВП_Здания
        ЛЕВОЕ СОЕДИНЕНИЕ Справочник.КВП_Здания.КонтактнаяИнформация КАК КВП_ЗданияКонтактнаяИнформация
        ПО КВП_Здания.Ссылка = КВП_ЗданияКонтактнаяИнформация.Ссылка

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

Теперь рассмотрим два правильных подхода к решению этой задачи.

Решение 1: Агрегация данных на уровне запроса

Это наиболее правильный и производительный метод, если вам нужно вывести только одно значение из табличной части (например, только юридический адрес или только один email). Суть в том, чтобы "схлопнуть" данные из табличной части до одной строки для каждого элемента справочника еще на уровне запроса, используя вложенный запрос.

Посмотрим на пример. Нам нужно для каждого здания вывести один, самый первый, почтовый адрес из его контактной информации.

  1. Создаем вложенный запрос, который выберет из табличной части КонтактнаяИнформация по одной записи для каждого здания. Мы будем отбирать нужный нам вид информации (например, "Адрес электронной почты") и использовать конструкцию ПЕРВЫЕ 1, чтобы гарантировать одну строку.
  2. Соединяем результат этого вложенного запроса с основной таблицей справочника через ЛЕВОЕ СОЕДИНЕНИЕ.

Так будет выглядеть итоговый текст запроса для динамического списка:


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

Что здесь важно:

После изменения запроса путь к данным для новой колонки станет простым и корректным: НовыйЭлемент.ПутьКДанным = "Список.АдресЭП";.

Решение 2: Использование события `СписокПриПолученииДанныхНаСервере`

Этот метод идеально подходит, когда логика получения данных сложна. Например, если нужно объединить несколько адресов или телефонов в одну строку через запятую. Этот подход не усложняет основной запрос списка и позволяет выполнить практически любые манипуляции с данными.

Разберем по шагам:

  1. Подготовка формы. В процедуре ПриСозданииНаСервере мы так же программно добавляем новую колонку, но, что критически важно, мы не указываем для нее свойство ПутьКДанным.
    
    &НаСервере
    Процедура Расш1_ПриСозданииНаСервереПосле(Отказ, СтандартнаяОбработка)
        // ...
        НовыйЭлемент = Элементы.Вставить("ПочтАдрес", Тип("ПолеФормы"), Элементы.Список);
        НовыйЭлемент.Вид = ВидПоляФормы.ПолеНадписи;
        НовыйЭлемент.Заголовок = "Почт. адрес";
        // Свойство ПутьКДанным не задаем!
    КонецПроцедуры
    
  2. Обработка события. Создаем обработчик события динамического списка ПриПолученииДанныхНаСервере. Это событие срабатывает каждый раз, когда список получает порцию данных для отображения на экране.
  3. Получение и обработка данных. Внутри обработчика мы выполняем отдельный запрос, чтобы получить контактную информацию только для тех строк, которые сейчас отображаются в списке. Затем мы обрабатываем результат и заполняем нашу новую колонку вручную.

Пример реализации обработчика:


&НаСервереБезКонтекста
Процедура СписокПриПолученииДанныхНаСервере(ИмяСписка, Настройки, Строки)

    МассивСсылок = Строки.ПолучитьКлючи();
    Если МассивСсылок.Количество() = 0 Тогда
        Возврат;
    КонецЕсли;
    
    Запрос = Новый Запрос;
    Запрос.Текст =
        "ВЫБРАТЬ
        |    КИ.Ссылка КАК Ссылка,
        |    КИ.Представление КАК АдресЭП
        |ИЗ
        |    Справочник.КВП_Здания.КонтактнаяИнформация КАК КИ
        |ГДЕ
        |    КИ.Ссылка В(&МассивСсылок)
        |    И КИ.Вид = &ВидАдресаЭП";
    
    Запрос.УстановитьПараметр("МассивСсылок", МассивСсылок);
    Запрос.УстановитьПараметр("ВидАдресаЭП", ПланыВидовХарактеристик.ВидыКонтактнойИнформации.EmailКонтрагента); // Пример

    Результат = Запрос.Выполнить().Выгрузить();
    
    // Используем Соответствие для быстрого поиска и объединения адресов
    ДанныеАдресов = Новый Соответствие;
    Для Каждого Стр Из Результат Цикл
        // Эта логика объединит несколько email одного здания в одну строку
        Если ДанныеАдресов[Стр.Ссылка] = Неопределено Тогда
            ДанныеАдресов.Вставить(Стр.Ссылка, Стр.АдресЭП);
        Иначе
            ДанныеАдресов[Стр.Ссылка] = ДанныеАдресов[Стр.Ссылка] + "; " + Стр.АдресЭП;
        КонецЕсли;
    КонецЦикла;
    
    // Заполняем ячейки в нашей новой колонке
    Для Каждого КлючИЗначение Из Строки Цикл
        Адрес = ДанныеАдресов[КлючИЗначение.Ключ];
        Если Адрес <> Неопределено Тогда
            КлючИЗначение.Значение.Ячейки.ПочтАдрес.УстановитьТекст(Адрес);
        КонецЕсли;
    КонецЦикла;

КонецПроцедуры

Этот метод дает максимальную гибкость. Он не "тормозит" основной запрос списка и выполняется только для видимых на экране строк, что эффективно расходует ресурсы сервера.

В итоге, выбор между двумя подходами зависит от задачи. Для простого вывода одного значения лучше использовать агрегацию в запросе — это элегантнее и производительнее. Для сложных случаев, требующих обработки и объединения нескольких значений, идеальным решением будет событие ПриПолученииДанныхНаСервере.

← На главную