При работе с прямыми SQL-запросами в 1С 7.7 с использованием внешней компоненты 1С++ программисты часто сталкиваются с ситуацией, когда синтаксически правильный запрос возвращает пустой результат. Особенно часто это происходит при попытке собрать данные из нескольких видов документов (например, "Реализация", "РеализацияРозница" и "РеализацияРаспоряжение") в один отчет. В этой статье мы подробно разберем, почему стандартные соединения INNER JOIN не работают в таких случаях, и как правильно спроектировать запрос для получения верных данных.
Разберем типичную ситуацию: автору необходимо подсчитать количество продаж определенного товара по трем разным видам документов. В исходном запросе использовалось последовательное соединение таблиц через INNER JOIN по полю IDDoc. С точки зрения архитектуры базы данных 1С 7.7, это критическая ошибка.
Выясним причину: в 1С 7.7 данные каждого вида документа хранятся в своих физических таблицах (шапки в DH*, строки в DT*). Поле IDDoc является уникальным идентификатором документа во всей системе. Один и тот же документ не может одновременно являться и "Реализацией", и "Розницей". Когда мы пишем INNER JOIN между тремя таблицами разных документов по IDDoc, мы буквально просим SQL-сервер: "Найди мне такую строку, идентификатор которой одновременно существует во всех трех таблицах". Естественно, таких строк не существует, и результат всегда будет пустым.
Даже добавление условий в блок WHERE по полю IDDOCDEF (идентификатор вида документа) не исправит ситуацию, так как сама логика соединения таблиц остается ошибочной. Рассмотрим правильные способы решения этой задачи.
Самый логичный способ собрать данные из разных таблиц — это выполнить выборку из каждой таблицы по отдельности и объединить результаты с помощью оператора UNION ALL. Рассмотрим, как это реализовать структурно.
Проанализируем пример кода, который позволяет получить общие данные по всем документам:
SELECT
SubQuery.Номенклатура [Номенклатура],
SUM(SubQuery.КолПродаж) AS ОбщееКолПродаж
FROM (
SELECT
$ДокР.Номенклатура [Номенклатура],
COUNT(DISTINCT ДокР.IDDoc) AS КолПродаж
FROM
_1SJourn Жур
INNER JOIN
$ДокументСтроки.Реализация AS ДокР ON ДокР.IDDoc = Жур.IDDoc
WHERE
Жур.Date_Time_IDDoc BETWEEN :НачДата AND :КонДата~
AND (Жур.CLOSED & 1 = 1)
AND ($ДокР.Номенклатура = :ВбрТовар)
GROUP BY $ДокР.Номенклатура
UNION ALL
SELECT
$ДокРоз.Номенклатура [Номенклатура],
COUNT(DISTINCT ДокРоз.IDDoc) AS КолПродаж
FROM
_1SJourn Жур
INNER JOIN
$ДокументСтроки.Реализация_Розница AS ДокРоз ON ДокРоз.IDDoc = Жур.IDDoc
WHERE
Жур.Date_Time_IDDoc BETWEEN :НачДата AND :КонДата~
AND (Жур.CLOSED & 1 = 1)
AND ($ДокРоз.Номенклатура = :ВбрТовар)
GROUP BY $ДокРоз.Номенклатура
UNION ALL
SELECT
$ДокРасп.Номенклатура [Номенклатура],
COUNT(DISTINCT ДокРасп.IDDoc) AS КолПродаж
FROM
_1SJourn Жур
INNER JOIN
$ДокументСтроки.Реализация_Распоряжение AS ДокРасп ON ДокРасп.IDDoc = Жур.IDDoc
WHERE
Жур.Date_Time_IDDoc BETWEEN :НачДата AND :КонДата~
AND (Жур.CLOSED & 1 = 1)
AND ($ДокРасп.Номенклатура = :ВбрТовар)
GROUP BY $ДокРасп.Номенклатура
) AS SubQuery
GROUP BY SubQuery.Номенклатура
Важные моменты в этом решении:
UNION ALL нам необходимо еще раз сгруппировать данные по номенклатуре и просуммировать значения КолПродаж, чтобы получить итоговую цифру.:КонДата~. В 1С++ символ тильды после параметра даты автоматически добавляет время 23:59:59, что позволяет корректно захватить документы за последнюю секунду дня.(Жур.CLOSED & 1 = 1) гарантирует, что мы учитываем только проведенные документы.Если задача стоит в получении итоговых данных по продажам, то обращение напрямую к таблицам строк документов (DT*) является не самым эффективным методом. В 1С 7.7 для этих целей существуют регистры. Рассмотрим, почему это лучше.
В регистрах накопления (например, "ПартииТМЦ" или "Продажи") данные уже консолидированы. Вместо того чтобы сканировать три разные таблицы документов, мы можем обратиться к одной таблице движений регистра RA*. Это значительно ускоряет выполнение запроса, так как таблицы регистров оптимизированы для аналитики.
Разберем пример запроса к регистру:
SELECT
COUNT(DISTINCT Рег.IDDoc) AS КолПродаж,
$Рег.Товар AS [Товар $Справочник.Номенклатура]
FROM
$Регистр.ПартииТМЦ AS Рег
INNER JOIN
_1SJourn AS Жур ON Рег.IDDoc = Жур.IDDoc
WHERE
Жур.Date_Time_IDDoc BETWEEN :НачДата AND :КонДата~
AND $Рег.Товар = :ВбрТовар
AND Жур.IDDocDef IN ($ВидДокумента.Реализация, $ВидДокумента.Реализация_Розница, $ВидДокумента.Реализация_Распоряжение)
GROUP BY
$Рег.Товар
Преимущества данного подхода:
UNION ALL, достаточно фильтрации по IDDocDef.Для того чтобы ваши запросы были читаемыми и переносимыми, всегда используйте возможности метапарсера 1С++. Проанализируем основные правила, которые помогут избежать ошибок:
1. Обращение к полям: Вместо прямых имен таблиц и полей используйте конструкции типа $ДокументСтроки.ИмяДокумента и $ДокументСтроки.ИмяДокумента.Номенклатура. Это позволит 1С++ самостоятельно подставить актуальные имена таблиц (DT*) и колонок (SP*).
2. Работа с типами: При использовании LEFT JOIN помните, что если данных в присоединенной таблице нет, SQL вернет NULL. В 1С 7.7 это может привести к ошибкам в агрегатных функциях. Используйте COALESCE(Поле, 0) или ISNULL(Поле, 0) для обработки пустых значений.
3. Индексация: Поле Date_Time_IDDoc в таблице _1SJourn является частью кластерного индекса. Всегда ставьте условие по этому полю в начало блока WHERE, чтобы запрос работал максимально быстро.
Подводя итог, можно сказать, что решение проблемы "пустого результата" заключается в понимании структуры хранения данных 1С 7.7. Используйте UNION ALL, если вам действительно нужны данные из ТЧ документов, или переходите на запросы к регистрам для повышения производительности и упрощения кода.