При работе с «прямыми» запросами к базе данных 1С 7.7 (используя 1С++ или Rainbow) разработчики часто сталкиваются с ситуацией, когда данные по одному и тому же бизнес-процессу распределены между шапкой документа и его табличной частью. Типичный пример — документ КомплектацияТМЦ. В зависимости от вида операции (Сборка или Разборка), интересующая нас номенклатура может находиться либо в реквизите шапки Комплект, либо в строках табличной части в реквизите Номенклатура.
Попытка собрать такие данные одним запросом через цепочку LEFT JOIN часто приводит к тому, что данные либо «теряются», либо задваиваются. Разберем, почему это происходит и как составить запрос правильно.
Рассмотрим ситуацию, описанную в теме. Автор пытается соединить таблицу журнала _1SJourn с таблицами шапок и строк одновременно. Основная сложность заключается в следующем:
$ДокументСтроки) к журналу по IDDoc, SQL-сервер создает столько строк в результирующей выборке, сколько позиций в документе. Если мы просто используем функцию COUNT, она посчитает количество строк, а не документов.LEFT JOIN и последующей фильтрации по полям этих таблиц в блоке WHERE, логика часто превращается в INNER JOIN, либо условия начинают исключать друг друга, если они завязаны на COALESCE от разных сущностей.Проанализируем предложенное решение — использование оператора 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 внутри подзапросов или группировку.
Проанализируем рекомендации по улучшению запроса:
$ДокументСтроки, добавьте GROUP BY по IDDoc и Номенклатура. Это «схлопнет» несколько строк одного товара в одну запись для корректного подсчета.Date_Time_IDDoc использовалось эффективно. Всегда передавайте параметры даты в правильном формате (строка 23 символа) и старайтесь не применять к этому полю функции в левой части оператора сравнения (кроме SUBSTRING для группировки).$ДокКомплШапка.КодОперации. В 1С 7.7 значения перечислений хранятся как идентификаторы (ID). Для надежности используйте типизацию метаданных 1С++: $Перечисление.КодыОпераций.Сборка.INNER JOIN с временной таблицей #TempTable (в которой лежат нужные товары) гораздо эффективнее, чем LEFT JOIN с последующей проверкой на IS NOT NULL.Если ваша задача — получить уникальное количество документов за период, можно упростить внешний запрос. Рассмотрим пример:
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, обеспечивая надежность и простоту отладки каждого сегмента данных в отдельности.