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

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

При разработке учетных механизмов в 1С часто возникает задача скорректировать плановые или нормативные показатели на фактически совершенные операции. Типичный пример — расчет доступного остатка блюд в столовой или ресторане, когда из первоначального лимита меню требуется вычесть уже оформленные за день заказы.

Разберем основные подходы к решению этой задачи на уровне языка запросов 1С: через объединение таблиц с агрегацией и через соединение временных таблиц.

Способ 1. Объединение запросов с инверсией знака (ОБЪЕДИНИТЬ ВСЕ)

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

Рассмотрим базовую схему запроса:


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

СГРУППИРОВАТЬ ПО
    ВТ_Итог.Блюдо

Проанализируем важные технические нюансы этого способа:

  1. Совместимость типов данных: При объединении запросов типы колонок во всех объединяемых выборках должны совпадать. Если в одной из веток поле количества передается как число, а в другой как пустая строка "", СУБД вернет ошибку несовместимости типов или сформирует составной тип. Пустые числовые поля всегда следует инициализировать нулем: 0 КАК ВНаличии или ВЫРАЗИТЬ(0 КАК ЧИСЛО(15, 3)).
  2. Оптимизация отборов по дате: Использование функций вроде НАЧАЛОПЕРИОДА(Документ.Ссылка.Дата, ДЕНЬ) в секции ГДЕ блокирует использование индекса по полю Дата, приводя к полному сканированию таблицы (Table Scan). Всегда передавайте границы периода параметрами через условие МЕЖДУ &ДатаНач И &ДатаКон.

Способ 2. Соединение с использованием временных таблиц (Рекомендуемый)

Для высоконагруженных баз данных и сложных запросов со срезами цен и реквизитами номенклатуры предпочтительнее использовать пакетный запрос с временными таблицами. Это позволяет СУБД построить оптимальный план выполнения и применить операцию Hash Match или Merge Join.

Посмотрим на реализацию пакетного запроса по шагам:

  1. Формируем временную таблицу с лимитами из документов меню.
  2. Получаем данные об оборотах заказов во вторую временную таблицу.
  3. Соединяем таблицы через ЛЕВОЕ СОЕДИНЕНИЕ и вычисляем разницу.

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

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

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

Критически важная деталь: При выполнении левого соединения для позиций, по которым еще не было заказов за день, поле Заказы.Заказано вернет значение NULL. Любая арифметическая операция с NULL (например, 10 - NULL) в языке запросов 1С возвращает NULL, в результате чего строка пропадет из итоговой выборки доступных блюд. Обязательно используйте функцию ЕСТЬNULL(Поле, 0) для всех вычитаемых колонок.

Способ 3. Программная обработка результатов (Таблицы Значений)

Если запросы выполняются в несвязанных модулях конфигурации или выгружаются в клиентские формы, расчет разницы можно произвести в коде 1С с предварительной индексацией:


ТаблицаМеню = ЗапросМеню.Выполнить().Выгрузить();
ТаблицаЗаказов = ЗапросЗаказов.Выполнить().Выгрузить();

// Индексируем таблицу заказов для мгновенного поиска
ТаблицаЗаказов.Индексы.Добавить("Номенклатура");

Для Каждого СтрМеню Из ТаблицаМеню Цикл
    НайденныйЗаказ = ТаблицаЗаказов.Найти(СтрМеню.Номенклатура, "Номенклатура");
    КоличествоЗаказано = ?(НайденныйЗаказ = Неопределено, 0, НайденныйЗаказ.Количество);
    СтрМеню.ВНаличии = СтрМеню.Лимит - КоличествоЗаказано;
КонецЦикла;

Подводя итог: для простых расчетов отлично подходит объединение с инверсией знака, а для построения сложных отчетов с множеством аналитик надежнее использовать пакетные запросы с временными таблицами и обязательной проверкой на ЕСТЬNULL.

← На главную