Как сгруппировать таблицу значений по дате и операции в 1С 8.3: подробное руководство

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

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

Постановка задачи и анализ исходного кода

Представим ситуацию: у нас есть исходная таблица значений, в которую мы построчно выгружаем данные из документов сдельных нарядов. В этой таблице присутствуют колонки Исполнитель, Дата, Операция, Расценки и КоличествоФакт. Наша цель — получить свернутые данные, где для каждой уникальной пары «Дата + Операция» будет рассчитано суммарное фактическое количество.

Давайте проанализируем фрагмент кода, с которого начинал автор темы:


КопияТаб = ТЗ_Сырой.Скопировать();
КопияТаб.Свернуть("Операция", "КоличествоФакт");
Для Каждого Стр Из КопияТаб Цикл
    Сообщить(Стр.Операция); 
КонецЦикла;

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

Решение №1: Правильное использование метода Свернуть()

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

Давайте исправим код и посмотрим, как должна выглядеть правильная свертка по дате и операции:


// Создаем копию таблицы, чтобы не испортить исходные сырые данные
КопияТаб = ТЗ_Сырой.Скопировать();

// Сворачиваем таблицу по двум ключевым колонкам группировки
КопияТаб.Свернуть("Дата, Операция", "КоличествоФакт");

// Выводим результат для проверки
Для Каждого Стр Из КопияТаб Цикл
    Сообщить("Дата: " + Формат(Стр.Дата, "ДФ=dd.MM.yyyy") + " | Операция: " + Стр.Операция + " | Кол-во: " + Стр.КоличествоФакт); 
КонецЦикла;

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

КопияТаб.Свернуть("Дата, Операция, Исполнитель, Расценки", "КоличествоФакт");

Подводный камень №1: Проблема со временем в датах

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

В платформе 1С тип данных Дата всегда содержит не только календарный день, но и точное время (часы, минуты, секунды). Если в исходную таблицу значений записывается дата непосредственно из документа, то одна строка может иметь значение 12.08.2026 10:15:30, а вторая — 12.08.2026 14:00:00. Для метода Свернуть() это абсолютно разные, несовпадающие значения, поэтому группировка по дню не произойдет.

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


Для каждого СтрМ из ТабОперации Цикл
    стрЗ1 = ТЗ_Сырой.Добавить();
    стрЗ1.Исполнитель = Док.Исполнитель;
    
    // Принудительно отсекаем время, оставляя только дату (00:00:00)
    стрЗ1.Дата = НачалоДня(Док.Дата); 
    
    стрЗ1.Операция = СтрМ.Операция;
    стрЗ1.Расценки = СтрМ.Расценка;
    стрЗ1.КоличествоФакт = СтрМ.КоличествоФакт;   
КонецЦикла;

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

Подводный камень №2: Неявная типизация колонок

Обратим внимание на то, как в исходном коде создавались колонки таблицы значений:


ТЗ_Сырой = Новый ТаблицаЗначений;
ТЗ_Сырой.Колонки.Добавить("Исполнитель");
ТЗ_Сырой.Колонки.Добавить("Дата");
ТЗ_Сырой.Колонки.Добавить("Операция");
ТЗ_Сырой.Колонки.Добавить("КоличествоФакт");

При таком объявлении колонки не имеют жестко заданного типа (их тип считается произвольным). Если при обходе документов в колонку КоличествоФакт по ошибке запишется пустая ссылка, строка или значение Неопределено, метод Свернуть() не сможет выполнить математическое сложение и либо вызовет ошибку выполнения, либо выдаст непредсказуемый результат.

Рекомендуется всегда явно типизировать колонки, особенно те, по которым планируется суммирование:


ТЗ_Сырой = Новый ТаблицаЗначений;
ТЗ_Сырой.Колонки.Добавить("Исполнитель");

// Задаем тип Дата для колонки дат
ТЗ_Сырой.Колонки.Добавить("Дата", Новый ОписаниеТипов("Дата"));

ТЗ_Сырой.Колонки.Добавить("Операция");

// Задаем числовой тип для суммируемой колонки
МассивЧисла = Новый Массив;
МассивЧисла.Добавить(Тип("Число"));
КвалификаторыЧисла = Новый КвалификаторыЧисла(15, 3);
ОписаниеТипаЧисло = Новый ОписаниеТипов(МассивЧисла, , , КвалификаторыЧисла);

ТЗ_Сырой.Колонки.Добавить("КоличествоФакт", ОписаниеТипаЧисло);

Решение №2: Использование механизма запросов (Best Practice)

Хотя метод Свернуть() прекрасно решает задачу "в лоб", обход документов в цикле с последующим ручным заполнением и сворачиванием таблицы значений — это не самый оптимальный путь с точки зрения производительности 1С. Гораздо быстрее и профессиональнее переложить эту задачу на СУБД с помощью механизма запросов.

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


Запрос = Новый Запрос;
Запрос.Текст = 
    "ВЫБРАТЬ
    |	СдельныйНарядОперации.Ссылка.Исполнитель КАК Исполнитель,
    |	НАЧАЛОПЕРИОДА(СдельныйНарядОперации.Ссылка.Дата, ДЕНЬ) КАК Дата,
    |	СдельныйНарядОперации.Операция КАК Операция,
    |	СдельныйНарядОперации.Расценка КАК Расценки,
    |	СУММА(СдельныйНарядОперации.КоличествоФакт) КАК КоличествоФакт
    |ИЗ
    |	Документ.СдельныйНаряд.Операции КАК СдельныйНарядОперации
    |ГДЕ
    |	СдельныйНарядОперации.Ссылка В(&МассивДокументов)
    |	И СдельныйНарядОперации.Ссылка.Исполнитель <> ЗНАЧЕНИЕ(Справочник.ФизическиеЛица.ПустаяСсылка)
    |
    |СГРУППИРОВАТЬ ПО
    |	СдельныйНарядОперации.Ссылка.Исполнитель,
    |	НАЧАЛОПЕРИОДА(СдельныйНарядОперации.Ссылка.Дата, ДЕНЬ),
    |	СдельныйНарядОперации.Операция,
    |	СдельныйНарядОперации.Расценка";

// Передаем массив документов, которые нужно обработать
Запрос.УстановитьПараметр("МассивДокументов", ПолучитьМассивДокументовНарядов());

РезультатЗапроса = Запрос.Выполнить();
ТЗ_Обработанная = РезультатЗапроса.Выгрузить();

Проанализируем преимущества использования запроса в данном сценарии:

  1. Скорость выполнения: СУБД сгруппирует данные на уровне сервера базы данных гораздо быстрее, чем это сделает код на сервере приложений 1С через перебор коллекции строк.
  2. Конструкция НАЧАЛОПЕРИОДА: Функция НАЧАЛОПЕРИОДА(..., ДЕНЬ) автоматически "отсекает" время на уровне SQL-запроса, избавляя нас от необходимости использовать функцию НачалоДня() в цикле.
  3. Конструкция СГРУППИРОВАТЬ ПО: Этот оператор выполняет ту же роль, что и метод Свернуть(), агрегируя данные по указанным полям группировки и суммируя поле КоличествоФакт.
  4. Читаемость кода: Весь сложный алгоритм выборки, фильтрации по исполнителю и суммирования упакован в лаконичный декларативный текст запроса.

Резюме и рекомендации

Для успешной группировки таблиц значений в 1С всегда следуйте этим правилам:

← На главную