Как перевести стандартный запрос 1С 7.7 на прямой SQL-запрос с использованием 1C++

Программист 1С v7.7 Управленческий учет
← На главную

Перевод старого «черного» запроса 1С 7.7 на прямые SQL-запросы — это классическая задача при оптимизации высоконагруженных систем или работе с большими объемами данных. Прямые запросы позволяют значительно ускорить получение данных, но требуют понимания того, как метаданные 1С проецируются на таблицы SQL Server. В этой статье мы подробно разберем процесс трансформации стандартного запроса в эффективный SQL-код с использованием внешней компоненты 1C++.

Анализируем исходный запрос

Для начала проанализируем структуру стандартного запроса, который нам нужно перевести. В исходном коде мы видим обращение к справочнику Графики, выборку реквизитов (Сотрудник, Подразделение, Отделение, ДатаРаб, Время) и наложение условий по спискам значений и периоду дат. Особое внимание стоит уделить группировкам — в стандартном запросе они определяют иерархию обхода результата.

Рассмотрим структуру исходного запроса:


//{{Запрос(СформироватьПоГрафику)
|ЭлементГрафика = Справочник.Графики.ТекущийЭлемент;
|Сотрудник = Справочник.Графики.Владелец;
|Подразделение = Справочник.Графики.Подразделение;
|Отделение = Справочник.Графики.Отделение;
|ДатаРаб = Справочник.Графики.ДатаРаб;
|ВремяРаб = Справочник.Графики.Время;
|Группировка Сотрудник;
|Группировка ЭлементГрафика;
|Группировка ДатаРаб;
|Группировка ВремяРаб;
|Условие(Подразделение в спПодразделение);
|Условие(Отделение в _Отделение);
|Условие(Сотрудник в спСотрудник);
|Условие(ДатаРаб >= _ДатаНач);
|Условие(ДатаРаб <= _ДатаКон);
|"//}}ЗАПРОС

Шаг 1: Подготовка SQL-запроса и использование метапарсера

При написании прямого запроса мы сталкиваемся с тем, что в базе данных SQL таблицы и поля называются не человекочитаемыми именами, а кодами типа SC123 или SP456. Чтобы не забивать голову этими идентификаторами, мы используем метапарсер 1C++. Символ $ перед именем таблицы или поля заставляет компоненту автоматически подставить реальное имя из SQL-словаря.

Выясним причину использования специальных полей. Поле ID — это уникальный идентификатор элемента справочника. Поле ParentExt в 1С 7.7 всегда хранит ссылку на Владельца (в нашем случае это Сотрудник), а поле ParentID — ссылку на родительскую группу.

Сформируем базовый текст SQL-запроса:


ТекстЗапроса = "
|SELECT
|    Графики.id [ЭлементГрафика $Справочник.Графики],
|    Графики.ParentExt [Сотрудник $Справочник.Сотрудники],
|    $Графики.Подразделение [Подразделение $Справочник.Подразделения],
|    $Графики.Отделение [Отделение $Справочник.Отделения],
|    $Графики.ДатаРаб [ДатаРаб $Дата],
|    $Графики.Время [ВремяРаб]
|FROM
|    $Справочник.Графики AS Графики (NOLOCK)
|WHERE
|    Графики.IsMark = 0
|    AND $Графики.ДатаРаб BETWEEN :ДатаНач AND :ДатаКон~
|";

Шаг 2: Работа со списками значений (Условие В)

В стандартном запросе 1С легко обрабатывает условия типа Подразделение в спПодразделение. В SQL для этого лучше всего использовать временные таблицы. Разберем по шагам, как передать список значений из 1С в SQL-сервер с помощью метода УложитьСписокОбъектов.

Проанализируем пример кода для передачи фильтров:


// Создаем объект Recordset
ЗапросSQL = СоздатьОбъект("ODBCRecordset");

// Укладываем список подразделений во временную таблицу #tab_podr
// Параметр "Подразделения" указывает тип данных для метапарсера
ЗапросSQL.УложитьСписокОбъектов(спПодразделение, "#tab_podr", "Подразделения");
ЗапросSQL.УложитьСписокОбъектов(спСотрудник, "#tab_sotr", "Сотрудники");

