Почему в запросе 1С возникает ошибка «Поле не найдено» при обращении к реквизиту через точку и как ее решить?

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

Каждый разработчик 1С рано или поздно сталкивается с ситуацией, когда один и тот же запрос прекрасно работает на одной базе данных или на одном наборе документов, но внезапно аварийно завершается на другом с ошибкой «Поле не найдено ...» или «Несовместимые типы ВЫРАЗИТЬ». Особенно часто это происходит при обращении «через точку» к реквизитам составного поля временной таблицы.

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

Суть проблемы: почему «исчезает» реквизит

Рассмотрим классическую ситуацию. У нас есть временная таблица (например, загруженная из внешней ТаблицыЗначений), содержащая колонку Документ составного типа. В запросе мы пытаемся получить реквизит конкретного типа документа, предварительно проверив тип через конструкцию ВЫБОР КОГДА ТИПЗНАЧЕНИЯ...:


ВЫБРАТЬ
    ВЫБОР
        КОГДА ТИПЗНАЧЕНИЯ(ВТ.Документ) = ТИП(Документ.ПеремещениеТоваров)
            ТОГДА ВТ.Документ.СкладОтправитель
        ИНАЧЕ НЕОПРЕДЕЛЕНО
    КОНЕЦ КАК СкладОтправитель
ИЗ
    ВТ_ТабСообщений_Внешняя КАК ВТ

На первом наборе данных этот запрос отрабатывает без нареканий. Но при изменении выборки (например, когда в исходной таблице за конкретный период не оказалось ни одного перемещения товаров) платформа выдает ошибку компиляции: «Поле не найдено "ВТ_ТабСообщений_Внешняя.Документ.СкладОтправитель"».

Попытка защититься с помощью функции ВЫРАЗИТЬ тоже приводит к ошибке, но уже другой: «Несовместимые типы ВЫРАЗИТЬ»:

ВЫРАЗИТЬ(ВТ.Документ КАК Документ.ПеремещениеТоваров).СкладОтправитель

Выясним причину, почему так происходит.

---

Причина 1: Динамическая типизация колонок во временных таблицах

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

Если колонка Документ в исходной ТЗ не была типизирована жестко при создании (например, была добавлена просто по имени ТЗ.Колонки.Добавить("Документ")), то платформа 1С определяет её тип динамически — по фактически находящимся в ней значениям на момент передачи в запрос:

---

Причина 2: Как СУБД компилирует обращение «через точку» (разыменование)

Обращение к реквизитам «через точку» от полей составного типа (ВТ.Документ.СкладОтправитель) — это синтаксический сахар платформы «1С:Предприятие». На уровне СУБД такое обращение транслируется в неявные левые соединения (LEFT JOIN) со всеми таблицами тех объектов, типы которых СУБД считает допустимыми для данной колонки.

Давайте проанализируем поведение компилятора запросов:

  1. Компиляция на «хорошем» наборе данных: Платформа видит, что в описании типов колонки ВТ.Документ есть тип Документ.ПеремещениеТоваров. Она автоматически генерирует неявное соединение с таблицей перемещений и находит там поле СкладОтправитель. Запрос успешно выполняется.
  2. Компиляция на «проблемном» наборе данных: Поскольку во входящей таблице перемещений не было, тип колонки сузился. Компилятор видит, что для колонки ВТ.Документ допустим только тип РеализацияТоваровУслуг. Соответственно, неявное левое соединение строится только с таблицей реализаций. Попытка обратиться к реквизиту СкладОтправитель (которого у реализации нет) вызывает ошибку компиляции «Поле не найдено».

Важно помнить, что логические условия ВЫБОР КОГДА ... вычисляются непосредственно при выполнении запроса (в runtime), тогда как структура связей и наличие полей проверяются СУБД строго на этапе компиляции (в compile-time). Защитить запрос от ошибки проверки схемы метаданных обычным логическим условием невозможно.

