При переходе на работу с прямыми SQL-запросами в среде 1С:Предприятие 7.7 с использованием внешней компоненты 1С++ разработчики часто сталкиваются со специфической структурой базы данных. Одним из первых барьеров становится выборка документов по дате. Попытка найти поле даты непосредственно в таблице конкретного документа (например, в таблице документа Перерасчет) оборачивается неудачей, поскольку архитектура СУБД в 1С 7.7 устроена иначе, чем в более современных версиях платформы.
В этой статье мы подробно разберем, где физически хранятся даты документов, как правильно связать таблицы базы данных, как использовать встроенные механизмы типизации 1С++ и как оптимизировать SQL-запрос для достижения максимальной производительности.
Для начала проанализируем, как платформа 1С 7.7 хранит информацию о документах на уровне СУБД (MS SQL Server или DBF-файлов). Информация о каждом документе разделена на две части:
_1SJOURN (Общий журнал документов).ЛицевойСчет в документе Перерасчет) хранятся в обособленной таблице вида $Документ.Перерасчет (на уровне SQL это таблица с именем вида _1S[Идентификатор]).Таким образом, чтобы получить документ с его специфическими реквизитами и одновременно отфильтровать его по дате создания, нам необходимо выполнить операцию соединения (INNER JOIN) таблицы конкретного документа с таблицей общего журнала _1SJOURN по уникальному 9-символьному идентификатору документа IDDOC.
В таблице общего журнала документов _1SJOURN нет привычного поля типа DateTime для хранения даты документа. Вместо этого используется составное текстовое поле DATE_TIME_IDDOC длиной 23 символа. Понимание его структуры критически важно для написания корректных запросов:
ГГГГММДД (например, 20230823).IDDOC (также в 36-ричном формате).Именно из-за текстового формата поля DATE_TIME_IDDOC простая передача стандартного параметра даты СУБД приведет к ошибке несовместимости типов или к неверным результатам выборки.
Рассмотрим, как связать таблицу конкретного документа с общим журналом. Начнем с простого запроса без фильтрации по дате, чтобы понять принцип связывания таблиц:
SELECT
Журнал.IDDOC [Документ $Документ.Перерасчет],
$Перерасчет.ЛицевойСчет [ЛицевойСчет $Справочник.ЛицевыеСчета]
FROM
$Документ.Перерасчет AS Перерасчет
INNER JOIN
_1SJOURN AS Журнал ON Перерасчет.IDDOC = Журнал.IDDOC
В данном примере мы связали таблицы по полю IDDOC. Обратите внимание на типизацию в квадратных скобках: конструкция [Документ $Документ.Перерасчет] указывает метапарсеру 1С++, что поле необходимо преобразовать в соответствующий объект ссылки 1С.
Для отбора документов по периоду нам необходимо наложить условие BETWEEN на строковое поле DATE_TIME_IDDOC. Передавать параметры напрямую в виде строк крайне неудобно и небезопасно с точки зрения SQL-инъекций. 1С++ предлагает элегантное решение — использование модификаторов параметров (тильды).
Разберем разницу между модификаторами дат в 1С++:
:КонДата~): Этот модификатор разработан специально для работы со строковым полем DATE_TIME_IDDOC. Если вы передадите в параметр дату 23.08.2023, то парсер преобразует конструкцию в строковое значение '20230823Z'. Символ Z в конце является максимально возможным символом в 36-ричной системе счисления. Благодаря этому условие BETWEEN :НачДата AND :КонДата~ корректно захватит абсолютно все документы за конечную дату, включая те, что проведены в последнюю секунду дня. Если не использовать тильду, документы, созданные в течение последнего дня периода, не попадут в выборку, так как их время в поле DATE_TIME_IDDOC лексикографически больше, чем просто строка даты '20230823'.:НачДата~~): Преобразует параметр типа "Дата" 1С в стандартный тип СУБД DateTime (например, '2023-08-23 00:00:00'). Этот модификатор применяется в случаях работы с обычными полями типа datetime в SQL-таблицах, но не подходит для фильтрации по DATE_TIME_IDDOC.Посмотрим на пример условия фильтрации:
WHERE Журнал.DATE_TIME_IDDOC BETWEEN :НачДата AND :КонДата~
При работе с большими базами данных скорость выполнения прямого запроса имеет решающее значение. В таблице _1SJOURN СУБД MS SQL создает кластерный индекс, построенный по полям DATE_TIME_IDDOC и IDDOCDEF (идентификатор вида документа).
Если в запросе использовать только соединение по IDDOC и фильтрацию по дате, планировщик запросов SQL Server может проигнорировать этот индекс и выполнить сканирование всей таблицы (Index Scan или Table Scan). Это приведет к колоссальным задержкам на базах объемом в десятки гигабайт.
Чтобы заставить SQL Server использовать операцию быстрого поиска по индексу (Index Seek), мы обязаны добавить в условие фильтрации ограничение по виду документа IDDOCDEF:
AND Журнал.IDDOCDEF = $ВидДокумента.Перерасчет
Такое простое дополнение сокращает время выполнения запроса в десятки и сотни раз, так как СУБД сразу отсекает документы всех остальных видов еще на этапе сканирования индекса.
Если вам требуется не просто отфильтровать документы, но и вывести дату каждого документа в результирующую таблицу значений в формате стандартной даты СУБД, используем строковые функции SQL. Мы можем "вырезать" первые 8 символов из поля DATE_TIME_IDDOC и привести их к типу DateTime:
CAST(LEFT(Журнал.DATE_TIME_IDDOC, 8) AS DateTime) AS [ДатаДокумента]
Этот прием позволяет получить полноценную дату на стороне SQL-сервера, которую можно использовать для сортировки, группировки или последующей математической обработки.
Объединим все рассмотренные шаги, правила оптимизации и лучшие практики в один готовый рабочий скрипт для 1С 7.7. Мы также добавим хинт оптимизации WITH (NOLOCK), который предотвращает избыточные блокировки таблиц базы данных при чтении данных активными пользователями.
// Создаем объект для выполнения прямого запроса
Запрос = СоздатьОбъект("ODBCRecordset");
Запрос.УстБД1С();
// Формируем текст запроса с учетом оптимизации
ТекстЗапроса = "
|SELECT
| Журнал.IDDOC [Документ $Документ.Перерасчет],
| $Перерасчет.ЛицевойСчет [ЛицевойСчет $Справочник.ЛицевыеСчета],
| CAST(LEFT(Журнал.DATE_TIME_IDDOC, 8) AS DateTime) AS [ДатаДокумента]
|FROM
| $Документ.Перерасчет AS Перерасчет WITH (NOLOCK)
|INNER JOIN
| _1SJOURN AS Журнал WITH (NOLOCK) ON Перерасчет.IDDOC = Журнал.IDDOC
|WHERE
| Журнал.DATE_TIME_IDDOC BETWEEN :НачДата AND :КонДата~
| AND Журнал.IDDOCDEF = $ВидДокумента.Перерасчет
|";
// Устанавливаем параметры дат
Запрос.УстановитьТекстовыйПараметр("НачДата", ВыбНачДата);
Запрос.УстановитьТекстовыйПараметр("КонДата", ВыбКонДата);
// Выполняем запрос и выгружаем результат в Таблицу Значений
ТаблицаРезультатов = Запрос.ВыполнитьИнструкцию(ТекстЗапроса);
// Теперь ТаблицаРезультатов содержит типизированные колонки,
// готовые к стандартному обходу на стороне 1С
ТаблицаРезультатов.ВыбратьСтроки();
Пока ТаблицаРезультатов.ПолучитьСтроку() = 1 Цикл
Сообщить("Документ: " + ТаблицаРезультатов.Документ + " от " + ТаблицаРезультатов.ДатаДокумента);
КонецЦикла;
Для корректного и быстрого выбора документов по дате в прямых SQL-запросах 1С++ всегда придерживайтесь следующих правил:
_1SJOURN по полю IDDOC.DATE_TIME_IDDOC и модификатор параметра даты с тильдой (:КонДата~), чтобы не потерять документы, проведенные в последний день периода.IDDOCDEF с использованием метаконструкции $ВидДокумента.ИмяДокумента для задействования кластерного индекса СУБД.WITH (NOLOCK) для предотвращения транзакционных блокировок в многопользовательской среде.