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