Рассмотрим частую задачу, с которой сталкиваются разработчики 1С: имеется исходная таблица с данными, где указаны ДатаНачала и ДатаОкончания. Если этот период охватывает несколько месяцев (например, сотрудник был в отпуске с середины августа по начало сентября), необходимо разделить эту запись на несколько строк так, чтобы каждая строка относилась строго к одному месяцу.
Например, из строки Иванов (17.08.2019 — 04.09.2019) мы должны получить две:
Проанализируем ситуацию и разберем два основных способа решения этой задачи: программно через Таблицу Значений (ТЗ) и средствами языка запросов.
Многие разработчики изначально идут по пути выгрузки данных в Таблицу Значений и последующей обработки строк в цикле. Если мы точно знаем, что перехлест периодов составляет не более одного месяца (например, только с августа на сентябрь), можно использовать простую конструкцию с проверкой условия.
Посмотрим на пример кода, который решает задачу для границы в один месяц:
Для каждого Строчка Из ТЗ Цикл
Если Строчка.ДатаНачала = Строчка.НачалоМесяцаХ Тогда
НовСтр = ТЗНов.Добавить();
НовСтр.Наименование = Строчка.Наименование;
НовСтр.ДатаНачала = Строчка.ДатаНачала;
НовСтр.ДатаОкончания = Строчка.ДатаОкончания;
Иначе
// Добавляем часть первого месяца
НовСтр = ТЗНов.Добавить();
НовСтр.Наименование = Строчка.Наименование;
НовСтр.ДатаНачала = Строчка.ДатаНачала;
НовСтр.ДатаОкончания = Строчка.КонецМесяцаХ;
// Добавляем часть второго месяца
НовСтр = ТЗНов.Добавить();
НовСтр.Наименование = Строчка.Наименование;
НовСтр.ДатаНачала = Строчка.НачалоМесяцаХ;
НовСтр.ДатаОкончания = Строчка.ДатаОкончания;
КонецЕсли;
КонецЦикла;
Однако выясним причину, по которой этот код не является универсальным. Что будет, если период охватывает три месяца? Например, с июля по октябрь. Указанный выше код не сработает, так как он рассчитан строго на одно разбиение.
Для создания универсального решения в коде, которое будет работать с любым количеством месяцев, необходимо использовать цикл Пока. Разберем по шагам правильный алгоритмический паттерн:
КонецМесяца().Этот метод прост в отладке, легко читается и не зависит от длины интервала. Вы также можете вынести этот алгоритм в отдельную общую функцию для повторного использования.
Рассмотрим подробнее вариант, при котором вся логика выполняется на уровне СУБД. Это всегда предпочтительнее при работе с большими массивами данных.
Если мы исходим из условия, что перехлест составляет только один месяц, можно использовать конструкцию с объединением запросов:
ВЫБРАТЬ
ИсходныеДанные.Наименование,
ИсходныеДанные.ДатаНачала КАК Начало,
ВЫБОР КОГДА МЕСЯЦ(ИсходныеДанные.ДатаНачала) <> МЕСЯЦ(ИсходныеДанные.ДатаОкончания) ТОГДА
КОНЕЦПЕРИОДА(ИсходныеДанные.ДатаНачала, МЕСЯЦ)
ИНАЧЕ ИсходныеДанные.ДатаОкончания КОНЕЦ КАК Конец
ИЗ ИсходныеДанные КАК ИсходныеДанные
ОБЪЕДИНИТЬ ВСЕ
ВЫБРАТЬ
ИсходныеДанные.Наименование,
НАЧАЛОПЕРИОДА(ИсходныеДанные.ДатаОкончания, МЕСЯЦ),
ИсходныеДанные.ДатаОкончания
ИЗ ИсходныеДанные КАК ИсходныеДанные
ГДЕ
МЕСЯЦ(ИсходныеДанные.ДатаНачала) <> МЕСЯЦ(ИсходныеДанные.ДатаОкончания)
Но как быть, если интервал может охватывать любое количество месяцев? В этом случае необходимо применять соединение исходной таблицы с «Таблицей периодов» (или таблицей месяцев).
В качестве эталонной таблицы дат может выступать регистр сведений производственного календаря или предварительно сформированная временная таблица, где каждая строка содержит первое число месяца. Для построения такой последовательности дат без использования календаря подойдут варианты формирования выборки диапазона дат в запросе.
Механика такого запроса следующая:
ТаблицаМесяцев.Дата МЕЖДУ НАЧАЛОПЕРИОДА(Исходная.ДатаНачала, МЕСЯЦ) И НАЧАЛОПЕРИОДА(Исходная.ДатаОкончания, МЕСЯЦ). За счет этого строка размножится ровно на то количество месяцев, которое затрагивает период.ВЫБОР.Пример выражений для полей в таком запросе:
// Новая дата начала:
ВЫБОР КОГДА НАЧАЛОПЕРИОДА(Исходная.ДатаНачала, МЕСЯЦ) = ТаблицаМесяцев.Дата
ТОГДА Исходная.ДатаНачала
ИНАЧЕ ТаблицаМесяцев.Дата КОНЕЦ
// Новая дата окончания:
ВЫБОР КОГДА НАЧАЛОПЕРИОДА(Исходная.ДатаОкончания, МЕСЯЦ) = ТаблицаМесяцев.Дата
ТОГДА Исходная.ДатаОкончания
ИНАЧЕ КОНЕЦПЕРИОДА(ТаблицаМесяцев.Дата, МЕСЯЦ) КОНЕЦ
Проанализируем преимущества и недостатки обоих подходов, чтобы вы могли сделать правильный выбор.
Запрос:
Использование запроса (особенно с таблицей календаря) значительно экономит ресурсы сервера 1С. Данные не нужно выгружать в оперативную память, перебирать в цикле и загружать обратно во временные таблицы. Это идеальный вариант для работы с большими объемами данных (сотни и тысячи строк). Однако, этот метод требует надежного и гарантированно заполненного регистра-календаря в системе.
Программный код (ТЗ):
Написание логики на языке программирования 1С (в цикле) лучше подходит для ситуаций, когда объем данных небольшой (до нескольких десятков строк). Код часто проще читать, понимать и поддерживать начинающим программистам. Кроме того, этот метод полностью независим от внешних справочников и регистров — он никогда не выдаст пустой результат из-за отсутствия данных в производственном календаре на следующий год.
Подводя итоги, настоятельно рекомендуем исходить из требований к производительности и поддерживаемости. Если это тяжелый массовый расчет зарплаты или себестоимости — пишем запрос с соединением таблиц. Если это разовая небольшая печатная форма или выгрузка — вполне допустимо применить аккуратный цикл Пока.