Как проверить существование таблицы в базе данных 1С перед выполнением запроса и избежать ошибки «Таблица не найдена»?

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

При разработке универсальных обработок, предназначенных для работы в различных конфигурациях (например, при переносе данных между базами 1С:ERP, 1С:Управление торговлей или самописными решениями), разработчики часто сталкиваются с ситуацией, когда определенный объект метаданных (справочник, документ или регистр сведений) существует в одной базе, но полностью отсутствует в другой. Если попытаться выполнить динамический запрос к таблице, которой нет в текущей конфигурации, платформа выдаст критическую ошибку «Таблица не найдена», и выполнение программы будет аварийно прервано.

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

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

Анализ исходного кода и скрытых ошибок

Рассмотрим код, который изначально пытался использовать автор темы:


&НаСервереБезКонтекста
Функция ПолучитьКоличествоЗаписейОбъектаСистемы(ОбъектСистемы)
    Если ОбъектСистемы <> Неопределено Тогда
        Запрос = Новый Запрос;
        Запрос.Текст = "Выбрать Количество(*) КАК КоличествоЗаписей ИЗ " + ОбъектСистемы;
        РезультатЗапроса = Запрос.Выполнить.Выбрать;
        Пока РезультатЗапроса.СледующийЦикл
            Возврат РезультатЗапроса.КоличествоЗаписей;
        КонецЦикла;
    КонецЕсли;
КонецФункции

При детальном рассмотрении этого фрагмента можно обнаружить сразу несколько серьезных ошибок и неоптимальных конструкций:

  1. Синтаксическая ошибка вызова методов платформы. Строка Запрос.Выполнить.Выбрать написана без скобок. В платформе 1С методы объектов вызываются строго со скобками, даже если у них нет параметров. Правильная запись должна выглядеть так: Запрос.Выполнить().Выбрать();. Без этого код просто вернет ошибку времени выполнения.
  2. Конструкция цикла Пока... КонецЦикла для одиночной записи. Запрос с агрегатной функцией Количество(*) без группировки всегда возвращает ровно одну строку (даже если в таблице нет записей, вернется строка со значением 0). Использовать здесь цикл бессмысленно и неэффективно. Достаточно использовать оператор Если.
  3. Синтаксическая ошибка в конструкции цикла. В коде написано РезультатЗапроса.СледующийЦикл. Это синтаксический гибрид. Метод обхода выборки называется Следующий(), а конструкция цикла — Пока ... Цикл. Правильно писать: Пока РезультатЗапроса.Следующий() Цикл.
  4. Главная логическая ошибка. Отсутствует проверка существования таблицы в метаданных текущей базы. Если в переменной ОбъектСистемы передано имя несуществующего регистра (например, "РегистрСведений.ЗначенияСвойствОбъектов"), СУБД не сможет построить план запроса, и платформа выбросит исключение.

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

Решение 1. Проверка существования в конкретной коллекции метаданных

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

Посмотрим на пример реализации данного подхода:


ИмяРегистра = "ЗначенияСвойствОбъектов"; // Передаем только имя, без префикса типа

Если Метаданные.РегистрыСведений.Найти(ИмяРегистра) <> Неопределено Тогда
    // Регистр существует в системе, можно смело выполнять запрос
    Запрос = Новый Запрос;
    Запрос.Текст = "ВЫБРАТЬ Количество(*) КАК Количество ИЗ РегистрСведений." + ИмяРегистра;
    // ... дальнейшая обработка запроса
Иначе
    // Регистра нет в этой базе, обрабатываем ситуацию без вызова ошибки
    Возврат 0;
КонецЕсли;

Преимущества метода: простой, понятный код, который быстро отрабатывает на уровне платформы.

Недостатки метода: жесткая привязка к типу метаданных. Если ваша обработка должна проверять разные типы объектов (например, сегодня это справочник "Справочник.Номенклатура", а завтра — регистр накопления "РегистрНакопления.Продажи"), данный способ потребует написания сложных разветвленных условий с анализом строкового префикса.

Решение 2. Универсальный поиск объекта по его полному имени

Для создания по-настоящему гибкого и отказоустойчивого кода рекомендуется использовать универсальный метод платформы Метаданные.НайтиПоПолномуИмени(). Этот метод принимает на вход строку с полным именем объекта метаданных (например, "РегистрСведений.ЗначенияСвойствОбъектов" или "Справочник.Контрагенты") и возвращает соответствующий объект метаданных, либо значение Неопределено, если объект в конфигурации отсутствует.

Разберем по шагам, как это реализовать:


ПолноеИмяТаблицы = "РегистрСведений.ЗначенияСвойствОбъектов";

// Проверяем наличие объекта в структуре метаданных текущей базы данных
ОбъектМетаданных = Метаданные.НайтиПоПолномуИмени(ПолноеИмяТаблицы);

Если ОбъектМетаданных = Неопределено Тогда
    // Объект не найден в конфигурации. Запрос выполнять нельзя!
    ЗаписатьЛог("Таблица " + ПолноеИмяТаблицы + " отсутствует в данной базе.");
    Возврат 0;
КонецЕсли;

