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

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

В процессе разработки на платформе 1С часто возникает задача сравнения двух наборов данных, например, табличных частей. Классический пример — система автоматических скидок или акций. У нас есть условия для получения бонуса (например, "купи Молоко 3 шт. и Печенье 1 шт."), и нам нужно проверить, выполнены ли эти условия в заказе клиента. Иначе говоря, как убедиться, что все строки из табличной части с условиями (ТЧ1) присутствуют в табличной части документа (ТЧ2), причем в количестве не меньшем, чем требуется?

Рассмотрим два основных подхода к решению этой задачи: итеративный (с помощью циклов) и декларативный (с помощью запроса). Проанализируем их сильные и слабые стороны.

Подход 1: Проверка в цикле — просто, но не всегда эффективно

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

Однако простой перебор может привести к ошибкам. Основная проблема, на которую указали на форуме, — это наличие нескольких строк с одной и той же номенклатурой в документе-заказе. Например, если условие — "Молоко 10 шт.", а в заказе две строки "Молоко 5 шт." и "Молоко 5 шт.", наивная проверка может не сработать. Чтобы избежать этого, необходимо сначала агрегировать (свернуть) данные в таблице заказа.

Посмотрим на улучшенный алгоритм:

  1. Выгружаем табличную часть заказа в таблицу значений.
  2. Сворачиваем полученную таблицу по номенклатуре, суммируя количество. Это гарантирует, что у нас будет только одна строка для каждой уникальной товарной позиции с её общим количеством.
  3. Перебираем в цикле строки из таблицы с условиями.
  4. Для каждой строки условия ищем соответствующую номенклатуру в нашей свернутой таблице заказа.
  5. Если номенклатура не найдена или её количество в заказе меньше требуемого, устанавливаем флаг в значение Ложь и прерываем цикл.
  6. Если цикл завершился без прерывания, значит все условия выполнены.

Рассмотрим пример кода:


// ТЧ1 - Табличная часть с условиями (например, из справочника скидок)
// ТЧ2 - Табличная часть товаров из документа (заказа)

// Шаг 1: Агрегируем товары в заказе, чтобы суммировать количество для одинаковых позиций.
// Это ключевой шаг, который решает проблему с дублирующимися строками.
ТЗ_ЗаказСвернутая = ТЧ2.Выгрузить();
ТЗ_ЗаказСвернутая.Свернуть("Номенклатура", "Количество");

// Шаг 2: Проверяем выполнение каждого условия
ФлагВсеУсловияВыполнены = Истина;
Для Каждого СтрокаУсловия Из ТЧ1 Цикл
    
    // Ищем номенклатуру из условия в свернутой таблице заказа
    ПараметрыПоиска = Новый Структура("Номенклатура", СтрокаУсловия.Номенклатура);
    НайденныеСтроки = ТЗ_ЗаказСвернутая.НайтиСтроки(ПараметрыПоиска);
    
    Если НайденныеСтроки.Количество() = 0 Тогда
        // Товар из условия вообще не найден в заказе
        ФлагВсеУсловияВыполнены = Ложь;
        Прервать;
    Иначе
        // Товар найден, теперь проверяем количество. 
        // Так как мы свернули таблицу, найденная строка будет только одна.
        НайденнаяСтрокаЗаказа = НайденныеСтроки[0];
        Если НайденнаяСтрокаЗаказа.Количество < СтрокаУсловия.Количество Тогда
            // Количество в заказе меньше требуемого по условию
            ФлагВсеУсловияВыполнены = Ложь;
            Прервать;
        КонецЕсли;
    КонецЕсли;
    
КонецЦикла;

Если ФлагВсеУсловияВыполнены Тогда
    Сообщить("Все условия выполнены, можно добавлять подарок!");
Иначе
    Сообщить("Условия для получения подарка не выполнены.");
КонецЕсли;

Преимущества:

Недостатки:

Подход 2: Использование запроса — правильный и производительный метод

Это наиболее правильный, производительный и масштабируемый способ решения подобных задач в 1С. Вся логика сравнения и вычислений переносится на сторону системы управления базами данных (СУБД), что работает на порядки быстрее, чем обработка данных в цикле средствами языка 1С.

Логику запроса построим "от обратного": мы будем искать те условия, которые не выполняются. Если в результате запроса мы не получим ни одной строки, это будет означать, что все условия выполнены.

Разберем по шагам:

  1. Создаем менеджер временных таблиц.
  2. Помещаем табличную часть с условиями (ТЧ1) в первую временную таблицу (ВТ), назовем ее ВТ_Условия.
  3. Помещаем табличную часть заказа (ТЧ2) во вторую ВТ, назовем ее ВТ_Заказ. При этом сразу же группируем данные по номенклатуре и суммируем количество, чтобы решить проблему дублирующихся строк.
  4. Пишем финальный запрос, в котором используем ЛЕВОЕ СОЕДИНЕНИЕ таблицы ВТ_Условия с таблицей ВТ_Заказ по полю номенклатуры.
  5. В условии ГДЕ ищем ситуации, когда условие не выполнено:
    • Товар из условия не найден в заказе (поле из присоединенной таблицы ВТ_Заказ будет равно NULL).
    • Товар найден, но его количество в заказе меньше, чем требуется по условию.
  6. Выполняем запрос и проверяем, пустой ли результат. Если пустой — все условия выполнены.

Полностью рабочий и прокомментированный код:


// ТЧ1 - Табличная часть с условиями
// ТЧ2 - Табличная часть товаров из заказа

// Создаем запрос и менеджер временных таблиц
Запрос = Новый Запрос;
Запрос.МенеджерВременныхТаблиц = Новый МенеджерВременныхТаблиц;

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

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

// Финальный запрос: ищем НЕвыполненные условия
Запрос.Текст = 
    "ВЫБРАТЬ
    |    Условия.Номенклатура
    |ИЗ
    |    ВТ_Условия КАК Условия
    |        ЛЕВОЕ СОЕДИНЕНИЕ ВТ_Заказ КАК Заказ
    |        ПО Условия.Номенклатура = Заказ.Номенклатура
    |ГДЕ
    |    // Условие 1: Товар из условия не найден в заказе
    |    Заказ.Номенклатура ЕСТЬ NULL 
    |    // Условие 2: Товар найден, но его количество в заказе меньше требуемого
    |    ИЛИ Условия.Количество > Заказ.Количество";

РезультатЗапроса = Запрос.Выполнить();

// Проверяем результат
Если РезультатЗапроса.Пустой() Тогда
    Сообщить("Все условия выполнены!");
Иначе
    Сообщить("Не все условия выполнены.");
    // При необходимости можно вывести, какие именно условия не выполнились
    Выборка = РезультатЗапроса.Выбрать();
    Пока Выборка.Следующий() Цикл
        Сообщить("Не выполнено условие для номенклатуры: " + Выборка.Номенклатура);
    КонецЦикла;
КонецЕсли;

// Важно! Менеджер временных таблиц нужно закрывать для освобождения ресурсов
Запрос.МенеджерВременныхТаблиц.Закрыть();

Преимущества:

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

← На главную