Как правильно выбрать главную таблицу при создании соединений в запросе 1С

Программист 1С v8.3 (Управляемые формы) IT и автоматизация бизнеса
← На главную

При написании запросов в системе «1С:Предприятие» одним из самых частых вопросов, возникающих у начинающих разработчиков, является выбор порядка таблиц в секции ЛЕВОЕ СОЕДИНЕНИЕ. На первый взгляд может показаться, что разницы нет, однако на практике перестановка таблиц местами может привести не только к неверному результату (количеству строк), но и к значительным задержкам в работе системы. В рамках данной статьи мы подробно разберем, как определить «главную» таблицу, как работают механизмы сопоставления данных и какие «подводные камни» подстерегают программиста при работе с соединениями.

Понятие «Главной» таблицы: логический аспект

В языке запросов 1С, как и в классическом SQL, под «главной» понимается таблица, данные из которой должны попасть в результирующую выборку в полном объеме, независимо от того, нашлось ли для них соответствие в другой таблице. Рассмотрим ключевые правила определения такой таблицы:

  1. Критерий полноты данных: Таблица, из которой нам нужно получить ВСЕ записи, должна стоять слева от ключевого слова ЛЕВОЕ СОЕДИНЕНИЕ.
  2. Второстепенная таблица: Таблица, которая стоит справа, называется «присоединяемой». Данные из неё добавятся только в том случае, если сработает условие связи, указанное после слова ПО.
  3. Пространственная ориентация: В сообществе разработчиков 1С принято использовать именно левое соединение. Правое соединение технически выполняет ту же функцию (просто меняет таблицы местами), но оно крайне редко используется из-за сложности визуального восприятия. Мы читаем код слева направо, и логика «возьмем основную таблицу и дополним её данными» воспринимается естественнее.

Проанализируем классический пример: нам нужно получить список всех товаров из справочника Номенклатура и узнать их текущие остатки. Если мы поставим справочник слева, а таблицу остатков справа, мы получим список всех товаров (даже тех, которых нет на складе). Если же мы сделаем наоборот, то в результат попадут только те товары, по которым есть остатки, что фактически превратит наш запрос в аналог ВНУТРЕННЕГО СОЕДИНЕНИЯ.

Разбор практической ситуации: Документ и Регистр сведений

Рассмотрим ситуацию, описанную в обсуждении: у нас есть документ и регистр сведений (РС). Что выбрать в качестве главной таблицы? Ответ всегда зависит от цели запроса. Давайте проанализируем две разные задачи.

Задача №1: Вывести список строк табличной части документа и для каждой строки найти значение из регистра (например, цену или скидку из РС ЦеныНоменклатуры).

В этом случае главной таблицей будет выступать табличная часть документа. Нам важно увидеть все строки документа, даже если в регистре цен не заполнена информация для какого-то товара. Посмотрим на пример кода:


ВЫБРАТЬ
    ДокТовары.Номенклатура КАК Товар,
    ДокТовары.Количество КАК Количество,
    ЕСТЬNULL(Цены.Цена, 0) КАК Цена
ИЗ
    Документ.РеализацияТоваровУслуг.Товары КАК ДокТовары
    ЛЕВОЕ СОЕДИНЕНИЕ РегистрСведений.ЦеныНоменклатуры.СрезПоследних(&Период, ) КАК Цены
    ПО ДокТовары.Номенклатура = Цены.Номенклатура
ГДЕ
    ДокТовары.Ссылка = &Ссылка

Задача №2: Проанализировать записи регистра сведений и найти, каким документом была сделана запись (если такая связь предусмотрена логикой базы).

Здесь главной таблицей станет уже Регистр Сведений, так как нас интересует полнота данных именно в нем.

Ошибки фильтрации: Ловушка секции «ГДЕ»

Разберем критическую ошибку, которую часто допускают программисты. При использовании левого соединения крайне важно следить за тем, где накладываются условия на правую (присоединяемую) таблицу. Рассмотрим ситуацию: мы соединяем Контрагентов (слева) и Договоры (справа). Мы хотим получить всех контрагентов, но только те договоры, которые помечены как «Действующие».

Если мы напишем условие в секции ГДЕ, например ГДЕ Договоры.Статус = "Действующий", то произойдет следующее: СУБД сначала выполнит левое соединение (у контрагентов без договоров в поле статус будет NULL), а затем применит фильтр ГДЕ. Так как NULL не равен строке "Действующий", все контрагенты без договоров будут отсечены. Наш запрос превратится во внутреннее соединение.

Правильное решение: Условия на правую таблицу нужно переносить в секцию ПО. Выясним причину: условия внутри ПО определяют, какие записи присоединить, а условия в ГДЕ определяют, какие записи оставить в итоговом результате.


