При разработке отчетов в конфигурациях на базе 1С:Библиотеки стандартных подсистем (БСП), таких как ERP, УТ 11, Комплексная автоматизация или УНФ, программисты часто сталкиваются со специфической задачей. Необходимо вывести в одну таблицу (или кросс-таблицу) абсолютно всю номенклатуру и сопоставить её со всеми возможными дополнительными реквизитами, которые для нее настроены. При этом важно показать даже те реквизиты, которые в карточке конкретного товара не заполнены — на их месте должен отображаться NULL или пустое значение.
Сложность заключается в том, что в базе данных значения дополнительных реквизитов хранятся в табличной части ДополнительныеРеквизиты справочника Номенклатура. Если использовать обычное левое соединение справочника с его табличной частью, мы получим только те свойства, которые физически заполнены пользователями. Незаполненные свойства просто не попадут в выборку. Чтобы решить эту задачу, нам потребуется построить полную матрицу (декартово произведение) всех товаров и всех доступных для них свойств, а затем соединить её с реальными значениями.
В этой статье мы подробно разберем, как решить эту задачу с помощью языка запросов 1С, проанализируем архитектурные особенности хранения реквизитов, рассмотрим штатный механизм СКД и обсудим критически важные вопросы оптимизации производительности.
Давайте проанализируем структуру хранения данных. Нам доступны три ключевые сущности:
Номенклатура — здесь хранятся пары "Свойство - Значение" для каждого конкретного товара.Чтобы получить полную матрицу, нам необходимо:
Номенклатура - Свойство для каждой возможной комбинации.ДополнительныеРеквизиты с помощью ЛЕВОГО соединения по двум полям: Номенклатура и Свойство.Рассмотрим базовый вариант запроса, который решает задачу «в лоб» для свойств, привязанных к общему набору номенклатуры. Этот запрос отлично подойдет, если у вас все свойства привязаны к одному общему набору параметров.
ВЫБРАТЬ
ВложенныйЗапрос.Номенклатура КАК Номенклатура,
ВложенныйЗапрос.Реквизит КАК Реквизит,
ЗначРек.Значение КАК Значение
{ВЫБРАТЬ
Номенклатура.*,
Реквизит.*,
Значение.*}
ИЗ
(ВЫБРАТЬ
СпрНом.Ссылка КАК Номенклатура,
ДопРек.Ссылка КАК Реквизит
ИЗ
Справочник.Номенклатура КАК СпрНом
ПОЛНОЕ СОЕДИНЕНИЕ ПланВидовХарактеристик.ДополнительныеРеквизитыИСведения КАК ДопРек
ПО (ДопРек.НаборСвойств = ЗНАЧЕНИЕ(Справочник.НаборыДополнительныхРеквизитовИСведений.Справочник_Номенклатура))
{ГДЕ
СпрНом.Ссылка.* КАК Номенклатура,
ДопРек.Ссылка.* КАК Реквизит}) КАК ВложенныйЗапрос
ЛЕВОЕ СОЕДИНЕНИЕ Справочник.Номенклатура.ДополнительныеРеквизиты КАК ЗначРек
ПО ВложенныйЗапрос.Номенклатура = ЗначРек.Ссылка
И ВложенныйЗапрос.Реквизит = ЗначРек.Свойство
Давайте разберем по шагам, как работает этот код:
Номенклатура и выполняем его ПОЛНОЕ СОЕДИНЕНИЕ с планом видов характеристик.ПО (ДопРек.НаборСвойств = ЗНАЧЕНИЕ(Справочник.НаборыДополнительныхРеквизитовИСведений.Справочник_Номенклатура)) ограничивает список свойств только теми, которые относятся к общим реквизитам номенклатуры.ЛЕВОЕ СОЕДИНЕНИЕ с реальной табличной частью ДополнительныеРеквизиты. Если у номенклатуры заполнено свойство, поле Значение заполнится реальными данными. Если свойство не заполнено, СУБД вернет NULL.На практике свойства редко привязываются к общему набору "Справочник_Номенклатура". Чаще всего в базах ERP и УТ 11 свойства жестко привязаны к конкретным Видам номенклатуры, а также существуют так называемые "Общие реквизиты", которые могут наследоваться. Чтобы учесть эту специфику, логику запроса нужно расширить.
Проанализируем более сложный и универсальный запрос, который учитывает и виды номенклатуры, и общие свойства:
ВЫБРАТЬ
ДопСведенияИРеквизитыПоВидамНоменклатуры.ВидНоменклатуры КАК ВидНоменклатуры,
ДопСведенияИРеквизитыПоВидамНоменклатуры.НаборСвойств КАК НаборСвойств,
ДопСведенияИРеквизитыПоВидамНоменклатуры.Свойство КАК Свойство,
НоменклатураБезГрупп.Ссылка КАК Номенклатура
ИЗ
(ВЫБРАТЬ
Номенклатура.Ссылка КАК Ссылка
ИЗ
Справочник.Номенклатура КАК Номенклатура
ГДЕ
Номенклатура.ЭтоГруппа = ЛОЖЬ) КАК НоменклатураБезГрупп
ВНУТРЕННЕЕ СОЕДИНЕНИЕ (ВЫБРАТЬ
ВидыНоменклатуры.Ссылка КАК ВидНоменклатуры,
НаборыДополнительныхРеквизитовИСведений.Ссылка КАК НаборСвойств,
НаборыДополнительныхРеквизитовИСведений.Свойство КАК Свойство,
ЛОЖЬ КАК ЭтоОбщееСвойствоСведение
ИЗ
Справочник.ВидыНоменклатуры КАК ВидыНоменклатуры
ЛЕВОЕ СОЕДИНЕНИЕ Справочник.НаборыДополнительныхРеквизитовИСведений.ДополнительныеРеквизиты КАК НаборыДополнительныхРеквизитовИСведений
ПО ВидыНоменклатуры.НаборСвойств = НаборыДополнительныхРеквизитовИСведений.Ссылка
ГДЕ
НаборыДополнительныхРеквизитовИСведений.Ссылка В ИЕРАРХИИ (ЗНАЧЕНИЕ(Справочник.НаборыДополнительныхРеквизитовИСведений.Справочник_Номенклатура))
И НЕ НаборыДополнительныхРеквизитовИСведений.Ссылка.ПометкаУдаления
ОБЪЕДИНИТЬ ВСЕ
ВЫБРАТЬ
НЕОПРЕДЕЛЕНО,
СвойстваОбъектов.Ссылка,
СвойстваОбъектов.Свойство,
ИСТИНА
ИЗ
Справочник.НаборыДополнительныхРеквизитовИСведений.ДополнительныеРеквизиты КАК СвойстваОбъектов
ГДЕ
СвойстваОбъектов.Ссылка В ИЕРАРХИИ (ЗНАЧЕНИЕ(Справочник.НаборыДополнительныхРеквизитовИСведений.Справочник_Номенклатура))
И НЕ СвойстваОбъектов.Ссылка.ПометкаУдаления
И СвойстваОбъектов.Ссылка = &ПризнакОбщиеРеквизиты) КАК ДопСведенияИРеквизитыПоВидамНоменклатуры
ПО (НоменклатураБезГрупп.Ссылка.ВидНоменклатуры = ДопСведенияИРеквизитыПоВидамНоменклатуры.ВидНоменклатуры
ИЛИ ДопСведенияИРеквизитыПоВидамНоменклатуры.ЭтоОбщееСвойствоСведение)
Рассмотрим структуру этого сложного запроса детально:
НоменклатураБезГрупп, так как группы не содержат дополнительных реквизитов.ДопСведенияИРеквизитыПоВидамНоменклатуры. Мы объединяем два запроса. Первый выбирает реквизиты, привязанные к конкретным видам номенклатуры. Второй запрос выбирает "Общие" реквизиты, которые привязаны к предопределенному элементу, переданному через параметр &ПризнакОбщиеРеквизиты.ЭтоОбщееСвойствоСведение.Внимание! Применение декартова произведения (умножения каждой строки номенклатуры на каждое свойство) таит в себе серьезную опасность для производительности системы. Если в вашей базе данных содержится 100 000 элементов номенклатуры, а количество активных дополнительных реквизитов равно 50, то размер результирующей виртуальной таблицы составит 5 000 000 строк.
В файловых версиях баз данных или на серверах со слабыми техническими характеристиками это гарантированно приведет к ошибке переполнения памяти операционной системы или СУБД. Чтобы избежать аварийного завершения работы базы данных, обязательно придерживайтесь следующих правил оптимизации:
Ссылка и ВидНоменклатуры.НЕ ПометкаУдаления — это обязательное требование для исключения "мусорных" колонок.Значение в табличной части доп. реквизитов имеет составной тип. Если вы знаете, какой тип должен быть у конкретного реквизита, используйте конструкцию ВЫРАЗИТЬ(Значение КАК...). Это значительно снизит нагрузку на оптимизатор СУБД.Если ваша задача — вывести эти данные в отчет, созданный на Системе компоновки данных (СКД), то писать громоздкие запросы с полными соединениями вообще не нужно! В СКД встроен мощный штатный механизм работы с характеристиками.
Выясним, как настроить характеристики в СКД:
Ссылка).НаборыДополнительныхРеквизитовИСведений.ДополнительныеРеквизиты.ДополнительныеРеквизиты вашей номенклатуры.После такой настройки СКД самостоятельно, на уровне генерации итогового макета, выполнит левое соединение со всеми доступными свойствами. При этом в настройках отчета пользователи смогут просто раскрыть ветку Дополнительные реквизиты у номенклатуры и перетащить нужные им свойства в колонки отчета. Пустые значения выведутся автоматически как NULL, а производительность будет на порядок выше, чем при использовании жестко прописанного декартова произведения в запросе.