---

Эффективные решения проблемы

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

Вариант 1. Явные левые соединения (Рекомендуемый способ)

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

Заменим обращение через точку на явное левое соединение:


ВЫБРАТЬ
    ВТ.СсылкаНаСтроку,
    ЕСТЬNULL(ДокПеремещение.СкладОтправитель, НЕОПРЕДЕЛЕНО) КАК СкладОтправитель,
    ЕСТЬNULL(ДокРеализация.СкладПолучатель, НЕОПРЕДЕЛЕНО) КАК СкладПолучатель
ИЗ
    ВТ_ТабСообщений_Внешняя КАК ВТ
    
    ЛЕВОЕ СОЕДИНЕНИЕ Документ.ПеремещениеТоваров КАК ДокПеремещение
        ПО ВТ.Документ = ДокПеремещение.Ссылка
        
    ЛЕВОЕ СОЕДИНЕНИЕ Документ.РеализацияТоваровУслуг КАК ДокРеализация
        ПО ВТ.Документ = ДокРеализация.Ссылка

Почему это работает стабильно?
Таблица метаданных Документ.ПеремещениеТоваров всегда существует в конфигурации и гарантированно содержит реквизит СкладОтправитель. Компилятор запросов без труда находит это поле при статическом анализе схемы. Если во временной таблице вообще не окажется перемещений, соединение просто вернет значение NULL для всех строк, но сам запрос отработает без ошибок.

Преимущество в производительности:
Обращение через точку к полям составного типа заставляет СУБД делать левые соединения со всеми таблицами конфигурации, входящими в этот тип. Если тип содержит десятки видов документов, платформа сгенерирует огромный SQL-запрос с десятками LEFT JOIN, что критически снизит производительность сервера. Явное левое соединение позволяет связаться только с теми таблицами, которые нам действительно нужны.

---

Вариант 2. Строгая типизация исходной Таблицы Значений

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

Для этого при создании исходной ТаблицыЗначений в коде 1С нужно обязательно инициализировать её колонки с явным указанием массива типов через ОписаниеТипов:


// 1. Создаем массив типов, которые могут содержаться в колонке
МассивТипов = Новый Массив;
МассивТипов.Добавить(Тип("ДокументСсылка.ПеремещениеТоваров"));
МассивТипов.Добавить(Тип("ДокументСсылка.РеализацияТоваровУслуг"));
МассивТипов.Добавить(Тип("ДокументСсылка.ПоступлениеТоваровУслуг"));

// 2. Создаем объект ОписаниеТипов
ОписаниеСоставногоТипа = Новый ОписаниеТипов(МассивТипов);

// 3. Добавляем колонку в ТЗ с жестко заданным описанием типов
ТаблицаШаг = Новый ТаблицаЗначений;
ТаблицаШаг.Колонки.Добавить("Документ", ОписаниеСоставногоТипа);
ТаблицаШаг.Колонки.Добавить("СсылкаНаСтроку", Новый ОписаниеТипов("Число"));

Даже если в процессе заполнения этой таблицы в нее не запишется ни одного документа перемещения, колонка Документ сохранит полную структуру типов. При помещении таблицы в запрос платформа 1С создаст в СУБД типизированную колонку со всеми потенциально возможными связями, и ошибка «Поле не найдено» больше не возникнет.

---

Резюме

Для того чтобы избежать «исчезновения» реквизитов и ошибок компиляции запросов в 1С, придерживайтесь следующих стандартов разработки:

  1. Никогда не обращайтесь через точку к реквизитам полей составного типа (особенно во временных таблицах).
  2. Используйте явные левые соединения с конкретными таблицами документов или справочников. Это не только предотвращает ошибки, но и существенно ускоряет работу СУБД.
  3. При передаче внешних таблиц значений в запрос всегда заботьтесь о предварительной жесткой типизации их колонок через объект ОписаниеТипов.
← На главную