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