Как правильно составить прямой SQL-запрос в 1С 7.7 для подсчета документов с данными в шапке и ТЧ

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

При работе с «прямыми» запросами к базе данных 1С 7.7 (используя 1С++ или Rainbow) разработчики часто сталкиваются с ситуацией, когда данные по одному и тому же бизнес-процессу распределены между шапкой документа и его табличной частью. Типичный пример — документ КомплектацияТМЦ. В зависимости от вида операции (Сборка или Разборка), интересующая нас номенклатура может находиться либо в реквизите шапки Комплект, либо в строках табличной части в реквизите Номенклатура.

Попытка собрать такие данные одним запросом через цепочку LEFT JOIN часто приводит к тому, что данные либо «теряются», либо задваиваются. Разберем, почему это происходит и как составить запрос правильно.

Анализ проблемы: почему классический JOIN может подвести

Рассмотрим ситуацию, описанную в теме. Автор пытается соединить таблицу журнала _1SJourn с таблицами шапок и строк одновременно. Основная сложность заключается в следующем:

  1. Умножение строк: Когда мы присоединяем табличную часть ($ДокументСтроки) к журналу по IDDoc, SQL-сервер создает столько строк в результирующей выборке, сколько позиций в документе. Если мы просто используем функцию COUNT, она посчитает количество строк, а не документов.
  2. Конфликт условий в WHERE: При использовании нескольких LEFT JOIN и последующей фильтрации по полям этих таблиц в блоке WHERE, логика часто превращается в INNER JOIN, либо условия начинают исключать друг друга, если они завязаны на COALESCE от разных сущностей.
  3. Ограничения SQL Server 2000: В старых версиях СУБД оптимизатор может некорректно строить план выполнения для тяжелых соединений с использованием функций в условиях связи, что приводит к критическому падению производительности.

Проанализируем предложенное решение — использование оператора UNION ALL.

Решение: Разделение логики через UNION ALL

Вместо того чтобы пытаться «впихнуть» все условия в один огромный набор соединений, мы разделим задачу на три независимых потока данных и объединим их. Это сделает код прозрачным, а работу SQL-сервера — предсказуемой.

Рассмотрим структуру запроса по шагам:

Шаг 1. Выборка из Возвратов (по табличной части). Здесь нас интересует номенклатура, указанная в строках документа.

Шаг 2. Выборка из Комплектации (Сборка). В этой операции мы ищем номенклатуру, которая является комплектующей (в ТЧ).

Шаг 3. Выборка из Комплектации (Разборка). Здесь мы ищем основной товар, который разбирают (в шапке).

Посмотрим на пример реализации такого подхода:


SELECT
    Res.Дата as [Дата],
    Res.Товар as [Товар],
    COUNT(Res.IDDOC) as [Приход]
FROM (
    -- Блок 1: Возвраты (ТЧ)
    SELECT
        SUBSTRING(Жур.Date_Time_IDDoc, 1, 8) as Дата,
        $ДокВ.Номенклатура as Товар,
        Жур.IDDoc as IDDOC
    FROM _1SJourn AS Жур
    INNER JOIN $ДокументСтроки.ВозвратОтПокупателя as ДокВ ON ДокВ.IDDoc = Жур.IDDoc
    WHERE Жур.Date_Time_IDDoc BETWEEN :НачДата AND :КонДата~
      AND Жур.iddocdef = $ВидДокумента.ВозвратОтПокупателя
      AND Жур.Closed & 1 = 1

    UNION ALL

    -- Блок 2: Комплектация - Сборка (ТЧ)
    SELECT
        SUBSTRING(Жур.Date_Time_IDDoc, 1, 8),
        $ДокКомплТЧ.Номенклатура,
        Жур.IDDoc
    FROM _1SJourn AS Жур
    INNER JOIN $ДокументСтроки.КомплектацияТМЦ as ДокКомплТЧ ON ДокКомплТЧ.IDDoc = Жур.IDDoc
    INNER JOIN $Документ.КомплектацияТМЦ as ДокКомплШапка ON ДокКомплШапка.IDDoc = Жур.IDDoc
    WHERE Жур.Date_Time_IDDoc BETWEEN :НачДата AND :КонДата~
      AND Жур.iddocdef = $ВидДокумента.КомплектацияТМЦ
      AND $ДокКомплШапка.КодОперации = :ТипКомплектации_Сборка
      AND Жур.Closed & 1 = 1

    UNION ALL

    -- Блок 3: Комплектация - Разборка (Шапка)
    SELECT
        SUBSTRING(Жур.Date_Time_IDDoc, 1, 8),
        $ДокКомплШапка.Комплект,
        Жур.IDDoc
    FROM _1SJourn AS Жур
    INNER JOIN $Документ.КомплектацияТМЦ as ДокКомплШапка ON ДокКомплШапка.IDDoc = Жур.IDDoc
    WHERE Жур.Date_Time_IDDoc BETWEEN :НачДата AND :КонДата~
      AND Жур.iddocdef = $ВидДокумента.КомплектацияТМЦ
      AND $ДокКомплШапка.КодОперации = :ТипКомплектации_Разборка
      AND Жур.Closed & 1 = 1
) AS Res
INNER JOIN #TempTable as фильтрТоваров ON Res.Товар = фильтрТоваров.val
GROUP BY Res.Дата, Res.Товар

Важные нюансы и оптимизация

Выясним причину, по которой даже такой запрос может выдавать неверные цифры. Если в одном документе ВозвратОтПокупателя одна и та же номенклатура встречается в двух разных строках, то UNION ALL вернет две записи для одного IDDoc. Чтобы посчитать именно количество документов, а не количество строк, необходимо использовать DISTINCT внутри подзапросов или группировку.

Проанализируем рекомендации по улучшению запроса:

  1. Борьба с дублями ТЧ: В блоках, где используется таблица $ДокументСтроки, добавьте GROUP BY по IDDoc и Номенклатура. Это «схлопнет» несколько строк одного товара в одну запись для корректного подсчета.
  2. Использование индексов: Для SQL Server 2000 критически важно, чтобы поле Date_Time_IDDoc использовалось эффективно. Всегда передавайте параметры даты в правильном формате (строка 23 символа) и старайтесь не применять к этому полю функции в левой части оператора сравнения (кроме SUBSTRING для группировки).
  3. Типизация перечислений: Обратите внимание на $ДокКомплШапка.КодОперации. В 1С 7.7 значения перечислений хранятся как идентификаторы (ID). Для надежности используйте типизацию метаданных 1С++: $Перечисление.КодыОпераций.Сборка.
  4. Фильтрация: Использование INNER JOIN с временной таблицей #TempTable (в которой лежат нужные товары) гораздо эффективнее, чем LEFT JOIN с последующей проверкой на IS NOT NULL.

Использование COUNT DISTINCT

Если ваша задача — получить уникальное количество документов за период, можно упростить внешний запрос. Рассмотрим пример:


SELECT
    Res.Товар as [Товар],
    COUNT(DISTINCT Res.IDDOC) as [КоличествоДокументов]
FROM (
    -- Тут ваши UNION подзапросы
) AS Res
GROUP BY Res.Товар

Важно: COUNT(DISTINCT ...) в SQL 2000 может работать медленно на больших объемах данных. Если производительность падает, лучше предварительно сгруппировать данные внутри каждого подзапроса блока UNION.

Таким образом, разделяя логику запроса на понятные части, мы избавляемся от проблем с COALESCE и некорректными связями. Метод с UNION ALL является «золотым стандартом» для сложных выборок в 1С 7.7, обеспечивая надежность и простоту отладки каждого сегмента данных в отдельности.

← На главную