При переходе с платформы 1С:Предприятие 8 на 1С 7.7 разработчики часто сталкиваются с трудностями при написании запросов. В "семерке" нет привычного конструктора запросов с явными конструкциями связей таблиц, а синтаксис встроенного языка запросов (так называемых "черных" запросов) сильно отличается от стандартов SQL. Разберем подробнее классическую задачу: как получить список элементов справочника-владельца (например, Книги) вместе с элементами подчиненного справочника (например, Инвентарные номера) и вывести их реквизиты.
Рассмотрим несколько подходов к решению этой задачи: от стандартных средств платформы до профессиональных методов с использованием прямых SQL-запросов, а также проанализируем "подводные камни" каждого из вариантов.
Встроенный язык запросов 1С 7.7 не поддерживает классические команды объединения. Для того чтобы связать две таблицы, необходимо перечислить поля через запятую при объявлении переменной запроса. Это даст транслятору понять, что таблицы нужно соединить по равенству указанных полей.
Посмотрим на пример правильного формирования такого запроса:
Процедура Сформировать()
Перем Запрос, ТекстЗапроса, Таб;
Запрос = СоздатьОбъект("Запрос");
ТекстЗапроса =
"//{{ЗАПРОС(Сформировать)
|Заголовок = Справочник.Книги.ТекущийЭлемент, Справочник.ИнвентарныеНомера.Владелец;
|ИнвентарныйНомер = Справочник.ИнвентарныеНомера.ТекущийЭлемент;
|Автор = Справочник.Книги.Автор;
|ДатаИздания = Справочник.Книги.ДатаИздания;
|Количество = Справочник.Книги.Количество;
|Группировка Заголовок без групп;
|Группировка ИнвентарныйНомер;
|"//}}ЗАПРОС
;
Если Запрос.Выполнить(ТекстЗапроса) = 0 Тогда
Возврат;
КонецЕсли;
Таб = СоздатьОбъект("Таблица");
Таб.ИсходнаяТаблица("Сформировать");
Таб.ВывестиСекцию("Шапка");
// Обходим результат
Пока Запрос.Группировка(1) = 1 Цикл
// Здесь позиционируемся на Книге
Таб.ВывестиСекцию("Книга");
Пока Запрос.Группировка(2) = 1 Цикл
// Здесь позиционируемся на Инвентарном номере
Таб.ВывестиСекцию("ИнвНомер");
КонецЦикла;
КонецЦикла;
Таб.ТолькоПросмотр(1);
Таб.Показать("Сформировать", "");
КонецПроцедуры
Важный нюанс механики: Такое связывание (через запятую) всегда эквивалентно внутреннему соединению. Это значит, что если в базе есть элемент справочника Книги, для которого еще не заведено ни одного инвентарного номера, эта книга вообще не попадет в результат выборки. Если вам нужен полный список книг (аналог левого соединения), такой штатный запрос не подойдет.
Начинающие разработчики в 1С 7.7 часто допускают критическую ошибку, которая приводит к катастрофическому падению производительности. Проанализируем ситуацию, когда в запросе выбирается только ссылка на справочник, а значения реквизитов извлекаются через точку в цикле обхода выборки:
Пока Запрос.Группировка(1) = 1 Цикл
Книга = Запрос.Заголовок;
Автор = Книга.Автор; // Ошибка производительности!
ДатаИздания = Книга.ДатаИздания; // Ошибка производительности!
КонецЦикла;
При таком подходе система выполняет отдельный неявный запрос к базе данных (к DBF-файлу или SQL-серверу) на каждой итерации цикла. Если запрос вернет 10 000 строк, система сделает 10 000 лишних обращений к базе. Это классическая проблема N+1.
Чтобы избежать этого, все необходимые реквизиты должны быть явно описаны в самом тексте запроса. Обращаться к ним нужно напрямую как к свойствам объекта Запрос:
Пока Запрос.Группировка(1) = 1 Цикл
// Данные уже находятся в кэше запроса
Автор = Запрос.Автор;
ДатаИздания = Запрос.ДатаИздания;
КонецЦикла;
Иногда задачу можно решить вообще без использования запроса, применяя встроенные методы перебора справочников. Рассмотрим, как это сделать с помощью метода ИспользоватьВладельца():
Книга = СоздатьОбъект("Справочник.Книги");
ИнвНомер = СоздатьОбъект("Справочник.ИнвентарныеНомера");
Таб = СоздатьОбъект("Таблица");
Таб.ИсходнаяТаблица("Таблица");
Книга.ВыбратьЭлементы();
Пока Книга.ПолучитьЭлемент() = 1 Цикл
Если Книга.ЭтоГруппа() = 1 Тогда
Продолжить;
КонецЕсли;
// Выводим книгу
Таб.ВывестиСекцию("Строка_Книга");
// Устанавливаем фильтр по владельцу
ИнвНомер.ИспользоватьВладельца(Книга.ТекущийЭлемент());
ИнвНомер.ВыбратьЭлементы();
Пока ИнвНомер.ПолучитьЭлемент() = 1 Цикл
// Выводим инвентарные номера текущей книги
Таб.ВывестиСекцию("Строка_ИнвНомер");
КонецЦикла;
КонецЦикла;
Таб.ТолькоПросмотр(1);
Таб.Показать();
Анализ метода: Этот способ кажется простым и понятным, но он не масштабируется. На локальных небольших базах в файловом формате (DBF) он сработает быстро. Однако на больших объемах и клиент-серверных базах (SQL) метод ВыбратьЭлементы() открывает курсоры. Вложенный цикл создает огромную нагрузку на сервер БД и приводит к избыточным блокировкам. Этот метод стоит применять только для обработки малых объемов данных.
Для разработчиков, пришедших с 1С 8 и привыкших к гибкости языка запросов, стандартом разработки сложных отчетов в 1С 7.7 является использование внешней компоненты 1С++. Она позволяет выполнять прямые запросы к базе данных средствами SQL-сервера, используя класс ODBCRecordSet.
У каждого подчиненного справочника на физическом уровне в 1С 7.7 создается системное поле PARENTEXT, в котором хранится ID элемента-владельца. Воспользуемся этим знанием, чтобы написать полноценное левое соединение. В отличие от штатного запроса, данный код позволит получить все книги, даже те, у которых еще нет инвентарных номеров.
// Пример текста прямого запроса SQL для 1С++
ТекстЗапросаSQL = "
|SELECT
| Books.ID as [Книга $Справочник.Книги],
| Books.DESCR as КнигаНаименование,
| $Books.Автор as [Автор $Справочник.Авторы],
| $Books.ДатаИздания as ДатаИздания,
| Inv.ID as [ИнвНомер $Справочник.ИнвентарныеНомера]
|FROM
| $Справочник.Книги AS Books WITH (NOLOCK)
|LEFT JOIN
| $Справочник.ИнвентарныеНомера AS Inv WITH (NOLOCK)
| ON Inv.PARENTEXT = Books.ID
|WHERE
| Books.ISMARK = 0
|";
Разберем преимущества прямого запроса:
WITH (NOLOCK) позволяет считывать данные без наложения блокировок на таблицы, что спасает рабочую базу от "зависаний" при формировании тяжелых отчетов пользователями.Выбирая метод решения задачи, всегда оценивайте объем данных и формат вашей информационной базы. Для разовых задач и простых списков достаточно штатного запроса или перебора. Для сложных отчетов с высокой нагрузкой — используйте мощь 1С++ и прямых SQL-запросов.