Как в запросе 1С получить дату документа-основания разного типа и с разной глубиной цепочки?

Программист 1С v8.3 (Управляемые формы) IT и автоматизация бизнеса
← На главную

При разработке отчетов, обработок или проведении документов часто возникает задача извлечь реквизиты (например, дату или номер) из документа-основания. Ситуация усложняется, когда реквизит ДокументОснование имеет составной тип, а цепочка подчиненности имеет разную длину: для одного вида основания дату нужно брать непосредственно из него, а для другого — переходить на уровень выше, к его собственному основанию.

Разберем детально, как правильно построить запрос в 1С для решения этой задачи, какие подводные камни производительности существуют при обращении через точку к составным типам и как оптимизировать выполнение запроса на уровне СУБД.

Вариант 1. Использование условной конструкции ВЫБОР КОГДА и оператора ССЫЛКА

Рассмотрим базовый сценарий. Допустим, у нас есть документ ДокументА. Если он введен на основании документа ДокументВ, нам требуется дата основания самого ДокументаВ (назовем его ДокументК). Если же основанием является ДокументС, то берется дата самого ДокументаС.

Проанализируем классический подход с использованием конструкции ВЫБОР КОГДА ... ССЫЛКА ...:


ВЫБРАТЬ
    МойДок.Ссылка КАК ДокументА,
    ВЫБОР
        КОГДА МойДок.ДокументОснование ССЫЛКА Документ.ДокументВ
            ТОГДА МойДок.ДокументОснование.ДокументОснование.Дата
        КОГДА МойДок.ДокументОснование ССЫЛКА Документ.ДокументС
            ТОГДА МойДок.ДокументОснование.Дата
        ИНАЧЕ NULL
    КОНЕЦ КАК ДатаОснования
ИЗ
    Документ.ДокументА КАК МойДок

Этот вариант нагляден и прост в написании, однако он имеет серьезный недостаток производительности при работе с составными типами данных.

Важно понимать: обращение через точку к реквизиту составного типа (например, МойДок.ДокументОснование.Дата) заставляет платформу 1С генерировать неявные левые соединения (LEFT JOIN) со всеми таблицами документов, входящими в этот тип. Двойное разыменование МойДок.ДокументОснование.ДокументОснование.Дата каскадно умножает число соединений. Если составной тип включает десятки документов, запрос будет работать крайне медленно.

Вариант 2. Оптимизация разыменования с помощью функции ВЫРАЗИТЬ

Чтобы избежать неявных соединений со всеми таблицами составного типа, подскажем оптимизатору платформы точные типы объектов на каждом шаге цепочки с помощью функции ВЫРАЗИТЬ.

Посмотрим на пример оптимизированного запроса:


ВЫБРАТЬ
    МойДок.Ссылка КАК ДокументА,
    ВЫБОР
        КОГДА МойДок.ДокументОснование ССЫЛКА Документ.ДокументВ
            ТОГДА ВЫРАЗИТЬ(ВЫРАЗИТЬ(МойДок.ДокументОснование КАК Документ.ДокументВ).ДокументОснование КАК Документ.ДокументК).Дата
        КОГДА МойДок.ДокументОснование ССЫЛКА Документ.ДокументС
            ТОГДА ВЫРАЗИТЬ(МойДок.ДокументОснование КАК Документ.ДокументС).Дата
        ИНАЧЕ ДАТАВРЕМЯ(1, 1, 1)
    КОНЕЦ КАК ДатаОснования
ИЗ
    Документ.ДокументА КАК МойДок

В этом случае платформа сформирует соединения исключительно с таблицами Документ.ДокументВ, Документ.ДокументК и Документ.ДокументС, что значительно ускорит выполнение запроса в базе данных.

Вариант 3. Использование явных левых соединений (LEFT JOIN)

Наиболее контролируемым и производительным с точки зрения СУБД является отказ от разыменования через точку в пользу явных левых соединений. Это исключает скрытую логику платформы и делает план выполнения запроса предсказуемым.

Разберем реализацию с явными соединениями по шагам:

  1. Выбираем исходный документ ДокументА.
  2. Явно соединяем его с таблицей Документ.ДокументВ по ссылке основания.
  3. Соединяем результат с таблицей родительского документа Документ.ДокументК по основанию из ДокументаВ.
  4. Соединяем исходный документ с альтернативной таблицей Документ.ДокументС.
  5. Используем функцию ЕСТЬNULL для выбора результирующего значения даты.

Посмотрим на пример реализации запроса:


ВЫБРАТЬ
    МойДок.Ссылка КАК ДокументА,
    ЕСТЬNULL(ДокК.Дата, ЕСТЬNULL(ДокС.Дата, ДАТАВРЕМЯ(1, 1, 1))) КАК ДатаОснования
ИЗ
    Документ.ДокументА КАК МойДок
    ЛЕВОЕ СОЕДИНЕНИЕ Документ.ДокументВ КАК ДокВ
        ПО МойДок.ДокументОснование = ДокВ.Ссылка
    ЛЕВОЕ СОЕДИНЕНИЕ Документ.ДокументК КАК ДокК
        ПО ДокВ.ДокументОснование = ДокК.Ссылка
    ЛЕВОЕ СОЕДИНЕНИЕ Документ.ДокументС КАК ДокС
        ПО МойДок.ДокументОснование = ДокС.Ссылка

Вариант 4. Разделение выборки через временные таблицы

Если выборка исходных документов велика, а логика ветвления сложна, эффективным решением будет предварительная подготовка данных во временных таблицах.

Рассмотрим пример пакета запросов:


ВЫБРАТЬ
    ДокВ.Ссылка КАК СсылкаВ,
    ВЫРАЗИТЬ(ДокВ.ДокументОснование КАК Документ.ДокументК).Дата КАК ДатаОснованияК
ПОМЕСТИТЬ ВТ_ДатыОснованийВ
ИЗ
    Документ.ДокументВ КАК ДокВ;

////////////////////////////////////////////////////////////////////////////////
ВЫБРАТЬ
    МойДок.Ссылка КАК ДокументА,
    ЕСТЬNULL(ВТ.ДатаОснованияК, ВЫРАЗИТЬ(МойДок.ДокументОснование КАК Документ.ДокументС).Дата) КАК ДатаОснования
ИЗ
    Документ.ДокументА КАК МойДок
    ЛЕВОЕ СОЕДИНЕНИЕ ВТ_ДатыОснованийВ КАК ВТ
        ПО МойДок.ДокументОснование = ВТ.СсылкаВ

Особенности обработки пустых ссылок и значений NULL

При работе со связями документов важно учитывать поведение незаполненных реквизитов:

  1. Если реквизит ДокументОснование не заполнен, он может содержать либо пустую ссылку определенного типа (например, ЗНАЧЕНИЕ(Документ.ДокументВ.ПустаяСсылка)), либо значение Неопределено.
  2. При обращении через точку к незаполненной ссылке возвращается пустая дата (01.01.0001 00:00:00), тогда как при отсутствии строки в левом соединении СУБД возвращает значение NULL.
  3. Для унификации результата всегда оборачивайте вычисляемые даты в функцию ЕСТЬNULL(Поле, ДАТАВРЕМЯ(1, 1, 1)), чтобы избежать ошибок при дальнейшей обработке полученных данных в коде 1С.
← На главную