При работе с запросами в 1С:Предприятие программисты часто сталкиваются с особенностью платформы: если в запросе используется ЛЕВОЕ СОЕДИНЕНИЕ или обращение к реквизитам через точку (которые потенциально могут быть не заполнены), результирующая таблица значений после метода Выгрузить() получает колонки с составным типом данных. В этот составной тип платформа принудительно включает NULL.
Это становится серьезной проблемой при дальнейшей обработке данных, например, при сериализации таблицы в XML или JSON, передаче через Web-сервисы или XDTO-пакеты. Даже если вы используете функцию ЕСТЬNULL() в тексте запроса и фактически в данных нет ни одного пустого значения, ОписаниеТипов колонки всё равно будет содержать NULL. Разберем подробно, почему так происходит и как получить «чистую» таблицу значений.
Проанализируем поведение платформы. Метод РезультатЗапроса.Выгрузить() формирует структуру таблицы значений на основе метаданных полей запроса. Если система видит, что источник данных может вернуть NULL (из-за специфики соединений), она страхуется и расширяет тип колонки. Это заложено на уровне ядра платформы. Даже если ЕСТЬNULL(Поле, "") гарантирует строку, тип колонки в ТЗ будет Строка, NULL.
Рассмотрим несколько эффективных способов решения этой задачи.
Если вы уже получили таблицу значений методом Выгрузить(), мы можем «почистить» её колонки, пересоздав их ОписаниеТипов. Этот метод удобен, когда структура запроса сложная и нам не хочется описывать колонки вручную.
Разберем алгоритм действий:
NULL.ОписаниеТипов на основании старого, но исключаем из него ненужный тип.Рассмотрим пример кода, реализующий этот подход:
Процедура ОчиститьТаблицуОтТипаNull(ТаблицаЗначений) Экспорт
Для Каждого Колонка Из ТаблицаЗначений.Колонки Цикл
ТипКолонки = Колонка.ТипЗначения;
// Проверяем наличие типа NULL в описании типов колонки
Если ТипКолонки.СодержитТип(Тип("NULL")) Тогда
ИмяКолонки = Колонка.Имя;
Заголовок = Колонка.Заголовок;
// Создаем новое описание типов на основании текущего,
// передавая в параметре исключаемые типы строку "NULL"
НовоеОписаниеТипов = Новый ОписаниеТипов(ТипКолонки, , "NULL");
// Для изменения типа существующей колонки в ТЗ с данными
// обычно требуется пересоздать колонку.
// Чтобы не потерять данные, используем временное переименование.
ТаблицаЗначений.Колонки.Добавить("Временная_" + ИмяКолонки, НовоеОписаниеТипов, Заголовок);
// Копируем данные из старой колонки в новую
Для Каждого СтрокаТЗ Из ТаблицаЗначений Цикл
СтрокаТЗ["Временная_" + ИмяКолонки] = СтрокаТЗ[ИмяКолонки];
КонецЦикла;
// Удаляем старую колонку и переименовываем новую
ИндексКолонки = ТаблицаЗначений.Колонки.Индекс(Колонка);
ТаблицаЗначений.Колонки.Удалить(ИмяКолонки);
ТаблицаЗначений.Колонки["Временная_" + ИмяКолонки].Имя = ИмяКолонки;
КонецЕсли;
КонецЦикла;
КонецПроцедуры
Обратите внимание на конструктор Новый ОписаниеТипов(ТипКолонки, , "NULL"). Третий параметр этого конструктора как раз предназначен для указания типов, которые нужно исключить из исходного набора.
Этот способ считается более «чистым» и часто более производительным, так как мы не модифицируем таблицу после создания, а сразу заполняем её в нужном формате. Вместо метода Выгрузить() мы будем использовать обход выборки.
Выясним причину эффективности этого метода: при ручном добавлении колонок в таблицу значений через ТЗ.Колонки.Добавить("Имя", ОписаниеТипов), мы жестко фиксируем типы. При заполнении строки через ЗаполнитьЗначенияСвойств(), значения из выборки (где благодаря ЕСТЬNULL в запросе уже нет реальных NULL) будут помещены в колонки с соответствующими типами без их изменения.
Посмотрим на пример реализации:
// 1. Выполняем запрос
РезультатЗапроса = Запрос.Выполнить();
// 2. Создаем пустую ТЗ и описываем колонки без типа NULL
ТаблицаРезультат = Новый ТаблицаЗначений;
Для Каждого ПолеИзЗапроса Из РезультатЗапроса.Колонки Цикл
// Исключаем NULL из описания типов принудительно
ЧистыйТип = Новый ОписаниеТипов(ПолеИзЗапроса.ТипЗначения, , "NULL");
ТаблицаРезультат.Колонки.Добавить(ПолеИзЗапроса.Имя, ЧистыйТип, ПолеИзЗапроса.Заголовок);
КонецЦикла;
// 3. Заполняем таблицу данными из выборки
Выборка = РезультатЗапроса.Выбрать();
Пока Выборка.Следующий() Цикл
НоваяСтрока = ТаблицаРезультат.Добавить();
ЗаполнитьЗначенияСвойств(НоваяСтрока, Выборка);
КонецЦикла;
Этот подход гарантирует, что в ТаблицаРезультат типы колонок будут строго соответствовать вашим ожиданиям, что позволит избежать ошибок при последующей сериализации.
Проанализируем ситуацию, когда даже после удаления типа NULL ошибка сериализации (например, в XML) сохраняется. Выясним причину: в строковых данных могут присутствовать невидимые спецсимволы, которые запрещены стандартом XML 1.0.
Особое внимание стоит уделить символу с кодом 29 (Group Separator). Он часто попадает в базу данных при сканировании штрихкодов формата GS1-128 или Data Matrix. В интерфейсе 1С он никак не отображается, но при попытке записи в XML платформа выдаст ошибку, которую легко перепутать с ошибкой несоответствия типов.
Чтобы избежать подобных проблем, при заполнении таблицы данных рекомендуется очищать строки от управляющих символов:
Функция ОчиститьСтрокуОтСпецсимволов(ИсходнаяСтрока)
// Символ с кодом 29 часто встречается в штрихкодах
Результат = СтрЗаменить(ИсходнаяСтрока, Символ(29), "");
// При необходимости можно добавить замену других управляющих символов (0-31)
Возврат Результат;
КонецФункции
При обходе выборки (как в Способе 2) вы можете индивидуально обрабатывать каждое строковое поле перед помещением в таблицу, что решит проблему «грязных» данных еще на этапе формирования результата.
Мы рассмотрели два основных способа избавления от типа NULL в результатах запроса.
Помните, что использование ЕСТЬNULL() в запросе — это обязательный шаг для корректности данных, но для получения корректного ОписанияТипов в 1С требуются дополнительные программные манипуляции.