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