// Добавляем условия в текст запроса
ТекстЗапроса = ТекстЗапроса + "
|    AND $Графики.Подразделение IN (SELECT val FROM #tab_podr)
|    AND Графики.ParentExt IN (SELECT val FROM #tab_sotr)
|";

Важный нюанс: при использовании метода УложитьСписокОбъектов во временной таблице создается колонка val (в некоторых версиях id), в которой хранятся 9-символьные идентификаторы объектов 1С. Всегда проверяйте имя колонки в вашей версии компоненты.

Шаг 3: Соединение таблиц (JOIN) для получения дополнительных данных

Часто в отчете нужно вывести не только ссылку на объект, но и его реквизиты (например, ФИО сотрудника). В стандартном запросе мы бы просто написали Запрос.Сотрудник.Наименование. В прямом запросе для этого нужно использовать LEFT JOIN. Это гораздо эффективнее, чем получать объекты по одному в цикле обхода результата.

Рассмотрим, как добавить соединение со справочником Сотрудники:


ТекстЗапроса = "
|SELECT
|    Графики.id [ЭлементГрафика $Справочник.Графики],
|    Сотр.Descr [СотрудникИмя],
|    $Графики.ДатаРаб [ДатаРаб $Дата],
|    $Графики.Время [ВремяРаб]
|FROM
|    $Справочник.Графики AS Графики (NOLOCK)
|LEFT JOIN
|    $Справочник.Сотрудники AS Сотр ON Сотр.ID = Графики.ParentExt
|WHERE
|    $Графики.ДатаРаб BETWEEN :ДатаНач AND :ДатаКон~
|";

Шаг 4: Эмуляция группировок через Индексированную Таблицу (ИТЗ)

SQL возвращает плоскую таблицу данных. Чтобы сохранить логику обхода «цикл в цикле» (группировки 1С), мы используем объект ИндексированнаяТаблица. Этот объект позволяет сгруппировать данные и вычислять итоги аналогично родному запросу 1С.

Посмотрим на пример обработки результата:


// Выполняем запрос и получаем результат в ИТЗ
ИТЗ = ЗапросSQL.ВыполнитьИнструкцию(ТекстЗапроса);

// Группируем данные. 
// Первый параметр - колонки группировки, второй - колонки для суммирования
ИТЗ.Сгруппировать("Сотрудник, ЭлементГрафика, ДатаРаб", "ВремяРаб");

// Теперь можно эмулировать обход группировок
// Для этого в 1C++ используются фильтры по ИТЗ или метод ВыбратьСтроки
ИТЗ.ВыбратьСтроки();
Пока ИТЗ.ПолучитьСтроку() = 1 Цикл
    // Логика обработки каждой строки аналогична Запрос.Группировка()
    ТекСотрудник = ИТЗ.Сотрудник;
    ТекВремя = ИТЗ.ВремяРаб;
КонецЦикла;

Типичные ошибки при переводе запросов

Проанализируем основные «грабли», на которые наступают разработчики:

  1. Забытый символ $: Если написать Графики.Подразделение вместо $Графики.Подразделение, SQL выдаст ошибку "Invalid column name", так как он не знает поля с таким именем.
  2. Область видимости временных таблиц: Временные таблицы с одной решеткой (#table) видны только в рамках одного соединения. Если вы создали таблицу в одном объекте ODBCRecordset, а запрос выполняете другим — таблица не будет найдена.
  3. Формат дат: SQL требует даты в формате YYYYMMDD. Используйте параметры :ДатаНач, которые метапарсер 1C++ конвертирует автоматически.
  4. IsMark: Никогда не забывайте добавлять IsMark = 0, если вам не нужны помеченные на удаление элементы, так как прямой запрос, в отличие от стандартного, «видит» всё.

Подводя итог, можно сказать, что перевод на прямые запросы — это не просто переписывание кода, а смена парадигмы работы с данными. Мы переносим тяжелые вычисления и фильтрацию на сторону SQL-сервера, получая взамен колоссальный прирост производительности.

← На главную