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