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

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

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

Анализ проблемы: почему искажаются числовые данные

Проанализируем типичную конструкцию условия соединения, которая приводит к ошибкам в расчетах:


ПО (ВЫБОР
        КОГДА ТаблицаРегистра.Регистратор ССЫЛКА Документ.ОтчетОРозничныхПродажах
            ТОГДА ВЫБОР
                    КОГДА ТаблицаРегистра.Регистратор.КассаККМ = ТорговыйОбъектТаблица.Склад
                        ТОГДА Истина
                    КОГДА ТаблицаРегистра.Регистратор.Склад = ТорговыйОбъектТаблица.Склад
                        ТОГДА Истина
                КОНЕЦ
        ИНАЧЕ ТаблицаРегистра.ДокументПродажи.Склад = ТорговыйОбъектТаблица.Склад
    КОНЕЦ)

Выясним причину некорректного поведения такого запроса. Здесь возникают сразу две критические проблемы:

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

Вариант 1. Явное приведение типов через ВЫРАЗИТЬ

Если требуется сохранить текущую логику сопоставления, необходимо обязательно ограничить составной тип поля Регистратор с помощью функции ВЫРАЗИТЬ. Это исключит неявные соединения и ускорит работу СУБД.

Посмотрим на пример условия:


ПО (ТаблицаРегистра.Регистратор ССЫЛКА Документ.ОтчетОРозничныхПродажах
        И (ВЫРАЗИТЬ(ТаблицаРегистра.Регистратор КАК Документ.ОтчетОРозничныхПродажах).КассаККМ = ТорговыйОбъектТаблица.Склад
            ИЛИ ВЫРАЗИТЬ(ТаблицаРегистра.Регистратор КАК Документ.ОтчетОРозничныхПродажах).Склад = ТорговыйОбъектТаблица.Склад))
    ИЛИ (НЕ ТаблицаРегистра.Регистратор ССЫЛКА Документ.ОтчетОРозничныхПродажах
        И ТаблицаРегистра.ДокументПродажи.Склад = ТорговыйОбъектТаблица.Склад)

Важно: Данный вариант решает проблему разыменования типов, но не устраняет риск задвоения сумм, если и касса, и склад из одного документа присутствуют в таблице сопоставления одновременно.

Вариант 2. Нормализация ключа связи во временной таблице (рекомендуемый подход)

Наиболее надежный и производительный способ решения задачи — предварительно сформировать единый ключ связи во временной таблице еще до выполнения соединения. Это гарантирует строгое соединение «один-к-одному» и позволяет оптимизатору СУБД применить быстрые алгоритмы соединения (Merge Join или Hash Join).

Разберем по шагам реализацию этого подхода:

  1. Помещаем выборку из регистра оборотов во временную таблицу и вычисляем единое поле ТорговыйОбъектКлюч с четким приоритетом (например, если есть касса ККМ — берем ее, иначе берем склад).
  2. Соединяем временную таблицу со справочником по строгому равенству единого поля.

ВЫБРАТЬ
    Продажи.Номенклатура КАК Номенклатура,
    Продажи.КоличествоОборот КАК Количество,
    Продажи.СтоимостьОборот КАК Стоимость,
    ВЫБОР
        КОГДА Продажи.Регистратор ССЫЛКА Документ.ОтчетОРозничныхПродажах
            ТОГДА ВЫБОР
                    КОГДА ВЫРАЗИТЬ(Продажи.Регистратор КАК Документ.ОтчетОРозничныхПродажах).КассаККМ ЕСТЬ НЕ NULL
                        ТОГДА ВЫРАЗИТЬ(Продажи.Регистратор КАК Документ.ОтчетОРозничныхПродажах).КассаККМ
                    ИНАЧЕ ВЫРАЗИТЬ(Продажи.Регистратор КАК Документ.ОтчетОРозничныхПродажах).Склад
                КОНЕЦ
        ИНАЧЕ Продажи.ДокументПродажи.Склад
    КОНЕЦ КАК ТорговыйОбъектКлюч
ПОМЕСТИТЬ ВТ_Продажи
ИЗ
    РегистрНакопления.Продажи.Обороты(&НачалоПериода, &КонецПериода, Авто, ) КАК Продажи
;

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

Вариант 3. Разделение запроса через ОБЪЕДИНИТЬ ВСЕ

Рассмотрим еще один эффективный сценарий, когда розничные и оптовые продажи разделяются на уровне источников данных. Такой подход полностью исключает сложные условия в секции ПО:


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

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

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

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

← На главную