Как подсчитать количество уникальных дней поступления номенклатуры в 1С 7.7 с помощью прямого SQL-запроса

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

При разработке сложных отчетов в среде 1С 7.7 программисты часто сталкиваются с необходимостью получения агрегированных данных, которые стандартные средства платформы (так называемые «черные» запросы) обрабатывают слишком медленно. Одной из таких задач является подсчет количества уникальных дней из выбранного периода, в которые происходило поступление конкретной позиции номенклатуры. Важное условие: если в один день было несколько документов «ПоступлениеТМЦ» по одной и той же номенклатуре, этот день должен считаться в итоговом результате как один.

В этой статье мы подробно разберем, как реализовать подобную логику, используя мощь прямых SQL-запросов и внешней компоненты 1C++. Мы проанализируем структуру хранения данных, рассмотрим синтаксис запроса и методы оптимизации производительности.

Понимание структуры данных 1С 7.7 в SQL

Прежде чем приступать к написанию кода, давайте разберем, где именно хранятся нужные нам данные. В отличие от 1С 8.x, в «семерке» информация о документах распределена следующим образом:

  1. Журнал документов (_1SJOURN): Здесь содержатся общие реквизиты всех документов: дата, время, вид документа (IDDOCDEF), пометка на удаление и уникальный идентификатор IDDOC. Поле DATE_TIME_IDDOC хранит дату в строковом формате (первые 8 символов — это ГГГГММДД).
  2. Табличные части (DT*): Данные о номенклатуре в строках документа «ПоступлениеТМЦ» хранятся в соответствующей таблице табличных частей. С помощью метапарсера 1C++ мы обращаемся к ней как $ДокументСтроки.ПоступлениеТМЦ.

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

Решение через вложенный запрос с использованием GROUP BY

Рассмотрим первый классический вариант. Идея заключается в том, чтобы сначала получить уникальные пары «Дата + Номенклатура» во внутреннем запросе, а затем во внешнем — просто подсчитать количество строк для каждой номенклатуры.

Разберем пример кода с использованием метапарсера:


|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, которая позволяет исключить документы, помеченные на удаление.

Оптимизация запроса через COUNT(DISTINCT ...)

Проанализируем более элегантный способ решения задачи. В 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++ является использование метода УложитьСписокОбъектов().

Рассмотрим, как это работает на практике:

  1. Создаем объект класса Query.
  2. Используем метод УложитьСписокОбъектов(СписокНоменклатуры, "#СписокНоменклатуры", "Номенклатура").
  3. В тексте SQL-запроса обращаемся к временной таблице #СписокНоменклатуры через подзапрос SELECT val FROM #СписокНоменклатуры.

Это гарантирует, что запрос отработает молниеносно даже при передаче тысяч позиций товаров.

Дополнительные рекомендации по производительности

Проанализируем ситуацию, когда база данных очень большая. Если запросы к табличным частям документов (DT*) занимают много времени, стоит проверить наличие индексов. В 1С 7.7 по умолчанию индекс по номенклатуре в строках документов может отсутствовать или быть неэффективным.

Альтернативный путь — использование таблиц движений регистров. Если документ «ПоступлениеТМЦ» делает приход по регистру (например, «ОстаткиТМЦ»), иногда быстрее выбрать данные из таблицы движений регистра ($Регистр.ОстаткиТМЦ), так как она индексирована по номенклатуре и дате по умолчанию. Однако в этом случае нужно обязательно фильтровать движения по типу документа-регистратора.

Подведение итогов

Мы рассмотрели несколько подходов к расчету уникальных дней поступления товаров. Использование прямого SQL-запроса позволяет сократить время формирования отчета в десятки раз по сравнению со стандартными методами 1С 7.7. Для достижения максимальной скорости рекомендуем использовать конструкцию COUNT(DISTINCT ...) и предварительную выгрузку списка фильтрации во временные таблицы SQL.

← На главную