При разработке сложных отчетов в среде 1С 7.7 программисты часто сталкиваются с необходимостью получения агрегированных данных, которые стандартные средства платформы (так называемые «черные» запросы) обрабатывают слишком медленно. Одной из таких задач является подсчет количества уникальных дней из выбранного периода, в которые происходило поступление конкретной позиции номенклатуры. Важное условие: если в один день было несколько документов «ПоступлениеТМЦ» по одной и той же номенклатуре, этот день должен считаться в итоговом результате как один.
В этой статье мы подробно разберем, как реализовать подобную логику, используя мощь прямых SQL-запросов и внешней компоненты 1C++. Мы проанализируем структуру хранения данных, рассмотрим синтаксис запроса и методы оптимизации производительности.
Прежде чем приступать к написанию кода, давайте разберем, где именно хранятся нужные нам данные. В отличие от 1С 8.x, в «семерке» информация о документах распределена следующим образом:
IDDOC. Поле DATE_TIME_IDDOC хранит дату в строковом формате (первые 8 символов — это ГГГГММДД).$ДокументСтроки.ПоступлениеТМЦ.Наша задача — соединить эти таблицы и сгруппировать данные так, чтобы исключить дублирование дат для одной и той же номенклатуры.
Рассмотрим первый классический вариант. Идея заключается в том, чтобы сначала получить уникальные пары «Дата + Номенклатура» во внутреннем запросе, а затем во внешнем — просто подсчитать количество строк для каждой номенклатуры.
Разберем пример кода с использованием метапарсера:
|SELECT
| Док.Номенклатура as [Номенклатура $Справочник.Номенклатура],
| COUNT(*) as КолвоДней
|FROM
| (
| SELECT
| LEFT(Журнал.DATE_TIME_IDDOC, 8) as ДатаДок,
| ТЧ.$Номенклатура as Номенклатура
| FROM
| $ДокументСтроки.ПоступлениеТМЦ as ТЧ
| INNER JOIN
| _1SJOURN as Журнал ON ТЧ.IDDOC = Журнал.IDDOC
| WHERE
| Журнал.DATE_TIME_IDDOC BETWEEN :НачДата AND :КонДата~
| AND Журнал.IDDOCDEF = $ВидДокумента.ПоступлениеТМЦ
| AND Журнал.ISMARK = 0
| AND ТЧ.$Номенклатура IN (SELECT val FROM #СписокНоменклатуры)
| GROUP BY
| LEFT(Журнал.DATE_TIME_IDDOC, 8),
| ТЧ.$Номенклатура
| ) as Док
|GROUP BY
| Док.Номенклатура
В данном примере мы используем LEFT(Журнал.DATE_TIME_IDDOC, 8), чтобы отсечь время и уникальный идентификатор документа, оставив только чистую дату в формате YYYYMMDD. Обратите внимание на фильтрацию ISMARK = 0, которая позволяет исключить документы, помеченные на удаление.
Проанализируем более элегантный способ решения задачи. В MS SQL Server существует конструкция COUNT(DISTINCT ...), которая позволяет выполнить расчет уникальных значений в одной группировке, не прибегая к вложенным подзапросам. Это может работать быстрее, так как серверу базы данных проще построить оптимальный план выполнения.
Посмотрим на пример такого запроса:
|SELECT
| ТЧ.$Номенклатура as [Номенклатура $Справочник.Номенклатура],
| COUNT(DISTINCT LEFT(Журнал.DATE_TIME_IDDOC, 8)) as КолвоДней
|FROM
| $ДокументСтроки.ПоступлениеТМЦ as ТЧ
|INNER JOIN
| _1SJOURN as Журнал ON ТЧ.IDDOC = Журнал.IDDOC
|WHERE
| Журнал.DATE_TIME_IDDOC BETWEEN :НачДата AND :КонДата~
| AND Журнал.IDDOCDEF = $ВидДокумента.ПоступлениеТМЦ
| AND Журнал.ISMARK = 0
| AND ТЧ.$Номенклатура IN (SELECT val FROM #СписокНоменклатуры)
|GROUP BY
| ТЧ.$Номенклатура
Выясним причину, почему этот вариант предпочтительнее. Во-первых, код становится более читаемым. Во-вторых, мы избавляемся от лишнего сканирования временных наборов данных, которые формируются во вложенных SELECT. Для СУБД это означает меньшую нагрузку на tempdb.
Если список номенклатуры, который передается в запрос, достаточно велик, использование оператора IN (:Список) может привести к падению производительности или ошибкам ограничения длины строки запроса. Профессиональным подходом в 1C++ является использование метода УложитьСписокОбъектов().
Рассмотрим, как это работает на практике:
Query.УложитьСписокОбъектов(СписокНоменклатуры, "#СписокНоменклатуры", "Номенклатура").#СписокНоменклатуры через подзапрос SELECT val FROM #СписокНоменклатуры.Это гарантирует, что запрос отработает молниеносно даже при передаче тысяч позиций товаров.
Проанализируем ситуацию, когда база данных очень большая. Если запросы к табличным частям документов (DT*) занимают много времени, стоит проверить наличие индексов. В 1С 7.7 по умолчанию индекс по номенклатуре в строках документов может отсутствовать или быть неэффективным.
Альтернативный путь — использование таблиц движений регистров. Если документ «ПоступлениеТМЦ» делает приход по регистру (например, «ОстаткиТМЦ»), иногда быстрее выбрать данные из таблицы движений регистра ($Регистр.ОстаткиТМЦ), так как она индексирована по номенклатуре и дате по умолчанию. Однако в этом случае нужно обязательно фильтровать движения по типу документа-регистратора.
Мы рассмотрели несколько подходов к расчету уникальных дней поступления товаров. Использование прямого SQL-запроса позволяет сократить время формирования отчета в десятки раз по сравнению со стандартными методами 1С 7.7. Для достижения максимальной скорости рекомендуем использовать конструкцию COUNT(DISTINCT ...) и предварительную выгрузку списка фильтрации во временные таблицы SQL.