// Если проверка пройдена, мы можем динамически сконструировать и запустить запрос
Запрос = Новый Запрос;
Запрос.Текст = "ВЫБРАТЬ Количество(*) КАК КоличествоЗаписей ИЗ " + ПолноеИмяТаблицы;

Выборка = Запрос.Выполнить().Выбрать();
Если Выборка.Следующий() Тогда
    Возврат Выборка.КоличествоЗаписей;
КонецЕсли;

Этот способ является наиболее предпочтительным в чистой платформе 1С, поскольку он полностью избавляет нас от необходимости анализировать тип объекта вручную.

Решение 3. Использование функций Библиотеки стандартных подсистем (БСП)

Если вы ведете разработку в современных типовых конфигурациях 1С (таких как ERP, УТ, КА, БП, ЗУП), то в вашем распоряжении имеется мощный инструмент — Библиотека стандартных подсистем (БСП). В ней реализованы готовые механизмы для работы с метаданными, которые работают быстрее стандартных методов платформы за счет использования кэширования.

Рекомендуется использовать метод общего модуля БСП ОбщегоНазначения.ОбъектМетаданныхПоПолномуИмени(). Проанализируем пример кода:


ПолноеИмяТаблицы = "РегистрСведений.ЗначенияСвойствОбъектов";

// Используем кэшируемую функцию БСП для получения метаданных
ОбъектМетаданных = ОбщегоНазначения.ОбъектМетаданныхПоПолномуИмени(ПолноеИмяТаблицы);

Если ОбъектМетаданных = Неопределено Тогда
    // Объекта нет в текущей базе
    Возврат 0;
КонецЕсли;

// Объект гарантированно существует, выполняем запрос
Запрос = Новый Запрос;
Запрос.Текст = "ВЫБРАТЬ Количество(*) КАК КолЗаписей ИЗ " + ПолноеИмяТаблицы;
// ... выполнение запроса

Использование БСП повышает производительность системы, особенно если проверка выполняется в цикле для большого списка различных объектов метаданных.

Почему нельзя использовать конструкцию Попытка... Исключение?

Иногда у начинающих разработчиков возникает соблазн не проверять метаданные, а просто обернуть запрос в блок перехвата ошибок:


// ВНИМАНИЕ: Данный код является примером ПЛОХОЙ ПРАКТИКИ!
Попытка
    Запрос.Текст = "ВЫБРАТЬ Количество(*) ИЗ " + ОбъектСистемы;
    Результат = Запрос.Выполнить();
Исключение
    // Обработка ошибки, если таблицы нет
КонецПопытки;

Рассмотрим подробнее, почему так делать категорически нельзя:

  1. Нарушение транзакционности. Если этот код будет вызван внутри какой-либо транзакции (например, при записи или проведении документа, в подписке на событие и т.д.), любая ошибка обращения к СУБД (включая отсутствие таблицы) приведет к немедленному и безусловному переходу всей транзакции в состояние ошибки. Конструкция Попытка... Исключение не сможет предотвратить откат транзакции. В итоге вся цепочка операций завершится аварийно.
  2. Нагрузка на СУБД и засорение логов. Каждая неудавшаяся попытка обращения к несуществующей таблице генерирует ошибку на уровне СУБД (например, MS SQL Server или PostgreSQL). Это создает лишнюю нагрузку на сервер баз данных и сильно засоряет Технологический Журнал (ТЖ) ошибками, затрудняя последующий аудит и администрирование системы.

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

Итоговый вариант безопасной и оптимальной функции

Объединим все лучшие практики и исправим исходную функцию автора темы. Мы добавим универсальную проверку на наличие объекта по полному имени, защитим код от пустых значений параметров и приведем синтаксис работы с запросом к стандартам разработки 1С.

Вот как должен выглядеть итоговый рабочий вариант кода:


&НаСервереБезКонтекста
Функция ПолучитьКоличествоЗаписейОбъектаСистемы(Знач ОбъектСистемы)
    
    // Шаг 1. Защита от передачи пустых параметров
    Если ПустаяСтрока(ОбъектСистемы) Тогда
        Возврат 0;
    КонецЕсли;
    
    // Шаг 2. Проверяем существование таблицы в метаданных текущей конфигурации
    Если Метаданные.НайтиПоПолномуИмени(ОбъектСистемы) = Неопределено Тогда
        // Таблицы нет в этой базе (например, это ERP, а не база НСИ)
        Возврат 0; 
    КонецЕсли;
    
    // Шаг 3. Безопасное конструирование и выполнение запроса
    Запрос = Новый Запрос;
    Запрос.Текст = "ВЫБРАТЬ Количество(*) КАК КоличествоЗаписей ИЗ " + ОбъектСистемы;
    
    Выборка = Запрос.Выполнить().Выбрать();
    Если Выборка.Следующий() Тогда
        Возврат Выборка.КоличествоЗаписей;
    КонецЕсли;
    
    Возврат 0;
    
КонецФункции

Применяя этот подход, мы гарантируем, что наша обработка будет стабильно работать в любой конфигурации 1С. Если таблица присутствует в базе, мы мгновенно получим актуальное число записей в ней, а если отсутствует — функция корректно вернет 0, не прерывая выполнение программы и не вызывая нареканий со стороны пользователей.

← На главную