Как правильно получить обороты из нескольких регистров накопления в одном запросе?

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

Часто перед разработчиком стоит задача собрать данные из разных источников — например, обороты по денежным средствам, продажам и закупкам — в одну таблицу. Первая мысль, которая возникает у многих, — использовать ЛЕВОЕ СОЕДИНЕНИЕ (LEFT JOIN), выбрав один из регистров в качестве основного. Однако этот путь часто ведет к логическим ошибкам и потере данных. Рассмотрим подробнее, почему подход с соединениями может не работать и как реализовать задачу правильно.

Почему Левое соединение — это ловушка

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

Проанализируем пример: если по павильону «А» были продажи и покупки, но не было движений по кассе, то основная таблица (Касса) не вернет строк для этого павильона. Следовательно, соединять будет просто не с чем. Использование условия ГДЕ КассаОбороты.Павилион = &Павилион только усугубляет ситуацию, отсекая все, что не попало в первичную выборку по кассе.

Правильный подход: Использование конструкции ОБЪЕДИНИТЬ ВСЕ

В экосистеме 1С «золотым стандартом» для объединения данных из разных регистров считается использование конструкции ОБЪЕДИНИТЬ ВСЕ с последующей группировкой. Рассмотрим, как это работает на практике. Мы создаем несколько независимых выборок (по одной для каждого регистра), приводим их к единой структуре колонок, а затем «схлопываем» результат.

Выполним задачу по шагам:

  1. Создадим выборку из каждого регистра.
  2. Для каждого регистра определим свои колонки ресурсов, а в остальных выборках заполним их нулями.
  3. Объединим результаты.
  4. Сгруппируем итог по измерениям (в нашем случае — по павильону).

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


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

СГРУППИРОВАТЬ ПО
    Т.Павилион

Разберем ключевые моменты этого решения

Во-первых, обратите внимание на параметры виртуальных таблиц Обороты. Мы передаем условие по павильону непосредственно в четвертый параметр виртуальной таблицы: Павилион = &Павилион. Это критически важно для производительности. Если использовать секцию ГДЕ во внешнем запросе, система сначала попытается рассчитать обороты по всем павильонам из всех регистров, и только потом отфильтрует нужный. Передача параметра в виртуальную таблицу заставляет СУБД выбирать только релевантные данные на самом раннем этапе.

Во-вторых, использование ОБЪЕДИНИТЬ ВСЕ (вместо простого ОБЪЕДИНИТЬ) избавляет сервер базы данных от необходимости проверять строки на уникальность и удалять дубликаты. Поскольку мы все равно будем делать группировку позже, нам не важна уникальность строк на этапе объединения. Это работает значительно быстрее на больших объемах данных.

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

Использование временных таблиц для чистоты кода

Если регистров много, или логика получения данных из каждого регистра сложнее, чем просто выборка оборотов, мы рекомендуем использовать Временные Таблицы. Это сделает запрос более читаемым и упростит его отладку.

Рассмотрим структуру такого запроса:

  1. Помещаем обороты кассы во временную таблицу ВТ_Касса.
  2. Помещаем обороты продаж во временную таблицу ВТ_Продажи.
  3. В финальном запросе делаем ОБЪЕДИНИТЬ ВСЕ из всех временных таблиц и группируем результат.

Проанализируем пример схемы с временными таблицами:


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

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

ВЫБРАТЬ
    ПродажиОбороты.Павилион,
    ПродажиОбороты.СуммаОборот
ИЗ
    РегистрНакопления.Продажи.Обороты(&НачалоПериода, &КонецПериода, , Павилион = &Павилион) КАК ПродажиОбороты
;

////////////////////////////////////////////////////////////////////////////////
ВЫБРАТЬ
    Т.Павилион КАК Павилион,
    СУММА(Т.Сумма) КАК ИтоговаяСумма
ИЗ
    ВТ_Данные КАК Т
СГРУППИРОВАТЬ ПО
    Т.Павилион

Альтернативный вариант: Ведущая таблица-справочник

Если по каким-то причинам вы все же хотите использовать ЛЕВОЕ СОЕДИНЕНИЕ, вам необходимо сменить архитектуру запроса. Чтобы не терять данные, в качестве левой (основной) таблицы должен выступать справочник (в данном случае — Справочник.Павилионы).

Алгоритм будет следующим:

Однако помните: такой подход менее эффективен. При объединении четырех и более регистров через соединения, планировщик запросов SQL-сервера может построить неоптимальный план, что приведет к замедлению работы системы. Метод через ОБЪЕДИНИТЬ ВСЕ является универсальным, более быстрым и легче поддается масштабированию (добавить пятый регистр в объединение гораздо проще, чем добавлять еще одно соединение в сложную структуру).

Резюме

Для получения оборотов из нескольких независимых регистров накопления всегда отдавайте предпочтение объединению запросов. Это гарантирует полноту данных, даже если в одном из регистров за период не было записей. Не забывайте передавать условия фильтрации (например, по конкретному объекту или периоду) в параметры виртуальных таблиц, чтобы ваш код работал максимально быстро.

← На главную