Как правильно передать табличную часть справочника в запрос и соединить с регистром сведений?

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

При разработке отчетов и обработок в среде «1С:Предприятие» перед разработчиками часто возникает задача: выбрать данные из табличной части (ТЧ) некоторого объекта (документа или справочника), а затем по полученному списку получить срез последних данных из регистра сведений. На первый взгляд задача кажется тривиальной, однако при ее реализации легко совершить архитектурные ошибки, которые приведут либо к неверному результату, либо к катастрофическому падению производительности системы.

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

Анализ исходной ситуации и выявление ключевых ошибок

Рассмотрим ситуацию, с которой столкнулся автор темы. Ему необходимо выбрать состав объектов негативного воздействия на окружающую среду (НВОС) из табличной части справочника и соединить их с регистром сведений СведенияОбЭксплуатации, чтобы получить актуальные данные об их эксплуатации. Автор попытался решить задачу следующим запросом:


ВЫБРАТЬ
    ОбъектыНВОСПоставленныеНаУчетСостав.НомерСтроки КАК НомерСтроки,
    ОбъектыНВОСПоставленныеНаУчетСостав.Ссылка КАК Ссылка,
    ОбъектыНВОСПоставленныеНаУчетСостав.Наименование КАК ИсточникЗагрязнения
ПОМЕСТИТЬ ВТ_СоставИЗАОбъектовНВОСПоставленныхНаУчет
ИЗ
    Справочник.ОбъектыНВОСПоставленныеНаУчет.Состав КАК ОбъектыНВОСПоставленныеНаУчетСостав
ГДЕ
    ОбъектыНВОСПоставленныеНаУчетСостав.Ссылка = &ОбъектНВОС
    И ТИПЗНАЧЕНИЯ(ОбъектыНВОСПоставленныеНаУчетСостав.Наименование) = ТИП(Справочник.ИсточникиЗагрязнения)
;
 ////////////////////////////////////////////////////////////////////////////////
ВЫБРАТЬ
    СведенияОбЭксплуатацииСрезПоследних.Период КАК Период,
    СведенияОбЭксплуатацииСрезПоследних.Организация КАК Организация,
    СведенияОбЭксплуатацииСрезПоследних.ЭксплуатируемыйОбъект КАК ЭксплуатируемыйОбъект,
    СведенияОбЭксплуатацииСрезПоследних.ВведеноВЭксплуатацию КАК ВведеноВЭксплуатацию,
    ВТ_СоставИЗАОбъектовНВОСПоставленныхНаУчет.ИсточникЗагрязнения КАК ИсточникЗагрязнения
ИЗ
    ВТ_СоставИЗАОбъектовНВОСПоставленныхНаУчет КАК ВТ_СоставИЗАОбъектовНВОСПоставленныхНаУчет
    ЛЕВОЕ СОЕДИНЕНИЕ РегистрСведений.СведенияОбЭксплуатации.СрезПоследних(
        &Период,
        ВведеноВЭксплуатацию = ИСТИНА
        И ТИПЗНАЧЕНИЯ(ЭксплуатируемыйОбъект) = ТИП(Справочник.ИсточникиЗагрязнения)) КАК СведенияОбЭксплуатацииСрезПоследних
    ПО ВТ_СоставИЗАОбъектовНВОСПоставленныхНаУчет.ИсточникЗагрязнения = СведенияОбЭксплуатацииСрезПоследних.ЭксплуатируемыйОбъект

Проанализируем этот запрос и выделим критические ошибки, которые мешают его корректной работе.

Ошибка 1. Использование строкового поля «Наименование» для связи объектов

Самая очевидная и критическая ошибка кроется в конструкции фильтра первой части запроса:

ТИПЗНАЧЕНИЯ(ОбъектыНВОСПоставленныеНаУчетСостав.Наименование) = ТИП(Справочник.ИсточникиЗагрязнения)

