Разберем типичную, но очень важную проблему, с которой часто сталкиваются начинающие разработчики 1С при написании сложных запросов с объединением данных. Представьте ситуацию: вам необходимо сформировать печатную форму или отчет по документу РеализацияТоваровУслуг, стянув в одну результирующую таблицу данные из двух принципиально разных табличных частей — «Товары» и «Возвратная тара». При этом в конфигурацию был добавлен пользовательский реквизит Густина (в переводе с украинского — «Плотность»), который присутствует в товарах, но отсутствует в таре. При попытке выполнить такой запрос платформа выдает критическую ошибку: «Поле не найдено (густина)».
В этой статье мы подробно проанализируем причины возникновения этой ошибки, разберем базовые правила работы оператора ОБЪЕДИНИТЬ ВСЕ, научимся правильно типизировать пустые значения с помощью конструкции ВЫРАЗИТЬ и рассмотрим наилучший способ оптимизации подобных запросов с использованием временных таблиц.
Для начала проанализируем исходную структуру запроса, с которой столкнулся автор темы. Запрос состоит из двух частей, соединенных оператором ОБЪЕДИНИТЬ ВСЕ. В первой части выбираются данные из вложенного запроса по табличной части Товары, а во второй — напрямую из табличной части ВозвратнаяТара.
Ошибка компиляции запроса происходит во второй части объединения:
|ОБЪЕДИНИТЬ ВСЕ
|
|ВЫБРАТЬ
| РеализацияТоваровУслуг.Номенклатура КАК Номенклатура,
| ...
| РеализацияТоваровУслуг.Густина КАК Густина, // Источник ошибки!
| ...
|ИЗ
| Документ.РеализацияТоваровУслуг.ВозвратнаяТара КАК РеализацияТоваровУслуг
Проблема заключается в том, что псевдоним РеализацияТоваровУслуг во втором запросе указывает на табличную часть ВозвратнаяТара. В метаданных этой табличной части (в отличие от ТЧ «Товары») реквизита Густина физически не существует. Платформа 1С пытается обратиться к свойству объекта базы данных, которого нет в схеме, и закономерно возвращает ошибку сборки запроса.
Попытка просто удалить это поле из списка выбора второго запроса также приведет к ошибке, так как нарушится базовое правило объединения таблиц.
При написании запросов с операторами объединения критически важно помнить о двух фундаментальных правилах платформы 1С:
ВЫБРАТЬ, а не по именам или псевдонимам. Это значит, что если в первом запросе поле Густина идет одиннадцатым по счету, то во втором запросе на одиннадцатом месте обязательно должно стоять поле-заглушка, соответствующее типу данных плотности.КАК ...) необязательно — они будут проигнорированы компилятором, хотя их написание считается хорошим тоном для читаемости кода.Таким образом, чтобы решить проблему, нам необходимо передать во второй части запроса пустое значение в той же самой позиции, где в первой части передается реальный реквизит Густина.
Рассмотрим несколько способов подстановки пустого значения во вторую часть объединения запроса.
Способ 1. Использование числового нуля (быстрое решение)
Самое простое решение, которое применил автор темы, — подставить обычный ноль в качестве заглушки:
| 0 КАК Густина,
Этот вариант работоспособен, но имеет логический изъян. Плотность возвратной тары (ящиков, поддонов) физически не может быть нулевой — ее просто не существует. С точки зрения чистоты данных, более корректно использовать пустое значение.
Способ 2. Использование значения NULL (неявная типизация)
Казалось бы, логично использовать специальное значение NULL, обозначающее отсутствие данных:
| NULL КАК Густина,
Однако здесь кроется серьезная архитектурная ловушка. Если в первом запросе поле Густина имеет тип Число, а во втором запросе мы пишем нетипизированный NULL, СУБД будет вынуждена создать для результирующей колонки составной тип данных (Число и NULL). Наличие составных типов в результатах запроса заставляет СУБД резервировать дополнительную память, усложняет построение планов выполнения запросов и снижает общую производительность системы.
Способ 3. Использование типизированного NULL через ВЫРАЗИТЬ (лучшая практика)
Чтобы избежать создания составного типа и сохранить максимальную скорость выполнения запроса, значение NULL необходимо принудительно привести к нужному типу данных. Делается это с помощью встроенной функции ВЫРАЗИТЬ. Предположим, что реквизит Густина имеет тип Число с длиной 15 и точностью 3 знака после запятой:
| ВЫРАЗИТЬ(NULL КАК ЧИСЛО(15, 3)) КАК Густина,
При таком подходе СУБД четко понимает, что данная колонка содержит строго числовые значения, и оптимизирует распределение памяти при выполнении запроса.
Обратим внимание на структуру исходного запроса автора темы. В нем используется конструкция (ВЫБРАТЬ ...) КАК ВложенныйЗапрос. Согласно стандартам разработки фирмы «1С», использование вложенных запросов в качестве источников данных для объединений или соединений крайне не рекомендуется.
При росте базы данных СУБД (будь то MS SQL Server или PostgreSQL) может некорректно построить план выполнения вложенного запроса, что приведет к сильным зависаниям при проведении документов или формировании отчетов. Правильный подход — предварительно поместить данные во временные таблицы.
Перепишем исходный запрос автора темы по стандартам разработки, исправив ошибку с реквизитом Густина и переведя логику на временные таблицы.
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| ТоварыДокумента.Номенклатура КАК Номенклатура,
| ТоварыДокумента.Коэффициент КАК Коэффициент,
| ТоварыДокумента.Густина КАК Густина,
| ТоварыДокумента.ЕдиницаИзмерения КАК ЕдиницаИзмерения,
| ТоварыДокумента.СтавкаНДС КАК СтавкаНДС,
| ТоварыДокумента.Цена * &Курс / &Кратность КАК Цена,
| ТоварыДокумента.Количество КАК Количество,
| ТоварыДокумента.Сумма * &Курс / &Кратность КАК Сумма,
| ТоварыДокумента.СуммаНДС * &Курс / &Кратность КАК СуммаНДС,
| ТоварыДокумента.НомерСтроки КАК НомерСтроки
|ПОМЕСТИТЬ ВТ_Товары
|ИЗ
| Документ.РеализацияТоваровУслуг.Товары КАК ТоварыДокумента
|ГДЕ
| ТоварыДокумента.Ссылка = &ТекущийДокумент
|;
|
|////////////////////////////////////////////////////////////////////////////////
|ВЫБРАТЬ
| ВТ_Товары.Номенклатура КАК Номенклатура,
| ВТ_Товары.Номенклатура.НаименованиеПолное КАК ТоварНаименование,
| ВТ_Товары.Номенклатура.НоменклатураГТД КАК НоменклатураГТД,
| ВТ_Товары.ЕдиницаИзмерения.Представление КАК БазоваяЕдиницаНаименование,
| ВТ_Товары.ЕдиницаИзмерения.Код КАК БазоваяЕдиницаКодПоОКЕИ,
| ВТ_Товары.ЕдиницаИзмерения КАК ЕдиницаИзмерения,
| ВТ_Товары.ЕдиницаИзмерения.НаименованиеПолное КАК ВидУпаковки,
| СУММА(ВТ_Товары.Количество) КАК Количество,
| ВТ_Товары.Коэффициент КАК Коэффициент,
| ВТ_Товары.Густина КАК Густина,
| ВЫБОР
| КОГДА ВТ_Товары.Коэффициент > 0
| ТОГДА ВТ_Товары.Коэффициент * 1
| ИНАЧЕ СУММА(ВТ_Товары.Количество) * ВТ_Товары.Коэффициент
| КОНЕЦ КАК Масса,
| ВТ_Товары.СтавкаНДС КАК СтавкаНДС,
| ВТ_Товары.Цена КАК Цена,
| СУММА(ВТ_Товары.Сумма) КАК Сумма,
| СУММА(ВТ_Товары.СуммаНДС) КАК СуммаНДС,
| МИНИМУМ(ВТ_Товары.НомерСтроки) КАК НомерСтроки,
| 0 КАК Метка
|ПОМЕСТИТЬ ВТ_СгруппированныеТовары
|ИЗ
| ВТ_Товары КАК ВТ_Товары
|
|СГРУППИРОВАТЬ ПО
| ВТ_Товары.Номенклатура,
| ВТ_Товары.Коэффициент,
| ВТ_Товары.Густина,
| ВТ_Товары.ЕдиницаИзмерения,
| ВТ_Товары.СтавкаНДС,
| ВТ_Товары.Цена
|;
|
|////////////////////////////////////////////////////////////////////////////////
|ВЫБРАТЬ
| ВТ_СгруппированныеТовары.Номенклатура КАК Номенклатура,
| ВТ_СгруппированныеТовары.ТоварНаименование КАК ТоварНаименование,
| ВТ_СгруппированныеТовары.НоменклатураГТД КАК НоменклатураГТД,
| ВТ_СгруппированныеТовары.БазоваяЕдиницаНаименование КАК БазоваяЕдиницаНаименование,
| ВТ_СгруппированныеТовары.БазоваяЕдиницаКодПоОКЕИ КАК БазоваяЕдиницаКодПоОКЕИ,
| ВТ_СгруппированныеТовары.ЕдиницаИзмерения КАК ЕдиницаИзмерения,
| ВТ_СгруппированныеТовары.ВидУпаковки КАК ВидУпаковки,
| ВТ_СгруппированныеТовары.Количество КАК Количество,
| ВТ_СгруппированныеТовары.Коэффициент КАК Коэффициент,
| ВТ_СгруппированныеТовары.Густина КАК Густина,
| ВТ_СгруппированныеТовары.Масса КАК Масса,
| ВТ_СгруппированныеТовары.СтавкаНДС КАК СтавкаНДС,
| ВТ_СгруппированныеТовары.Цена КАК Цена,
| ВТ_СгруппированныеТовары.Сумма КАК Сумма,
| ВТ_СгруппированныеТовары.СуммаНДС КАК СуммаНДС,
| ВТ_СгруппированныеТовары.НомерСтроки КАК НомерСтроки,
| ВТ_СгруппированныеТовары.Метка КАК Метка
|ИЗ
| ВТ_СгруппированныеТовары КАК ВТ_СгруппированныеТовары
|
|ОБЪЕДИНИТЬ ВСЕ
|
|ВЫБРАТЬ
| Тара.Номенклатура,
| Тара.Номенклатура.НаименованиеПолное,
| Тара.Номенклатура.НоменклатураГТД,
| Тара.Номенклатура.БазоваяЕдиницаИзмерения.Представление,
| Тара.Номенклатура.БазоваяЕдиницаИзмерения.Код,
| Тара.Номенклатура.БазоваяЕдиницаИзмерения,
| NULL,
| Тара.Количество,
| 0,
| ВЫРАЗИТЬ(NULL КАК ЧИСЛО(15, 3)), // Принудительно приводим тип к Числу
| 0,
| ""Без НДС"",
| Тара.Цена * &Курс / &Кратность,
| Тара.Сумма * &Курс / &Кратность,
| 0,
| Тара.НомерСтроки,
| 2
|ИЗ
| Документ.РеализацияТоваровУслуг.ВозвратнаяТара КАК Тара
|ГДЕ
| Тара.Ссылка = &ТекущийДокумент
|
|УПОРЯДОЧИТЬ ПО
| Метка ВОЗР,
| НомерСтроки ВОЗР";
Для понимания логики работы конфигурации важно осознавать физический смысл реквизитов. Название «Густина» (плотность) — это специализированный реквизит, часто используемый в отраслевых конфигурациях (например, для Украины или стран СНГ при учете жидких фракций):
Масса = Объем * Плотность в каждую строку документа реализации вносится фактическая плотность конкретной партии топлива на момент отгрузки.Поскольку возвратная тара представляет собой штучный жесткий инвентарь (деревянные поддоны, пластиковые ящики, металлические бочки), понятие плотности к ней неприменимо. Именно поэтому данного поля изначально нет в табличной части тары. Предложенное нами решение с использованием ВЫРАЗИТЬ(NULL КАК ЧИСЛО(15, 3)) полностью исключает программные ошибки, корректно обрабатывает структуру метаданных и сохраняет высокую скорость работы базы данных.