В практике разработки на платформе 1С нередко возникает загадочная на первый взгляд ситуация: выполняется запрос с конструкцией ОБЪЕДИНИТЬ ВСЕ, объединяющий две временные таблицы. При проверке каждой таблицы по отдельности в них содержатся заполненные значения, но в итоговом результате объединения в некоторых строках внезапно появляются значения NULL.
Проанализируем природу этой проблемы, выясним, как платформа 1С и СУБД обрабатывают строковые данные и ссылки, и разберем корректные способы решения задачи.
Рассмотрим типовой сценарий, в котором проявляется такое поведение. В менеджере временных таблиц формируются две таблицы, поля которых на уровне кода 1С кажутся одинаковыми строками:
ВЫБРАТЬ
ВременнаяТаблица.Показатель,
ВременнаяТаблица.Сумма,
ВременнаяТаблица.СсылкаНаДокумент
ИЗ
ВременнаяТаблица1 КАК ВременнаяТаблица
ОБЪЕДИНИТЬ ВСЕ
ВЫБРАТЬ
ВременнаяТаблица.Показатель,
ВременнаяТаблица.Сумма,
ВременнаяТаблица.СсылкаНаДокумент
ИЗ
ВременнаяТаблица2 КАК ВременнаяТаблица
Если поле СсылкаНаДокумент в исходных временных таблицах было сформировано с помощью конструкции ПРЕДСТАВЛЕНИЕ(Ссылка) или ПРЕДСТАВЛЕНИЕССЫЛКИ(Ссылка), то во временную таблицу помещается вовсе не обычная примитивная строка.
Разберем внутренний механизм работы функции ПРЕДСТАВЛЕНИЕ() в платформе 1С:
ПРЕДСТАВЛЕНИЕ() не преобразует данные в тип NVARCHAR или VARCHAR на уровне СУБД в момент создания временной таблицы. Вместо этого платформа помещает в физическую таблицу СУБД набор служебных полей, необходимых для последующего вычисления строки при передаче результата клиенту._IDRRef и _Description._IDRRef, _Number и _Date_Time.Посмотрим, что происходит на уровне SQL при выполнении ОБЪЕДИНИТЬ ВСЕ (UNION ALL): СУБД сопоставляет колонки исключительно по их порядковым номерам. Если в одной временной таблице поле представления сформировано от одного типа документов, а во второй — от другого типа или справочника, физический состав и порядок скрытых служебных колонок не совпадают. В несовпадающие позиции СУБД естественным образом подставляет NULL.
Когда итоговый результат возвращается платформе 1С, она пытается сформировать строковое представление из полученных колонок. Обнаружив в наборе служебных полей значения NULL, механизм платформы возвращает результирующий NULL вместо ожидаемой строки.
Согласно стандартам разработки фирмы «1С», поля представлений (как поле Представление, так и функции ПРЕДСТАВЛЕНИЕ() / ПРЕДСТАВЛЕНИЕССЫЛКИ()) предназначены исключительно для отображения данных пользователю в самом внешнем запросе. Их запрещено использовать:
ПОМЕСТИТЬ).ГДЕ, ИМЕЮЩИЕ, ПО, ВЫБОР, СГРУППИРОВАТЬ ПО, УПОРЯДОЧИТЬ ПО.Игнорирование этих правил ведет к неопределенному поведению запросов, некорректным соединениям и неожиданным значениям NULL, особенно при выполнении процедур через внешние COM-соединения, где отсутствуют интерактивные клиентские механизмы кэширования представлений.
Наиболее правильный и производительный способ — помещать во временные таблицы исходные ссылки объектов, а функцию ПРЕДСТАВЛЕНИЕ() вызывать только в финальном запросе, который передает данные вызывающему коду.
Разберем пример реализации по шагам:
// 1. Формируем первую временную таблицу с исходной ссылкой
ВЫБРАТЬ
АвансовыйОтчет.Ссылка КАК ДокументСсылка,
АвансовыйОтчет.СуммаДокумента КАК Сумма
ПОМЕСТИТЬ ВТ_Первая
ИЗ
Документ.АвансовыйОтчет КАК АвансовыйОтчет
;
// 2. Формируем вторую временную таблицу с исходной ссылкой
ВЫБРАТЬ
ВозвратТоваров.Ссылка КАК ДокументСсылка,
ВозвратТоваров.СуммаДокумента КАК Сумма
ПОМЕСТИТЬ ВТ_Вторая
ИЗ
Документ.ВозвратТоваровПоставщику КАК ВозвратТоваров
;
// 3. Объединяем таблицы по ссылкам, а ПРЕДСТАВЛЕНИЕ получаем только на выходе
ВЫБРАТЬ
ПРЕДСТАВЛЕНИЕ(ВТ.ДокументСсылка) КАК ДокументПредставление,
ВТ.Сумма КАК Сумма
ИЗ
(ВЫБРАТЬ
ВТ_Первая.ДокументСсылка КАК ДокументСсылка,
ВТ_Первая.Сумма КАК Сумма
ИЗ
ВТ_Первая КАК ВТ_Первая
ОБЪЕДИНИТЬ ВСЕ
ВЫБРАТЬ
ВТ_Вторая.ДокументСсылка КАК ДокументСсылка,
ВТ_Вторая.Сумма КАК Сумма
ИЗ
ВТ_Вторая КАК ВТ_Вторая) КАК ВТ
В этом случае платформа объединяет ссылки единого базового типа, а строковые представления строит централизованно один раз на этапе выгрузки выборки.
Если логика алгоритма категорически требует наличия именно строковых полей уже внутри промежуточных временных таблиц, не используйте виртуальные функции представлений. Выбирайте конкретные реквизиты объектов и явно приводите их к строке фиксированной длины с помощью функции ВЫРАЗИТЬ().
Посмотрим на пример формирования строки для документов и справочников:
// Для справочника берем явное строковое наименование
ВЫБРАТЬ
ВЫРАЗИТЬ(Контрагенты.Наименование КАК СТРОКА(150)) КАК ПредставлениеОбъекта
ПОМЕСТИТЬ ВТ_Данные
ИЗ
Справочник.Контрагенты КАК Контрагенты
Если требуется объединить документы разных видов, можно собрать строку через операцию конкатенации номера и даты:
ВЫБРАТЬ
ВЫРАЗИТЬ("Авансовый отчет № " + Документ.Номер КАК СТРОКА(100)) КАК ДокументСтрокой
ПОМЕСТИТЬ ВТ_Документы
ИЗ
Документ.АвансовыйОтчет КАК Документ
Такой подход гарантирует, что в СУБД будет создана физическая колонка строго определенного строкового типа, и при выполнении ОБЪЕДИНИТЬ ВСЕ структуры колонок совпадут без образования NULL.
Когда требуется сложное системное представление (с учетом префиксов, номеров и логики событий модулей менеджеров ОбработкаПолученияПредставления), а получить его в запросе без ссылок невозможно, разделим процесс на два этапа:
ТаблицаЗначений.Строка(СтрокаТЗ.Ссылка).Проанализируем пример кода на встроенном языке 1С:
// Получаем данные с реальными ссылками
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| Документ.Ссылка КАК СсылкаНаДокумент,
| Документ.СуммаДокумента КАК Сумма
|ИЗ
| Документ.АвансовыйОтчет КАК Документ";
ТаблицаДанных = Запрос.Выполнить().Выгрузить();
// Добавляем строго типизированную строковую колонку
ТаблицаДанных.Колонки.Добавить("СсылкаСтрокой", Новый ОписаниеТипов("Строка",,,, Новый КвалификаторыСтроки(150)));
Для Каждого Стр Из ТаблицаДанных Цикл
Стр.СсылкаСтрокой = Строка(Стр.СсылкаНаДокумент);
КонецЦикла;
// Передаем таблицу значений обратно в запрос через временную таблицу
ЗапросИТД = Новый Запрос;
ЗапросИТД.МенеджерВременныхТаблиц = Новый МенеджерВременныхТаблиц;
ЗапросИТД.Параметры.Вставить("ТаблицаДанных", ТаблицаДанных);
ЗапросИТД.Текст =
"ВЫБРАТЬ
| ТЗ.СсылкаСтрокой КАК ПредставлениеДокумента,
| ТЗ.Сумма КАК Сумма
|ПОМЕСТИТЬ ВТ_ГотовыеДанные
|ИЗ
| &ТаблицаДанных КАК ТЗ";
ЗапросИТД.Выполнить();
Этот способ исключает зависимость от специфики разворачивания виртуальных полей платформы в СУБД и надежно работает при любых вариантах вызова, включая COM-соединения и фоновые задания.