Как в 1С 7.7 через SQLite выбрать документы по их статусу проведения и пометки на удаление?

Программист 1С v7.7 IT и автоматизация бизнеса
← На главную

При работе в платформе 1С:Предприятие 7.7 на крупных базах данных штатные запросы (так называемые «черные запросы») могут работать медленно. Для кардинального ускорения выборки разработчики часто используют внешние компоненты (например, 1C++), позволяющие выполнять прямые SQL-запросы к физическим таблицам базы данных через движок SQLite (в случае DBF-версии базы данных). В этой статье мы подробно разберем, как быстро и правильно выбрать документы с фильтрацией по статусу «Не проведен» и «Не помечен на удаление».

Постановка задачи и проблемы штатных механизмов

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


ТекстЗапроса = 
"//{{ЗАПРОС(Сформировать)
|Период с Дата1 по Дата2;
|ОбрабатыватьДокументы НеПроведенные;
|Обрабатывать НеПомеченныеНаУдаление;
|Док = Документ.Доставка.ТекущийДокумент;
|Группировка Док;
|"//}}ЗАПРОС
;
Запрос = СоздатьОбъект("Запрос");
Рез = Запрос.Выполнить(ТекстЗапроса);
Если Рез = 1 Тогда
    Запрос.Выгрузить(ТабФорм);    
КонецЕсли;

Важное примечание: Как было замечено в обсуждении на форуме, при написании штатного запроса начинающие разработчики часто забывают добавить директиву Группировка. Без нее запрос вернет пустой результат или отработает некорректно. Обязательно указывайте группировку по документу!

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

Анатомия таблицы журнала документов _1SJOURN

В системе 1С 7.7 все документы регистрируются в общей таблице журнала документов. Физически в базе данных DBF она называется _1SJOURN (в SQLite-запросах мы обращаемся к ней как к таблице Журнал). Для решения нашей задачи ключевое значение имеют три поля этой таблицы:

  1. IDDOCDEF — идентификатор вида документа (числовой или текстовый код метаданных).
  2. ISMARK — признак пометки на удаление.
  3. CLOSED — битовая маска, хранящая статус проведения документа и наличие связанных движений.

Рассмотрим особенности работы с этими полями подробнее, так как именно здесь кроются основные нюансы оптимизации.

Секреты поля CLOSED и работа с битовой маской

Часто разработчики ошибочно полагают, что поле CLOSED принимает простые значения вроде 0 или 1. Однако на практике при анализе таблиц можно обнаружить в этом поле значения 4, 5 или другие числа. Почему так происходит?

Поле CLOSED — это битовая маска. Каждый бит в байте отвечает за определенное состояние документа:

Именно поэтому для проведенного документа оперативного учета (например, расходной накладной с движениями по регистрам) значение поля CLOSED будет равно 5 (1 — проведен + 4 — движения по регистрам). Для аналогичного, но распроведенного документа значение будет равно 4 (документ не проведен, но структура метаданных подразумевает движения).

Как правильно проверять статус проведения?

Многие используют деление по модулю (например, Журнал.CLOSED % 2 = 0 для непроведенных). Однако математически более эффективным и стандартным для SQL является применение побитового И (&). В SQLite это выглядит так:

Анализ поля ISMARK (пометка на удаление)

В зависимости от версии СУБД (DBF или MS SQL), поле пометки на удаление может вести себя по-разному. В DBF-версии базы данных (которую мы читаем через SQLite драйвер dbeng) это строковое поле длиной в один символ:

Чтобы ваш запрос был максимально надежным и переносимым, лучше использовать условие отрицания пометки: Журнал.ISMARK != '*'.

Оптимизация: использование индекса по дате

Если мы выполняем прямой запрос к таблице Журнал без ограничения по датам, СУБД будет вынуждена сканировать всю таблицу целиком (Full Table Scan), что на реальной базе приведет к серьезным задержкам. Чтобы этого избежать, обязательно задействуем главный составной индекс таблицы журнала — текстовое поле DATE_TIME_IDDOC.

Данное поле имеет строго фиксированный формат: ГГГГММДДЧЧММССXXXXXXXXX (где последние символы — это ID документа). Поиск по диапазону этого текстового поля в SQLite происходит мгновенно, так как по нему построен индекс базы данных. Условие фильтрации по периоду в запросе должно выглядеть так:

AND Журнал.DATE_TIME_IDDOC BETWEEN :НачДата AND :КонДата~

Обратите внимание на символ тильды ~ в конце параметра даты. В синтаксисе парсера 1С++ он указывает системе автоматически дополнить значение даты временем конца дня (до 23:59:59), чтобы не потерять документы, созданные внутри последнего дня периода.

Итоговый шаблон оптимального SQLite-запроса

Рассмотрим и проанализируем финальный вариант программного кода для выполнения быстрого прямого запроса в 1С 7.7. Мы инициализируем виртуальную таблицу SQLite на основе физической DBF-таблицы журнала и выгрузим результат напрямую в Таблицу Значений.


// Создаем объект базы данных SQLite через компоненту 1С++
БазаДанных = СоздатьОбъект("SQLiteDatabase");
ЗапросSQLite = БазаДанных.НовыйЗапрос();

// Регистрируем виртуальную таблицу журнала с помощью драйвера dbeng
ЗапросSQLite.ВыполнитьЗапрос("create virtual table Журнал using dbeng(Журнал)");

// Задаем параметры фильтрации
ЗапросSQLite.Подставлять("НачДата", ВыбНачДата);
ЗапросSQLite.Подставлять("КонДата", ВыбКонДата);

ТекстЗапроса = "
|SELECT 
|   Журнал.IDDOC [Документ :Документ.Доставка] 
|FROM 
|   Журнал 
|WHERE 
|   Журнал.IDDOCDEF = :ВидДокумента.Доставка
|   AND Журнал.DATE_TIME_IDDOC BETWEEN :НачДата AND :КонДата~
|   AND (Журнал.CLOSED & 1) = 0
|   AND Журнал.ISMARK != '*'
|";

// Выполняем высокоскоростной запрос
ТабРезультат = ЗапросSQLite.ВыполнитьЗапрос(ТекстЗапроса);

// Закрываем соединение с базой данных для освобождения ресурсов
БазаДанных.Закрыть();

// Выводим результат на форму или обрабатываем в коде
ТабРезультат.Выгрузить(ТабФорм);

Сравнение производительности

При проведении тестов на небольших тестовых базах данных разница во времени выполнения штатного запроса и SQLite-запроса может показаться незначительной (например, 18 мс против 7 мс). Однако на рабочих базах данных объемом в несколько гигабайт и сотнями тысяч документов, прямой запрос к индексу DATE_TIME_IDDOC с побитовым сравнением поля CLOSED показывает ускорение работы до 10–15 раз по сравнению со стандартным объектом Запрос.

← На главную