При разработке отчетов и обработок на платформе 1С 7.7 с применением библиотеки 1С++ и прямых SQL-запросов нередко возникает задача: в табличной части или шапке документа присутствует реквизит с составным (неопределенным) типом Документ. В этом поле могут храниться документы разных видов (например, Полуфабрикат1 или Полуфабрикат2), и из каждого вида требуется получить значение конкретного реквизита (например, НомерИзвещения).
Разберем детально, как устроено хранение таких данных на уровне СУБД, почему нельзя просто соединять таблицы по 9-значному идентификатору и как правильно составить SQL-запрос с использованием специализированных макросов 1С++.
Проанализируем внутреннее представление реквизитов составного типа в таблицах 1С 7.7:
Документ без указания конкретного вида хранится в базе как фиксированная строка CHAR(13).LEFT(Поле, 4)) содержат 36-ричный идентификатор вида документа (код метаданных), а оставшиеся 9 символов (RIGHT(Поле, 9)) представляют собой уникальный идентификатор конкретного документа — IDDOC.IDDOC уникальны только в рамках одного вида документа или в журнале документов, но между таблицами шапок разных видов документов значения IDDOC могут пересекаться. Поэтому соединение только по 9 правым символам без проверки вида объекта является грубой ошибкой и может привести к дублированию или искажению данных.Библиотека 1С++ предоставляет макросы для работы с метаданными. Важно понимать различие между ними:
$ВидДокумента.ИмяВида — в среде MS SQL возвращает числовое значение типа int, используемое для фильтрации поля IDDOCDEF в общей таблице журнала _1SJOURN.$ВидДокумента36.ИмяВида — возвращает строку длиной 4 символа (CHAR(4)) в 36-ричной системе счисления. Именно этот макрос предназначен для сравнения с первыми 4 символами составного поля.Поскольку в SQL невозможно динамически подставить имя таблицы в секцию JOIN с помощью конструкции CASE, мы должны явно соединить каждую потенциальную таблицу документа через LEFT JOIN. В условии соединения обязательно проверяем как 4 символа вида документа, так и 9 символов его идентификатора.
Посмотрим на практический пример запроса, который выбирает строки табличной части документа ВыпускПродукции и извлекает НомерИзвещения из документов Полуфабрикат1 или Полуфабрикат2:
SELECT
ДокВыпСтр.IDDOC AS [Док $Документ.ВыпускПродукции],
ДокВыпСтр.$Полуфабрикат AS [ПФ $Документ],
COALESCE(ПФ1.$НомерИзвещения, ПФ2.$НомерИзвещения, '') AS НомерИзвещения
FROM $Документ.ВыпускПродукцииСтроки AS ДокВыпСтр
LEFT JOIN _1SJOURN AS Жур ON Жур.IDDOC = ДокВыпСтр.IDDOC
LEFT JOIN $Документ.Полуфабрикат1 AS ПФ1
ON LEFT(ДокВыпСтр.$Полуфабрикат, 4) = $ВидДокумента36.Полуфабрикат1
AND RIGHT(ДокВыпСтр.$Полуфабрикат, 9) = ПФ1.IDDOC
LEFT JOIN $Документ.Полуфабрикат2 AS ПФ2
ON LEFT(ДокВыпСтр.$Полуфабрикат, 4) = $ВидДокумента36.Полуфабрикат2
AND RIGHT(ДокВыпСтр.$Полуфабрикат, 9) = ПФ2.IDDOC
WHERE Жур.DATE_TIME_IDDOC BETWEEN :НачДата AND :КонДата~
AND Жур.CLOSED & 1 = 1
Разберем ключевые элементы этого запроса:
[ПФ $Документ] сообщает 1С++, что 13-символьную строку необходимо типизировать в объект типа «Документ» при выгрузке данных в ТаблицуЗначений.LEFT(ДокВыпСтр.$Полуфабрикат, 4) = $ВидДокумента36.ИмяВида и RIGHT(ДокВыпСтр.$Полуфабрикат, 9) = ПФ.IDDOC обеспечивается точная привязка конкретной строки только к нужной таблице метаданных.COALESCE(...) (или конструкция CASE WHEN ... THEN ... END) выбирает первое непустое значение реквизита из успешно присоединенной таблицы.Рассмотрим дополнительные сценарии в зависимости от структуры вашей конфигурации:
НомерИзвещения является общим реквизитом документов и включен для хранения в общем журнале _1SJOURN, присоединять таблицы шапок документов $Документ.Полуфабрикат1 и $Документ.Полуфабрикат2 не требуется. Достаточно выполнить LEFT JOIN _1SJOURN по условию _1SJOURN.IDDOC = RIGHT(ДокВыпСтр.$Полуфабрикат, 9) и выбрать поле общего реквизита.LEFT JOIN начинают снижать производительность запроса, эффективнее разбить выборку на несколько запросов с фильтрацией WHERE LEFT(ДокВыпСтр.$Полуфабрикат, 4) = $ВидДокумента36... и объединить результаты через UNION ALL.WHERE или в условие соединения проверку на заполненность поля: ДокВыпСтр.$Полуфабрикат <> ' '.