Выясним причину, почему это условие всегда будет возвращать значение Ложь. По умолчанию стандартное поле Наименование любого справочника или реквизит табличной части с именем "Наименование" имеет тип данных Строка. Попытка сравнить тип строки с типом ссылки на справочник ИсточникиЗагрязнения никогда не увенчается успехом. На уровне СУБД это сравнение типов всегда будет ложным, и временная таблица ВТ_СоставИЗАОбъектовНВОСПоставленныхНаУчет окажется абсолютно пустой.

Как исправить: Необходимо изучить структуру табличной части Состав справочника ОбъектыНВОСПоставленныеНаУчет. Наверняка там присутствует специализированный реквизит ссылочного типа (например, ИсточникЗагрязнения, Объект или Оборудование), который и хранит ссылку на элемент справочника ИсточникиЗагрязнения. Именно его и нужно выбирать вместо текстового наименования.

Ошибка 2. Понимание поля «Ссылка» в табличных частях

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

ОбъектыНВОСПоставленныеНаУчетСостав.Ссылка = &ОбъектНВОС

Предполагалось, что поле Ссылка в табличной части может принадлежать только документам. Давайте развеем это заблуждение. В платформе «1С:Предприятие» табличные части могут создаваться как у документов, так и у справочников (а также у процессов, задач и т.д.). Поле Ссылка в табличной части всегда указывает на элемент-владелец. В данном случае, поскольку мы обращаемся к ТЧ справочника, поле Ссылка содержит ссылку на сам элемент этого справочника. Условие написано абсолютно корректно и позволяет отфильтровать строки ТЧ по конкретному элементу справочника, переданному в параметре &ОбъектНВОС.

Ошибка 3. Катастрофическая неоптимальность работы с виртуальной таблицей «СрезПоследних»

Посмотрим, как выполняется левое соединение во второй части исходного запроса. Автор связывает временную таблицу с виртуальной таблицей среза последних:

ЛЕВОЕ СОЕДИНЕНИЕ РегистрСведений.СведенияОбЭксплуатации.СрезПоследних(&Период, ...)

Если выполнять соединение таким образом, СУБД сначала получит срез последних по абсолютно всем записям, имеющимся в регистре сведений на указанную дату, выполняя при этом тяжелые подзапросы с группировками. И только после построения среза для всей базы данных система выполнит левое соединение с нашей небольшой временной таблицей по условию равенства источников загрязнения. На реальных базах данных с сотнями тысяч записей такой запрос вызовет колоссальные просадки производительности и может привести к превышению времени ожидания блокировок.

Как исправить: Необходимо передать фильтр по целевым объектам непосредственно в параметры виртуальной таблицы СрезПоследних. Для этого идеально подходит конструкция с оператором В (ВЫБРАТЬ ...) на основе уже созданной временной таблицы.

Ошибка 4. Нежелательное использование функции ТИПЗНАЧЕНИЯ в запросах

Использование функции ТИПЗНАЧЕНИЯ в условиях ГДЕ или в параметрах виртуальных таблиц считается нерекомендуемым стандартом разработки 1С. Такая функция мешает оптимизатору СУБД использовать индексы таблиц, что приводит к сканированию всей таблицы (Table Scan) вместо быстрого поиска по индексу (Index Seek).

Как исправить: Если поле имеет составной тип данных, и нам нужно отфильтровать конкретный тип, гораздо эффективнее использовать оператор ССЫЛКА. Например: ЭксплуатируемыйОбъект ССЫЛКА Справочник.ИсточникиЗагрязнения. Более того, если мы передаем отбор через временную таблицу, в которой уже лежат типизированные ссылки, СУБД автоматически выполнит неявную фильтрацию по типу, и необходимость в дополнительных проверках отпадет сама собой.

---

Пошаговое построение оптимального запроса

Теперь, когда мы детально разобрали все слабые места, давайте совместно напишем правильный и быстрый пакет запросов. Решать задачу будем в три последовательных шага.

Шаг 1. Получение очищенного списка источников из табличной части справочника

