Как свернуть таблицу значений по произвольным интервалам в 1С

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

В практике разработчика 1С часто возникает задача агрегации данных не по прямому совпадению значений, а по принадлежности к определенному диапазону. Типичный пример: имеется список продаж в разрезе этажей здания, и необходимо получить итоги по группам этажей (например, "1–3 этаж", "4–10 этаж" и т.д.), при этом сами интервалы задаются пользователем динамически в отдельной таблице. Стандартный метод Свернуть() здесь не поможет напрямую, так как он ищет полное соответствие значений в колонках группировки.

В данной статье мы разберем несколько эффективных способов решения этой задачи: от использования механизмов запросов до оптимизированных алгоритмов на встроенном языке. Мы проанализируем преимущества каждого метода и выясним, какой из них лучше подходит для различных объемов данных.

Метод 1: Использование временных таблиц и механизмов запроса

Большинство опытных разработчиков сходятся во мнении, что задачи, связанные с соединением таблиц и группировкой данных, эффективнее всего решать с помощью языка запросов. Несмотря на то, что наши таблицы (исходные данные и интервалы) находятся в оперативной памяти в виде объектов ТаблицаЗначений, мы можем передать их в запрос как параметры.

Рассмотрим пошагово этот алгоритм. Основная "хитрость" заключается в том, что в запросе при соединении таблиц можно использовать не только оператор равенства, но и операторы сравнения или оператор МЕЖДУ.

Предположим, у нас есть ТЗ_Данные (колонки: Этаж, Площадь, Количество) и ТЗ_Интервалы (колонки: НижняяГраница, ВерхняяГраница).


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

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

Запрос.УстановитьПараметр("ТЗ_Данные", ТЗ_Данные);
Запрос.УстановитьПараметр("ТЗ_Интервалы", ТЗ_Интервалы);

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

Преимущества этого метода:

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

Важный нюанс: Если интервалы, заданные пользователем, пересекаются (например, 1–5 и 3–7), то этажи с 3-го по 5-й попадут в обе группы, и итоговые суммы "задвоятся". Если такая ситуация возможна, перед запросом следует проверить корректность таблицы интервалов.

Метод 2: Обход в цикле с классической сверткой

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

Рассмотрим реализацию с удалением лишних строк (тех, что не попали ни в один интервал):


МассивКУдалению = Новый Массив;

Для Каждого СтрДанные Из ТЗ_Данные Цикл
	ПрошлаУсловие = Ложь;
	
	Для Каждого СтрИнтервал Из ТЗ_Интервалы Цикл
		Если СтрДанные.Этаж >= СтрИнтервал.НижняяГраница 
		   И СтрДанные.Этаж <= СтрИнтервал.ВерхняяГраница Тогда
			
			// Заменяем этаж на нижнюю границу (как признак группы)
			СтрДанные.Этаж = СтрИнтервал.НижняяГраница; 
			ПрошлаУсловие = Истина;
			Прервать;
		КонецЕсли;
	КонецЦикла;
	
	Если Не ПрошлаУсловие Тогда
		МассивКУдалению.Добавить(СтрДанные);
	КонецЕсли;
КонецЦикла;

// Удаляем строки, не вошедшие в интервалы
Для Каждого Стр Удаления Из МассивКУдалению Цикл
	ТЗ_Данные.Удалить(СтрУдаления);
КонецЦикла;

// Сворачиваем по новой "группе"
ТЗ_Данные.Свернуть("Этаж", "Площадь, Количество");

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

Метод 3: Оптимизация производительности через "Соответствие"

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

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


// 1. Создаем карту соответствия Этаж -> НачалоИнтервала
КартаЭтажей = Новый Соответствие;
МаксЭтаж = 200; // Условный максимум этажей в здании

Для Каждого СтрИнтервал Из ТЗ_Интервалы Цикл
	Для ТекЭтаж = СтрИнтервал.НижняяГраница По СтрИнтервал.ВерхняяГраница Цикл
		КартаЭтажей.Вставить(ТекЭтаж, СтрИнтервал.НижняяГраница);
	КонецЦикла;
КонецЦикла;

// 2. Проходим по основной таблице один раз
МассивКУдалению = Новый Массив;
Для Каждого СтрДанные Из ТЗ_Данные Цикл
	ИдентификаторГруппы = КартаЭтажей[СтрДанные.Этаж];
	
	Если ИдентификаторГруппы <> Неопределено Тогда
		СтрДанные.Этаж = ИдентификаторГруппы;
	Иначе
		МассивКУдалению.Добавить(СтрДанные);
	КонецЕсли;
КонецЦикла;

// 3. Удаление и свертка
// ... (аналогично предыдущему методу)

Этот метод работает за линейное время O(n) относительно количества строк в таблице данных. Поиск в Соответствии происходит практически мгновенно, независимо от того, сколько интервалов задал пользователь.

Метод 4: Прямое накопление итогов

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

  1. Добавим в ТЗ_Интервалы колонки СуммаПлощадь и СуммаКоличество.
  2. Для каждой строки из ТЗ_Данные находим соответствующую строку в ТЗ_Интервалы.
  3. Прибавляем значения к колонкам итогов.

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

Для Каждого СтрДанные Из ТЗ_Данные Цикл
	Для Каждого СтрИнтервал Из ТЗ_Интервалы Цикл
		Если СтрДанные.Этаж >= СтрИнтервал.НижняяГраница 
		   И СтрДанные.Этаж <= СтрИнтервал.ВерхняяГраница Тогда
			
			СтрИнтервал.Площадь = СтрИнтервал.Площадь + СтрДанные.Площадь;
			СтрИнтервал.Количество = СтрИнтервал.Количество + СтрДанные.Количество;
			Прервать;
			
		КонецЕсли;
	КонецЦикла;
КонецЦикла;

Подведение итогов

Мы проанализировали различные ситуации и выяснили следующее:

Совет: Всегда проверяйте типы данных. Если в одной таблице этаж хранится как Число, а в другой как Строка, сравнение >= выдаст ошибку или неверный результат. Используйте Число() для явного приведения типов перед выполнением алгоритма.

← На главную