В практике разработки на платформе 1С периодически возникают задачи, которые кажутся простыми на первый взгляд, но упираются в архитектурные особенности языка запросов. Одна из таких задач — циклическое прибавление определенного срока к дате до тех пор, пока все даты в наборе данных не станут одинаковыми. Рассмотрим, почему встроенный язык запросов ограничивает нас в этом вопросе, и какие обходные пути существуют для решения данной проблемы.
Для начала разберем суть задачи. У нас есть таблица значений с двумя колонками: Дата и Срок (количество дней). Нам необходимо многократно прибавлять Срок к соответствующей Дате для каждой строки, пока не будет достигнута точка синхронизации — момент, когда во всех строках получится одна и та же дата.
Основная сложность заключается в том, что язык запросов 1С не поддерживает циклы. В отличие от процедурного языка 1С (где есть операторы Для и Пока), язык запросов является декларативным. Мы описываем, какие данные хотим получить, но не можем заставить СУБД выполнять итеративные вычисления «строка за строкой» с изменяющимся состоянием в рамках одного прохода.
Функция ДОБАВИТЬКДАТЕ, доступная в запросах 1С, предназначена для разового вычисления. Мы можем написать:
ВЫБРАТЬ
ДОБАВИТЬКДАТЕ(Таблица.Дата, ДЕНЬ, Таблица.Срок) КАК НоваяДата
ИЗ
ВременнаяТаблица КАК Таблица
Это прибавит срок один раз. Однако мы не знаем заранее, сколько итераций потребуется: 5, 10 или 1000. Поскольку в запросе нельзя написать Пока ДатыНеРавны Цикл, прямое решение «в лоб» через запрос невозможно.
Наиболее надежным и понятным способом решения задачи является использование программного кода. Проанализируем ситуацию: если данных немного, цикл по таблице значений отработает мгновенно. Рассмотрим пример кода, который реализует логику автора темы:
// Предположим, ТЗДанных уже заполнена колонками Дата и Срок
ВсеДатыРавны = Ложь;
Пока Не ВсеДатыРавны Цикл
МаксимальнаяДата = ТЗДанных.Итог("Дата") / ТЗДанных.Количество(); // Грубая проверка
// На самом деле лучше найти реальный максимум
МаксимальнаяДата = '00010101';
Для Каждого СтрокаТЗ Из ТЗДанных Цикл
Если СтрокаТЗ.Дата > МаксимальнаяДата Тогда
МаксимальнаяДата = СтрокаТЗ.Дата;
КонецЕсли;
КонецДля;
ВсеДатыРавны = Истина;
Для Каждого СтрокаТЗ Из ТЗДанных Цикл
Пока СтрокаТЗ.Дата < МаксимальнаяДата Цикл
СтрокаТЗ.Дата = ДобавитьМесяц(СтрокаТЗ.Дата, СтрокаТЗ.Срок); // Или другая логика
ВсеДатыРавны = Ложь;
КонецЦикла;
КонецДля;
КонецЦикла;
Этот подход гарантирует результат, так как мы полностью контролируем процесс итераций. Однако, если программист все же хочет использовать мощь запросов, существуют «хитрые» методы имитации итераций.
Существует технический прием, позволяющий имитировать «цикл» в запросе. Он заключается в использовании заранее подготовленной таблицы, содержащей последовательность чисел (0, 1, 2, 3... N). Разберем этот метод по шагам.
ДОБАВИТЬКДАТЕ(ИсходнаяДата, ДЕНЬ, ЧислоИтерации * Срок).Проанализируем пример такого запроса:
ВЫБРАТЬ
Цифры.Номер КАК Коэффициент
ПОМЕСТИТЬ ВТ_Итератор
ИЗ
(ВЫБРАТЬ 0 КАК Номер ОБЪЕДИНИТЬ ВЫБРАТЬ 1 ... /* и так далее до нужного предела */) КАК Цифры
;
ВЫБРАТЬ
Т.ИдентификаторСтроки,
ДОБАВИТЬКДАТЕ(Т.Дата, ДЕНЬ, Итератор.Коэффициент * Т.Срок) КАК ВозможнаяДата
ПОМЕСТИТЬ ВТ_ВсеВарианты
ИЗ
ВТ_ИсходныеДанные КАК Т
СОЕДИНЕНИЕ ВТ_Итератор КАК Итератор ПО (ИСТИНА)
;
ВЫБРАТЬ
Варианты.ВозможнаяДата
ИЗ
ВТ_ВсеВарианты КАК Варианты
СГРУППИРОВАТЬ ПО
Варианты.ВозможнаяДата
ИМЕЮЩИЕ
КОЛИЧЕСТВО(РАЗЛИЧНЫЕ Варианты.ИдентификаторСтроки) = &ОбщееКоличествоСтрок
Важный момент: Этот метод сработает только в том случае, если общая дата находится в пределах заданного количества итераций. Если для совпадения нужно 10 001 итерация, а в таблице чисел их 10 000, запрос вернет пустой результат.
С точки зрения математики, задача сводится к поиску общего значения арифметических прогрессий. Если сроки (шаги) являются взаимно простыми числами, общая дата может быть очень далеко в будущем. В теории чисел это решается через нахождение наименьшего общего кратного (НОК) разностей дат и периодов.
Выясним, можно ли вычислить это в запросе без перебора? К сожалению, в 1С отсутствуют функции для работы с остатком от деления (MOD) или сложные математические функции в языке запросов (хотя в SQL они есть). Поэтому математическое решение без перебора в запросе 1С практически нереализуемо.
Проанализируем, почему эксперты в обсуждении называют такие запросы «извращением». Использование декартова произведения (умножения строк) в запросе создает колоссальную нагрузку на сервер СУБД. Если в исходной таблице 100 строк, а итератор содержит 1000 чисел, временная таблица будет содержать 100 000 записей. Это может привести к замедлению работы системы и нерациональному использованию памяти.
Основные выводы:
Рассмотрим ситуацию, когда такие вычисления все же оправданы в запросе. Это могут быть отчеты, где нужно найти пересечение графиков платежей или производства, но даже в этом случае профессионалы рекомендуют подготовить данные в коде перед передачей в схему компоновки данных.
Таким образом, мы выяснили причину, по которой штатный механизм запросов не позволяет решить задачу в одну строку, и разобрали альтернативные варианты, которые помогут вам в реальной разработке.