Создадим временную таблицу, в которую поместим только нужные нам ссылки. Вместо строкового поля Наименование выберем реквизит связи (предположим, что он называется ИсточникЗагрязнения и имеет ссылочный тип).


ВЫБРАТЬ
    ОбъектыНВОССостав.Ссылка КАК ОбъектВладелец,
    ОбъектыНВОССостав.ИсточникЗагрязнения КАК ИсточникЗагрязнения
ПОМЕСТИТЬ ВТ_ВыбранныеИсточники
ИЗ
    Справочник.ОбъектыНВОСПоставленныеНаУчет.Состав КАК ОбъектыНВОССостав
ГДЕ
    ОбъектыНВОССостав.Ссылка = &ОбъектНВОС
    И НЕ ОбъектыНВОССостав.ИсточникЗагрязнения.ПометкаУдаления // Дополнительное полезное условие отсечения удаленных элементов
;

Шаг 2. Оптимальное получение данных из среза последних

Во второй части пакета запросов мы выберем данные из регистра сведений. Главная хитрость заключается в том, что в параметры виртуальной таблицы СрезПоследних мы передадим отбор по объектам из нашей временной таблицы ВТ_ВыбранныеИсточники. Это заставит СУБД рассчитывать срез только для тех нескольких источников, которые указаны в табличной части нашего объекта!


ВЫБРАТЬ
    СрезЭксплуатации.Период КАК Период,
    СрезЭксплуатации.Организация КАК Организация,
    СрезЭксплуатации.ЭксплуатируемыйОбъект КАК ЭксплуатируемыйОбъект,
    СрезЭксплуатации.ВведеноВЭксплуатацию КАК ВведеноВЭксплуатацию
ПОМЕСТИТЬ ВТ_СрезПоследнихЭксплуатации
ИЗ
    РегистрСведений.СведенияОбЭксплуатации.СрезПоследних(
        &Период,
        ЭксплуатируемыйОбъект В (ВЫБРАТЬ ВТ.ИсточникЗагрязнения ИЗ ВТ_ВыбранныеИсточники КАК ВТ)
        И ВведеноВЭксплуатацию = ИСТИНА
    ) КАК СрезЭксплуатации
;

Шаг 3. Финальное соединение результатов

Теперь нам остается лишь соединить две подготовленные легкие временные таблицы. Обратите внимание на выбор типа соединения. Если нам нужны строго только те объекты, которые сейчас эксплуатируются (то есть по ним есть подтвержденная запись в регистре), мы должны использовать ВНУТРЕННЕЕ СОЕДИНЕНИЕ. Если же нам важно вывести абсолютно весь состав табличной части справочника, даже если по какому-то объекту нет данных в регистре, мы используем ЛЕВОЕ СОЕДИНЕНИЕ.

Рассмотрим вариант с левым соединением для сохранения исходной логики автора:


ВЫБРАТЬ
    ВТ_Источники.ИсточникЗагрязнения КАК ИсточникЗагрязнения,
    ЕСТЬNULL(ВТ_Срез.Период, ДАТАВРЕМЯ(1, 1, 1)) КАК Период,
    ЕСТЬNULL(ВТ_Срез.Организация, ЗНАЧЕНИЕ(Справочник.Организации.ПустаяСсылка)) КАК Организация,
    ЕСТЬNULL(ВТ_Срез.ВведеноВЭксплуатацию, ЛОЖЬ) КАК ВведеноВЭксплуатацию
ИЗ
    ВТ_ВыбранныеИсточники КАК ВТ_Источники
    ЛЕВОЕ СОЕДИНЕНИЕ ВТ_СрезПоследнихЭксплуатации КАК ВТ_Срез
    ПО ВТ_Источники.ИсточникЗагрязнения = ВТ_Срез.ЭксплуатируемыйОбъект

Использование функции ЕСТЬNULL при левом соединении позволяет избежать появления значений NULL в итоговой выборке, заменяя их на пустые ссылки, пустые даты или логическое ЛОЖЬ, что значительно упрощает последующую обработку и вывод данных в отчет.

Резюме

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

← На главную