При разработке прикладных решений на платформе 1С часто возникает задача отобрать элементы справочника или документы по условию, затрагивающему одновременно реквизит шапки и данные одной из строк табличной части. Например, требуется найти всех контрагентов с определенным статусом в шапке и конкретным контактным лицом в табличной части, либо найти элементы справочника по сочетанию реквизитов Фамилия (в шапке) и Город (в табличной части).
Разберем детально, как устроено хранение таких данных в реляционной СУБД, какие способы написания запросов существуют, как они влияют на производительность и каких типичных ошибок следует избегать.
Проанализируем первый классический вариант. Табличная часть любого объекта в платформе 1С физически хранится в отдельной таблице базы данных. Связь между основной таблицей объекта и таблицей его табличной части осуществляется по полю Ссылка.
Рассмотрим пример запроса, где мы явно объединяем таблицу справочника с его табличной частью с помощью конструкции ВНУТРЕННЕЕ СОЕДИНЕНИЕ:
ВЫБРАТЬ РАЗЛИЧНЫЕ
Спр.Ссылка КАК Ссылка,
Спр.Фамилия КАК Фамилия
ИЗ
Справочник.ФизическиеЛица КАК Спр
ВНУТРЕННЕЕ СОЕДИНЕНИЕ Справочник.ФизическиеЛица.КонтактнаяИнформация КАК СпрТЧ
ПО Спр.Ссылка = СпрТЧ.Ссылка
ГДЕ
Спр.Фамилия = &ПараметрФамилия
И СпрТЧ.Город = &ПараметрГород
Разберем по шагам ключевые элементы этого запроса:
ПО Спр.Ссылка = СпрТЧ.Ссылка. Это гарантирует сопоставление строк табличной части именно с тем элементом, которому они принадлежат.ВЫБРАТЬ РАЗЛИЧНЫЕ. Если у одного элемента справочника в табличной части окажется несколько строк, подходящих под условие (или если в выборку не включены уникальные реквизиты самой строки), то без слова РАЗЛИЧНЫЕ ссылка на объект продублируется в результате столько раз, сколько совпадений найдено в его табличной части.ГДЕ мы свободно комбинируем поля шапки и табличной части с помощью логического оператора И.Рассмотрим более компактный синтаксический вариант, когда базовой таблицей источника выступает сама табличная часть справочника, а к реквизиту шапки мы обращаемся «через точку»:
ВЫБРАТЬ РАЗЛИЧНЫЕ
ТЧ.Ссылка КАК Ссылка,
ТЧ.Город КАК Город,
ТЧ.Ссылка.Фамилия КАК Фамилия
ИЗ
Справочник.ФизическиеЛица.КонтактнаяИнформация КАК ТЧ
ГДЕ
ТЧ.Город = &ПараметрГород
И ТЧ.Ссылка.Фамилия = &ПараметрФамилия
Выясним причину споров о производительности данного подхода. При обращении через точку (ТЧ.Ссылка.Фамилия) платформа 1С на уровне СУБД автоматически генерирует неявное соединение (LEFT OUTER JOIN) с таблицей справочника. Если реквизит не является составным типом данных, платформа строит соединение ровно с одной таблицей.
Тем не менее, явное внутреннее соединение считается более предсказуемым для оптимизатора СУБД, так как левое соединение в сложных запросах может приводить к построению неоптимального плана выполнения, если СУБД не сможет вовремя свернуть его до внутреннего.
Если в результате запроса нам требуется получить только список самих элементов справочника (только ссылки), без вывода конкретных строк табличной части, наиболее эффективным с точки зрения архитектуры СУБД является использование конструкции В (ВЫБРАТЬ ...):
ВЫБРАТЬ
Спр.Ссылка КАК Ссылка,
Спр.Фамилия КАК Фамилия
ИЗ
Справочник.ФизическиеЛица КАК Спр
ГДЕ
Спр.Фамилия = &ПараметрФамилия
И Спр.Ссылка В
(ВЫБРАТЬ
ТЧ.Ссылка
ИЗ
Справочник.ФизическиеЛица.КонтактнаяИнформация КАК ТЧ
ГДЕ
ТЧ.Город = &ПараметрГород)
Посмотрим на преимущества этого решения:
ВЫБРАТЬ РАЗЛИЧНЫЕ или СГРУППИРОВАТЬ ПО.SEMI JOIN или оператор EXISTS), выполняя поиск по индексу и прекращая сканирование строк ТЧ для текущего объекта сразу после первого совпадения.При работе с большими базами данных выбор структуры запроса должен сопровождаться правильной настройкой метаданных:
Table Scan). Если поиск выполняется регулярно, в конфигураторе для реквизита табличной части необходимо обязательно установить свойство Индексировать.