Как заполнить колонку "Количество" для товаров-аналогов в табличной части 1С?

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

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

Анализ неверного подхода: почему так писать нельзя

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


Для каждого стр Из Товары Цикл
    Если стр.Количество <> 0 тогда
        ЭлементыФормы.Товары.ОтборСтрок.Цена.ВидСравнения = ВидСравнения.Равно;
        ЭлементыФормы.Товары.ОтборСтрок.Цена.Использование = Истина;
        ЭлементыФормы.Товары.ОтборСтрок.Цена.Значение = стр.Цена;
        Количество = стр.Количество;
        Для каждого стр Из Товары Цикл
            Если НЕ ЭлементыФормы.Товары.ПроверитьСтроку(стр) Тогда
                Продолжить;
            КонецЕсли;
            стр.Количество = Количество;
        КонецЦикла;
    КонецЕсли;
КонецЦикла;

Выясним причину, почему этот код является категорически неверным и опасным:

  1. Одинаковая переменная цикла. Использование переменной стр одновременно во внешнем и внутреннем цикле Для Каждого приводит к перезаписи указателя итератора. Это классическая грубая ошибка в 1С, которая вызывает пропуск строк при обходе коллекции, зацикливание или выход за границы.
  2. Обращение к элементам интерфейса внутри цикла. Использование ЭлементыФормы.Товары.ОтборСтрок для вычислений — это нарушение архитектуры. Работа с данными должна происходить на уровне объекта (самой коллекции строк), а не на уровне интерфейса (формы). Постоянное перестроение отборов на форме внутри цикла вызывает сильные "тормоза", а на управляемых формах такой код в принципе не будет работать на стороне сервера.

Оптимальное решение 1: Использование объекта "Соответствие" со сложным ключом

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

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

  1. Создадим объект Соответствие для хранения заполненных количеств по группам аналогов.
  2. Выполним первый проход (цикл) по табличной части. Сформируем уникальный строковый ключ, состоящий из цены и первых 5 символов наименования. Если количество заполнено, запишем его в соответствие.
  3. Выполним второй проход по той же табличной части. Для каждой строки снова сформируем ключ. Если по этому ключу в соответствии найдется значение, присвоим его колонке Количество.

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


// Создаем соответствие для хранения данных об аналогах
СоответствиеКоличества = Новый Соответствие;

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

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

Оптимальное решение 2: Использование запросов (Профессиональный метод)

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

В тексте запроса мы применим функцию ПОДСТРОКА(Номенклатура.Наименование, 1, 5) для получения первых пяти символов. Через агрегатную функцию МАКСИМУМ(Количество) мы гарантированно получим заполненное количество для каждой группы аналогов (ведь пустое количество равно нулю).

Разберем пример реализации через запрос:


Запрос = Новый Запрос;
Запрос.Текст = 
    "ВЫБРАТЬ
    |    ТаблицаТовары.Номенклатура КАК Номенклатура,
    |    ТаблицаТовары.Цена КАК Цена,
    |    ТаблицаТовары.Количество КАК Количество,
    |    ПОДСТРОКА(ТаблицаТовары.Номенклатура.Наименование, 1, 5) КАК КлючНаименования,
    |    ТаблицаТовары.НомерСтроки КАК НомерСтроки
    |ПОМЕСТИТЬ ВТ_ИсходныеДанные
    |ИЗ
    |    &ТаблицаТовары КАК ТаблицаТовары
    |;
    |
    |////////////////////////////////////////////////////////////////////////////////
    |ВЫБРАТЬ
    |    ВТ_ИсходныеДанные.Цена КАК Цена,
    |    ВТ_ИсходныеДанные.КлючНаименования КАК КлючНаименования,
    |    МАКСИМУМ(ВТ_ИсходныеДанные.Количество) КАК МаксимальноеКоличество
    |ПОМЕСТИТЬ ВТ_МаксимальныеКоличества
    |ИЗ
    |    ВТ_ИсходныеДанные КАК ВТ_ИсходныеДанные
    |ГДЕ
    |    ВТ_ИсходныеДанные.Количество > 0
    |
    |СГРУППИРОВАТЬ ПО
    |    ВТ_ИсходныеДанные.Цена,
    |    ВТ_ИсходныеДанные.КлючНаименования
    |;
    |
    |////////////////////////////////////////////////////////////////////////////////
    |ВЫБРАТЬ
    |    ВТ_ИсходныеДанные.НомерСтроки КАК НомерСтроки,
    |    ЕСТЬNULL(ВТ_МаксимальныеКоличества.МаксимальноеКоличество, 0) КАК Количество
    |ИЗ
    |    ВТ_ИсходныеДанные КАК ВТ_ИсходныеДанные
    |        ЛЕВОЕ СОЕДИНЕНИЕ ВТ_МаксимальныеКоличества КАК ВТ_МаксимальныеКоличества
    |        ПО ВТ_ИсходныеДанные.Цена = ВТ_МаксимальныеКоличества.Цена
    |            И ВТ_ИсходныеДанные.КлючНаименования = ВТ_МаксимальныеКоличества.КлючНаименования
    |ГДЕ
    |    ВТ_ИсходныеДанные.Количество = 0";

Запрос.УстановитьПараметр("ТаблицаТовары", Товары.Выгрузить());
РезультатЗапроса = Запрос.Выполнить();
Выборка = РезультатЗапроса.Выбрать();

// Обходим результат и заполняем исходную таблицу
Пока Выборка.Следующий() Цикл
    // Ищем строку по номеру (НомерСтроки - 1, т.к. индексы начинаются с 0)
    СтрокаТЧ = Товары.Получить(Выборка.НомерСтроки - 1);
    СтрокаТЧ.Количество = Выборка.Количество;
КонецЦикла;

Такой код является наиболее производительным при работе с большими объемами данных и легко читается другими разработчиками.

Архитектурные замечания по бизнес-логике

В завершение проанализируем саму логику постановки задачи. Поиск аналогов по совпадению цены и части названия — это "костыль", который может привести к непредсказуемым ошибкам в учете. Например, совершенно разные товары могут временно получить одинаковую цену (скажем, корм для животных и кетчуп по акции за 29.99), а их названия могут случайно совпасть в первых пяти символах.

Как сделать правильно? В типовых конфигурациях (УТ, ERP, УНФ) для связи товаров существует регистр сведений АналогиНоменклатуры. Правильным подходом будет научить систему понимать связь товаров через ссылки (уникальные идентификаторы). При обработке данных мы должны обращаться запросом к этому регистру и проставлять данные, опираясь на строго заданные связи в базе, а не на случайные текстовые совпадения.

← На главную