// Правильный подход
ВЫБРАТЬ
    Контрагенты.Наименование,
    Договоры.Номер
ИЗ
    Справочник.Контрагенты КАК Контрагенты
    ЛЕВОЕ СОЕДИНЕНИЕ Справочник.Договоры КАК Договоры
    ПО Контрагенты.Ссылка = Договоры.Владелец
    И Договоры.Статус = ЗНАЧЕНИЕ(Перечисление.СтатусыДоговоров.Действует)

Проблема «Размножения» записей (Один ко многим)

Важно помнить, что выбор главной таблицы влияет не только на наличие NULL, но и на количество строк. Если в левой (главной) таблице 1 строка, а в правой таблице по условию связи найдено 10 строк, то в результате вы получите 10 строк. Левая таблица «размножится».

Проанализируем ситуацию: вы хотите проверить, есть ли у документа хотя бы одна строка с услугой. Если вы просто присоедините таблицу Товары левым соединением, и в документе будет 50 строк, ваш документ задублируется 50 раз. В таких случаях эффективнее использовать конструкцию ГДЕ Ссылка В (ВЫБРАТЬ...) или соединение с предварительно сгруппированной таблицей.

Производительность и оптимизация: Технический взгляд

Хотя логически мы определяем главную таблицу по смыслу задачи, для СУБД (SQL Server или PostgreSQL) порядок таблиц также имеет значение. Посмотрим на ситуацию с точки зрения оптимизатора:

  1. Ведущая таблица (Driving Table): Оптимизатор СУБД сам пытается определить, какую таблицу прочитать первой, чтобы минимизировать количество операций. Как правило, выгоднее начинать с таблицы, которая после применения фильтров вернет наименьшее количество строк.
  2. Индексация: Поля, по которым происходит связь (после слова ПО), обязательно должны быть проиндексированы. В 1С это обычно поля типа Ссылка, Период или измерения регистров, которые индексируются платформой автоматически.
  3. Виртуальные таблицы: При соединении с виртуальными таблицами (Остатки, Срезы) крайне не рекомендуется ставить их в правую часть левого соединения без параметров. Рассмотрим почему: если вы не передадите фильтр (например, список товаров) в параметры виртуальной таблицы, система будет вынуждена рассчитать остатки по всей базе, прежде чем присоединить их к вашему узкому списку товаров из документа.

Использование временных таблиц как «Фундамента»

Современный стандарт разработки на платформе 1С 8.3 предполагает активное использование временных таблиц. Рассмотрим, как это помогает решить проблему выбора главной таблицы. Вместо того чтобы строить сложные цепочки из 10 соединений, мы можем:

  1. Выбрать необходимые ссылки во временную таблицу (например, только нужные документы).
  2. Проиндексировать временную таблицу.
  3. Использовать эту временную таблицу в качестве «фундамента» (самой первой, левой таблицы) для всех последующих соединений.

Это делает структуру запроса линейной и понятной: «Берем наш отобранный список и последовательно дополняем его данными из разных источников».


// Шаг 1: Подготовка фундамента
ВЫБРАТЬ Ссылка ИЗ Документ.ЗаказПокупателя ГДЕ Дата МЕЖДУ &Начало И &Конец ПОМЕСТИТЬ ВТ_Заказы;
ИНДЕКСИРОВАТЬ ВТ_Заказы.Ссылка;

// Шаг 2: Получение данных
ВЫБРАТЬ
    ВТ.Ссылка,
    ЕСТЬNULL(Остатки.КоличествоОстаток, 0) КАК Остаток
ИЗ
    ВТ_Заказы КАК ВТ
    ЛЕВОЕ СОЕДИНЕНИЕ РегистрНакопления.ТоварыНаСкладах.Остатки(, Номенклатура В (ВЫБРАТЬ Номенклатура ИЗ Документ.ЗаказПокупателя.Товары ГДЕ Ссылка В (ВЫБРАТЬ Ссылка ИЗ ВТ_Заказы))) КАК Остатки
    ПО ВТ.Ссылка = ... // и так далее

Обработка NULL: функция ЕСТЬNULL

При левом соединении для записей, которые не были найдены в правой таблице, СУБД возвращает значение NULL. Это «пустое» значение коварно: любая математическая операция с ним (например, Цена * NULL) даст в результате NULL. Чтобы этого избежать, всегда используйте функцию ЕСТЬNULL(Поле, ЗначениеЗамены).

Проанализируем: если мы выводим сумму, логично заменить NULL на 0. Если выводим строку — на пустую строку. Это обеспечит корректную работу отчетов и исключит странные пустые ячейки в результатах запроса.

Таким образом, определение главной таблицы — это не просто выбор «кто слева», а осознанное проектирование структуры данных, которое учитывает и требования бизнеса к полноте отчета, и технические особенности работы баз данных.

← На главную