При разработке отчетов, обработок или проведении документов часто возникает задача извлечь реквизиты (например, дату или номер) из документа-основания. Ситуация усложняется, когда реквизит ДокументОснование имеет составной тип, а цепочка подчиненности имеет разную длину: для одного вида основания дату нужно брать непосредственно из него, а для другого — переходить на уровень выше, к его собственному основанию.
Разберем детально, как правильно построить запрос в 1С для решения этой задачи, какие подводные камни производительности существуют при обращении через точку к составным типам и как оптимизировать выполнение запроса на уровне СУБД.
Рассмотрим базовый сценарий. Допустим, у нас есть документ ДокументА. Если он введен на основании документа ДокументВ, нам требуется дата основания самого ДокументаВ (назовем его ДокументК). Если же основанием является ДокументС, то берется дата самого ДокументаС.
Проанализируем классический подход с использованием конструкции ВЫБОР КОГДА ... ССЫЛКА ...:
ВЫБРАТЬ
МойДок.Ссылка КАК ДокументА,
ВЫБОР
КОГДА МойДок.ДокументОснование ССЫЛКА Документ.ДокументВ
ТОГДА МойДок.ДокументОснование.ДокументОснование.Дата
КОГДА МойДок.ДокументОснование ССЫЛКА Документ.ДокументС
ТОГДА МойДок.ДокументОснование.Дата
ИНАЧЕ NULL
КОНЕЦ КАК ДатаОснования
ИЗ
Документ.ДокументА КАК МойДок
Этот вариант нагляден и прост в написании, однако он имеет серьезный недостаток производительности при работе с составными типами данных.
Важно понимать: обращение через точку к реквизиту составного типа (например, МойДок.ДокументОснование.Дата) заставляет платформу 1С генерировать неявные левые соединения (LEFT JOIN) со всеми таблицами документов, входящими в этот тип. Двойное разыменование МойДок.ДокументОснование.ДокументОснование.Дата каскадно умножает число соединений. Если составной тип включает десятки документов, запрос будет работать крайне медленно.
Чтобы избежать неявных соединений со всеми таблицами составного типа, подскажем оптимизатору платформы точные типы объектов на каждом шаге цепочки с помощью функции ВЫРАЗИТЬ.
Посмотрим на пример оптимизированного запроса:
ВЫБРАТЬ
МойДок.Ссылка КАК ДокументА,
ВЫБОР
КОГДА МойДок.ДокументОснование ССЫЛКА Документ.ДокументВ
ТОГДА ВЫРАЗИТЬ(ВЫРАЗИТЬ(МойДок.ДокументОснование КАК Документ.ДокументВ).ДокументОснование КАК Документ.ДокументК).Дата
КОГДА МойДок.ДокументОснование ССЫЛКА Документ.ДокументС
ТОГДА ВЫРАЗИТЬ(МойДок.ДокументОснование КАК Документ.ДокументС).Дата
ИНАЧЕ ДАТАВРЕМЯ(1, 1, 1)
КОНЕЦ КАК ДатаОснования
ИЗ
Документ.ДокументА КАК МойДок
В этом случае платформа сформирует соединения исключительно с таблицами Документ.ДокументВ, Документ.ДокументК и Документ.ДокументС, что значительно ускорит выполнение запроса в базе данных.
Наиболее контролируемым и производительным с точки зрения СУБД является отказ от разыменования через точку в пользу явных левых соединений. Это исключает скрытую логику платформы и делает план выполнения запроса предсказуемым.
Разберем реализацию с явными соединениями по шагам:
ДокументА.Документ.ДокументВ по ссылке основания.Документ.ДокументК по основанию из ДокументаВ.Документ.ДокументС.ЕСТЬNULL для выбора результирующего значения даты.Посмотрим на пример реализации запроса:
ВЫБРАТЬ
МойДок.Ссылка КАК ДокументА,
ЕСТЬNULL(ДокК.Дата, ЕСТЬNULL(ДокС.Дата, ДАТАВРЕМЯ(1, 1, 1))) КАК ДатаОснования
ИЗ
Документ.ДокументА КАК МойДок
ЛЕВОЕ СОЕДИНЕНИЕ Документ.ДокументВ КАК ДокВ
ПО МойДок.ДокументОснование = ДокВ.Ссылка
ЛЕВОЕ СОЕДИНЕНИЕ Документ.ДокументК КАК ДокК
ПО ДокВ.ДокументОснование = ДокК.Ссылка
ЛЕВОЕ СОЕДИНЕНИЕ Документ.ДокументС КАК ДокС
ПО МойДок.ДокументОснование = ДокС.Ссылка
Если выборка исходных документов велика, а логика ветвления сложна, эффективным решением будет предварительная подготовка данных во временных таблицах.
Рассмотрим пример пакета запросов:
ВЫБРАТЬ
ДокВ.Ссылка КАК СсылкаВ,
ВЫРАЗИТЬ(ДокВ.ДокументОснование КАК Документ.ДокументК).Дата КАК ДатаОснованияК
ПОМЕСТИТЬ ВТ_ДатыОснованийВ
ИЗ
Документ.ДокументВ КАК ДокВ;
////////////////////////////////////////////////////////////////////////////////
ВЫБРАТЬ
МойДок.Ссылка КАК ДокументА,
ЕСТЬNULL(ВТ.ДатаОснованияК, ВЫРАЗИТЬ(МойДок.ДокументОснование КАК Документ.ДокументС).Дата) КАК ДатаОснования
ИЗ
Документ.ДокументА КАК МойДок
ЛЕВОЕ СОЕДИНЕНИЕ ВТ_ДатыОснованийВ КАК ВТ
ПО МойДок.ДокументОснование = ВТ.СсылкаВ
При работе со связями документов важно учитывать поведение незаполненных реквизитов:
ДокументОснование не заполнен, он может содержать либо пустую ссылку определенного типа (например, ЗНАЧЕНИЕ(Документ.ДокументВ.ПустаяСсылка)), либо значение Неопределено.01.01.0001 00:00:00), тогда как при отсутствии строки в левом соединении СУБД возвращает значение NULL.ЕСТЬNULL(Поле, ДАТАВРЕМЯ(1, 1, 1)), чтобы избежать ошибок при дальнейшей обработке полученных данных в коде 1С.