Как объединить две и более таблицы в запросе 1С и вывести пустые значения?

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

Часто при разработке отчетов в 1С возникает необходимость собрать данные из нескольких регистров. Например, мы хотим увидеть в одной таблице текущие остатки товаров на складах, количество товаров в резерве и объем продаж. Если мы попытаемся использовать базовое соединение таблиц, то столкнемся с проблемой: выводятся только те номенклатурные позиции, которые присутствуют во всех соединяемых таблицах одновременно. Позиции, где есть остаток, но нет резерва (или наоборот), просто пропадают из результата. Рассмотрим подробнее эту ситуацию, проанализируем типичные ошибки и разберем по шагам правильные и оптимальные способы решения этой задачи.

Типичная ошибка: Внутреннее соединение

Начинающие разработчики часто пытаются соединить виртуальные таблицы с помощью конструкции ВНУТРЕННЕЕ СОЕДИНЕНИЕ. Разберем причину, почему это не работает для нашей цели. Внутреннее соединение оставляет в выборке только те строки, для которых нашлось совпадение по условию соединения в обеих таблицах. Если товара на складе 10 штук, а в резерве 0 (запись в регистре резервов отсутствует), внутреннее соединение отбросит эту номенклатуру.

Решение 1: Использование ПОЛНОЕ СОЕДИНЕНИЕ (Full Join)

Первое логичное решение, которое приходит на ум — использовать ПОЛНОЕ СОЕДИНЕНИЕ. Оно позволяет получить все записи из обеих таблиц. Если во второй таблице нет соответствующей записи, запрос вернет пустое значение. Однако здесь кроется важный нюанс — функция ЕСТЬNULL.

Если при внешнем или полном соединении запись в одной из таблиц не найдена, 1С возвращает специальное значение NULL (отсутствие значения). Любая математическая операция с ним (например, сложение остатка и резерва) вернет пустой результат. Чтобы вместо пустоты получить ноль, все числовые поля из присоединяемых таблиц нужно обязательно оборачивать в функцию преобразования:

ЕСТЬNULL(ТоварыНаСкладахОстатки.КоличествоОстаток, 0) КАК Остаток

Почему полное соединение плохо работает для трех и более таблиц?

Хотя для двух таблиц этот метод работает нормально, попытка соединить три таблицы (Остатки, Резервы, Продажи) приведет к серьезным проблемам. Во-первых, это сильно бьет по производительности СУБД. Во-вторых, для получения правильных измерений (например, колонки "Номенклатура" в итоговой таблице) придется писать громоздкие многоэтажные конструкции, чтобы заполнить пустые измерения:


ЕСТЬNULL(Таб1.Номенклатура, ЕСТЬNULL(Таб2.Номенклатура, Таб3.Номенклатура)) КАК Номенклатура

Решение 2: Эталонный паттерн (ОБЪЕДИНИТЬ ВСЕ + Группировка)

Посмотрим на самый правильный и производительный способ, который является стандартом разработки в 1С при работе с несколькими регистрами. Он состоит из нескольких шагов:

  1. Каждая виртуальная таблица запрашивается отдельно.
  2. Они связываются через ОБЪЕДИНИТЬ ВСЕ. Главное правило: количество и порядок колонок во всех блоках должны строго совпадать. Если в таблице остатков нет колонки «ВРезерве», мы пишем 0 КАК ВРезерве.
  3. Этот объединенный массив помещается во временную таблицу с помощью команды ПОМЕСТИТЬ.
  4. Из временной таблицы делается итоговая выборка с конструкцией СГРУППИРОВАТЬ ПО для измерений (Номенклатура, Склад) и агрегатной функцией СУММА() для числовых ресурсов. Это автоматически сворачивает дублирующиеся строки и избавляет от значений NULL.

Проанализируем пример готового и правильного кода:


ВЫБРАТЬ
    ТоварыНаСкладахОстатки.Склад КАК Склад,
    ТоварыНаСкладахОстатки.Номенклатура КАК Номенклатура,
    ТоварыНаСкладахОстатки.КоличествоОстаток КАК НаСкладах,
    0 КАК ВРезерве
ПОМЕСТИТЬ ВТОстатки
ИЗ
    РегистрНакопления.ТоварыНаСкладах.Остатки(&ДатаАктуальности, ) КАК ТоварыНаСкладахОстатки

ОБЪЕДИНИТЬ ВСЕ

ВЫБРАТЬ
    ТоварыВРезервеНаСкладах.Склад,
    ТоварыВРезервеНаСкладах.Номенклатура,
    0,
    ТоварыВРезервеНаСкладах.КоличествоОстаток
ИЗ
    РегистрНакопления.ТоварыВРезервеНаСкладах.Остатки(&ДатаАктуальности, ) КАК ТоварыВРезервеНаСкладах;

////////////////////////////////////////////////////////////////////////////////
ВЫБРАТЬ 
    Склад КАК Склад, 
    Номенклатура КАК Номенклатура, 
    СУММА(НаСкладах) КАК НаСкладах, 
    СУММА(ВРезерве) КАК ВРезерве 
ИЗ 
    ВТОстатки 
СГРУППИРОВАТЬ ПО 
    Склад, 
    Номенклатура

Решение 3: Альтернативный метод с ЛЕВЫМ СОЕДИНЕНИЕМ

Если нам требуется вывести вообще все значения из базы, включая ту номенклатуру, по которой не было ни продаж, ни остатков, ни резервов в выбранном периоде, конструкций только с регистрами будет недостаточно. В этом случае базовой таблицей в запросе (которая идет после ИЗ) должен выступать Справочник.Номенклатура.

И уже к этому справочнику необходимо делать ЛЕВОЕ СОЕДИНЕНИЕ виртуальных таблиц регистров с обязательным применением ЕСТЬNULL() к числовым полям. Это гарантирует, что справочник выступит каркасом отчета, к которому приклеятся все имеющиеся движения и остатки.

Важное правило: Отборы внутри виртуальных таблиц

Выясним причину еще одной распространенной ошибки. При работе с объединениями или левыми/полными соединениями использовать секцию ГДЕ для фильтрации присоединенных таблиц — грубая ошибка. Например, если написать ГДЕ ТоварыНаСкладахОстатки.Номенклатура.ЭтоГруппа = ЛОЖЬ в конце запроса с внешним соединением, условие отработает после соединения и просто отсечет строки, где значение оказалось равно NULL, превратив левое соединение во внутреннее.

Любые отборы нужно передавать строго в параметры самих виртуальных таблиц. Для регистра остатков это второй параметр виртуальной таблицы. Именно там нужно прописывать все условия на номенклатуру, склады и другие измерения.

← На главную