При разработке в среде 1С программисты часто сталкиваются с ситуацией: результат выполнения запроса выгружается в объект ТаблицаЗначений через метод Выгрузить(), но при проверке свойств колонок оказывается, что каждая из них имеет составной тип данных, содержащий NULL (а зачастую и Неопределено). Например, колонка с числом получает тип Число, NULL, а ссылка на справочник превращается в СправочникСсылка.Номенклатура, NULL.
Посмотрим на классический пример такого кода:
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| ВнутренниеЗаказыОбороты.Номенклатура КАК Номенклатура,
| ВнутренниеЗаказыОбороты.КоличествоПриход КАК Количество
|ИЗ
| РегистрНакопления.ВнутренниеЗаказы.Обороты(&НачДата, &КонДата, , Заказчик = &Склад) КАК ВнутренниеЗаказыОбороты";
тзПотребности = Запрос.Выполнить().Выгрузить();
ЭлементыФормы.тпПотребности.СоздатьКолонки();
Разберем детально, почему так происходит, почему языковые конструкции запросов не решают эту задачу и какими способами можно получить чистую, строго типизированную таблицу значений.
Автоматическое добавление типа NULL в описание типов колонок — это фундаментальная архитектурная особенность метода Выгрузить() в платформе 1С:Предприятие. При формировании результирующей таблицы платформа не проводит глубокий семантический анализ логики выборки на предмет того, могут ли в строках фактически оказаться пустые значения (например, из-за левых соединений или группировок). Она перестраховывается и автоматически добавляет тип NULL во все поля.
Проверим это поведение на элементарном запросе:
Запрос = Новый Запрос;
Запрос.Текст = "ВЫБРАТЬ 1 КАК Поле";
Таблица = Запрос.Выполнить().Выгрузить();
Сообщить(Таблица.Колонки.Поле.ТипЗначения); // В окне сообщений получим "Число, NULL"
Важно понимать, что конструкции языка запросов ВЫРАЗИТЬ(...) и ЕСТЬNULL(...) работают исключительно на уровне значений конкретных строк и преобразуют данные, но никак не влияют на метаописание ОписаниеТипов колонки в итоговой таблице значений при использовании Выгрузить().
Во многих сценариях наличие составного типа с NULL никак не мешает работе. Однако возникают критические ситуации, когда строгость типов обязательна:
ПостроительЗапроса или ПостроительОтчета: если передать таблицу значений с составным типом NULL в качестве источника данных, фильтрация и наложение условий могут завершаться ошибками.Так как платформа не позволяет напрямую перезаписать или изменить свойство ТипЗначения у уже существующей колонки таблицы значений, рассмотрим рабочие способы решения этой задачи.
Самый надежный и универсальный метод — программно создать новую таблицу значений, скопировать в нее состав колонок, удалив из их описания тип NULL, и перенести данные. Напишем вспомогательную функцию:
Функция ПолучитьТЗБезТипаNULL(ИсходнаяТЗ)
РезультатТЗ = Новый ТаблицаЗначений;
Для Каждого Колонка Из ИсходнаяТЗ.Колонки Цикл
МассивТипов = Новый Массив;
Для Каждого ТекущийТип Из Колонка.ТипЗначения.Типы() Цикл
// Исключаем тип NULL и Неопределено при необходимости
Если ТекущийТип <> Тип("NULL") И ТекущийТип <> Тип("Неопределено") Тогда
МассивТипов.Добавить(ТекущийТип);
КонецЕсли;
КонецЦикла;
НовоеОписаниеТипов = Новый ОписаниеТипов(МассивТипов);
РезультатТЗ.Колонки.Добавить(Колонка.Имя, НовоеОписаниеТипов, Колонка.Заголовок, Колонка.Ширина);
КонецЦикла;
// Переносим строки из исходной таблицы
Для Каждого СтрокаИсходная Из ИсходнаяТЗ Цикл
ЗаполнитьЗначенияСвойств(РезультатТЗ.Добавить(), СтрокаИсходная);
КонецЦикла;
Возврат РезультатТЗ;
КонецФункции
Применим эту функцию к исходному примеру:
тзПотребности = Запрос.Выполнить().Выгрузить();
тзБезNull = ПолучитьТЗБезТипаNULL(тзПотребности);
// Привязываем к табличному полю уже очищенную таблицу
ЭлементыФормы.тпПотребности.Значение = тзБезNull;
ЭлементыФормы.тпПотребности.СоздатьКолонки();
Если структура таблицы заранее известна и колонок немного, эффективнее отказаться от метода Выгрузить() и обойти результат запроса через выборку, заполняя заранее подготовленную таблицу с жестко заданной типизацией:
// Создаем структуру таблицы значений вручную
тзПотребности = Новый ТаблицаЗначений;
ТипНоменклатура = Новый ОписаниеТипов("СправочникСсылка.Номенклатура");
ТипКоличество = Новый ОписаниеТипов("Число", Новый КвалификаторыЧисла(15, 3));
ТипЕдиница = Новый ОписаниеТипов("СправочникСсылка.КлассификаторЕдиницИзмерения");
тзПотребности.Колонки.Добавить("Номенклатура", ТипНоменклатура, "Номенклатура");
тзПотребности.Колонки.Добавить("Количество", ТипКоличество, "Количество");
тзПотребности.Колонки.Добавить("ЕдиницаИзмерения", ТипЕдиница, "Единица измерения");
// Выполняем запрос и обходим выборку
Выборка = Запрос.Выполнить().Выбрать();
Пока Выборка.Следующий() Цикл
НоваяСтрока = тзПотребности.Добавить();
ЗаполнитьЗначенияСвойств(НоваяСтрока, Выборка);
КонецЦикла;
Этот подход гарантирует отсутствие нежелательных типов в метаданных колонок и дает полный контроль над квалификаторами чисел и строк.
Если необходимость избавиться от типа NULL вызвана тем, что выгруженную таблицу планируется фильтровать через ПостроительЗапроса, лучшим решением будет передача исходного текста запроса напрямую в построитель. В этом случае не создаются промежуточные объекты в оперативной памяти, а фильтрация выполняется средствами СУБД:
Построитель = Новый ПостроительЗапроса;
Построитель.Текст = Запрос.Текст;
// Устанавливаем параметры запроса в построитель
Построитель.Параметры.Вставить("НачДата", НачДата);
Построитель.Параметры.Вставить("КонДата", КонДата);
Построитель.Параметры.Вставить("Склад", Склад);
// Выполняем построитель
Построитель.Выполнить();
РезультатТаблица = Построитель.Результат.Выгрузить();
Таким образом, для исключения составного типа NULL достаточно либо пересоздать колонки с новым объектом ОписаниеТипов, либо формировать таблицу вручную, минуя стандартный механизм Выгрузить().