Почему после выгрузки запроса в таблицу значений колонки имеют составной тип с NULL и как это исправить?

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

При разработке в среде 1С программисты часто сталкиваются с ситуацией: результат выполнения запроса выгружается в объект ТаблицаЗначений через метод Выгрузить(), но при проверке свойств колонок оказывается, что каждая из них имеет составной тип данных, содержащий NULL (а зачастую и Неопределено). Например, колонка с числом получает тип Число, NULL, а ссылка на справочник превращается в СправочникСсылка.Номенклатура, NULL.

Посмотрим на классический пример такого кода:


Запрос = Новый Запрос;
Запрос.Текст = 
    "ВЫБРАТЬ
    |    ВнутренниеЗаказыОбороты.Номенклатура КАК Номенклатура,
    |    ВнутренниеЗаказыОбороты.КоличествоПриход КАК Количество
    |ИЗ
    |    РегистрНакопления.ВнутренниеЗаказы.Обороты(&НачДата, &КонДата, , Заказчик = &Склад) КАК ВнутренниеЗаказыОбороты";

тзПотребности = Запрос.Выполнить().Выгрузить();
ЭлементыФормы.тпПотребности.СоздатьКолонки();

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

Причина поведения платформы: почему появляется тип NULL

Автоматическое добавление типа NULL в описание типов колонок — это фундаментальная архитектурная особенность метода Выгрузить() в платформе 1С:Предприятие. При формировании результирующей таблицы платформа не проводит глубокий семантический анализ логики выборки на предмет того, могут ли в строках фактически оказаться пустые значения (например, из-за левых соединений или группировок). Она перестраховывается и автоматически добавляет тип NULL во все поля.

Проверим это поведение на элементарном запросе:


Запрос = Новый Запрос;
Запрос.Текст = "ВЫБРАТЬ 1 КАК Поле";
Таблица = Запрос.Выполнить().Выгрузить();
Сообщить(Таблица.Колонки.Поле.ТипЗначения); // В окне сообщений получим "Число, NULL"

Важно понимать, что конструкции языка запросов ВЫРАЗИТЬ(...) и ЕСТЬNULL(...) работают исключительно на уровне значений конкретных строк и преобразуют данные, но никак не влияют на метаописание ОписаниеТипов колонки в итоговой таблице значений при использовании Выгрузить().

В чем заключается проблема наличия типа NULL

Во многих сценариях наличие составного типа с NULL никак не мешает работе. Однако возникают критические ситуации, когда строгость типов обязательна:

  1. Работа с объектом ПостроительЗапроса или ПостроительОтчета: если передать таблицу значений с составным типом NULL в качестве источника данных, фильтрация и наложение условий могут завершаться ошибками.
  2. Передача таблицы в качестве параметра в другой запрос: типизация колонок временных таблиц при передаче через параметр требует строгого соответствия типов.
  3. Отображение в интерфейсе: генерация элементов управления и колонок табличных полей для составного типа может вызывать появление лишних кнопок выбора типа и некорректное поведение при редактировании ячеек.

Так как платформа не позволяет напрямую перезаписать или изменить свойство ТипЗначения у уже существующей колонки таблицы значений, рассмотрим рабочие способы решения этой задачи.

Способ 1. Универсальная функция пересоздания колонок без типа NULL

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


Функция ПолучитьТЗБезТипаNULL(ИсходнаяТЗ)
    
    РезультатТЗ = Новый ТаблицаЗначений;
    
    Для Каждого Колонка Из ИсходнаяТЗ.Колонки Цикл
        
        МассивТипов = Новый Массив;
        Для Каждого ТекущийТип Из Колонка.ТипЗначения.Типы() Цикл
            // Исключаем тип NULL и Неопределено при необходимости
            Если ТекущийТип <> Тип("NULL") И ТекущийТип <> Тип("Неопределено") Тогда
                МассивТипов.Добавить(ТекущийТип);
            КонецЕсли;
        КонецЦикла;
        
        НовоеОписаниеТипов = Новый ОписаниеТипов(МассивТипов);
        РезультатТЗ.Колонки.Добавить(Колонка.Имя, НовоеОписаниеТипов, Колонка.Заголовок, Колонка.Ширина);
        
    КонецЦикла;
    
    // Переносим строки из исходной таблицы
    Для Каждого СтрокаИсходная Из ИсходнаяТЗ Цикл
        ЗаполнитьЗначенияСвойств(РезультатТЗ.Добавить(), СтрокаИсходная);
    КонецЦикла;
    
    Возврат РезультатТЗ;
    
КонецФункции

Применим эту функцию к исходному примеру:


тзПотребности = Запрос.Выполнить().Выгрузить();
тзБезNull = ПолучитьТЗБезТипаNULL(тзПотребности);

// Привязываем к табличному полю уже очищенную таблицу
ЭлементыФормы.тпПотребности.Значение = тзБезNull;
ЭлементыФормы.тпПотребности.СоздатьКолонки();

Способ 2. Заполнение предварительно типизированной таблицы через выборку

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


// Создаем структуру таблицы значений вручную
тзПотребности = Новый ТаблицаЗначений;

ТипНоменклатура = Новый ОписаниеТипов("СправочникСсылка.Номенклатура");
ТипКоличество = Новый ОписаниеТипов("Число", Новый КвалификаторыЧисла(15, 3));
ТипЕдиница = Новый ОписаниеТипов("СправочникСсылка.КлассификаторЕдиницИзмерения");

тзПотребности.Колонки.Добавить("Номенклатура", ТипНоменклатура, "Номенклатура");
тзПотребности.Колонки.Добавить("Количество", ТипКоличество, "Количество");
тзПотребности.Колонки.Добавить("ЕдиницаИзмерения", ТипЕдиница, "Единица измерения");

// Выполняем запрос и обходим выборку
Выборка = Запрос.Выполнить().Выбрать();
Пока Выборка.Следующий() Цикл
    НоваяСтрока = тзПотребности.Добавить();
    ЗаполнитьЗначенияСвойств(НоваяСтрока, Выборка);
КонецЦикла;

Этот подход гарантирует отсутствие нежелательных типов в метаданных колонок и дает полный контроль над квалификаторами чисел и строк.

Способ 3. Передача запроса напрямую в ПостроительЗапроса (без промежуточной ТЗ)

Если необходимость избавиться от типа NULL вызвана тем, что выгруженную таблицу планируется фильтровать через ПостроительЗапроса, лучшим решением будет передача исходного текста запроса напрямую в построитель. В этом случае не создаются промежуточные объекты в оперативной памяти, а фильтрация выполняется средствами СУБД:


Построитель = Новый ПостроительЗапроса;
Построитель.Текст = Запрос.Текст;

// Устанавливаем параметры запроса в построитель
Построитель.Параметры.Вставить("НачДата", НачДата);
Построитель.Параметры.Вставить("КонДата", КонДата);
Построитель.Параметры.Вставить("Склад", Склад);

// Выполняем построитель
Построитель.Выполнить();
РезультатТаблица = Построитель.Результат.Выгрузить();

Таким образом, для исключения составного типа NULL достаточно либо пересоздать колонки с новым объектом ОписаниеТипов, либо формировать таблицу вручную, минуя стандартный механизм Выгрузить().

← На главную