Как реализовать циклическое сложение дат в запросе 1С и возможно ли это

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

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

Анализ проблемы: циклы в запросах

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

Основная сложность заключается в том, что язык запросов 1С не поддерживает циклы. В отличие от процедурного языка 1С (где есть операторы Для и Пока), язык запросов является декларативным. Мы описываем, какие данные хотим получить, но не можем заставить СУБД выполнять итеративные вычисления «строка за строкой» с изменяющимся состоянием в рамках одного прохода.

Почему стандартная функция ДОБАВИТЬКДАТЕ не решает задачу напрямую

Функция ДОБАВИТЬКДАТЕ, доступная в запросах 1С, предназначена для разового вычисления. Мы можем написать:


ВЫБРАТЬ
    ДОБАВИТЬКДАТЕ(Таблица.Дата, ДЕНЬ, Таблица.Срок) КАК НоваяДата
ИЗ
    ВременнаяТаблица КАК Таблица

Это прибавит срок один раз. Однако мы не знаем заранее, сколько итераций потребуется: 5, 10 или 1000. Поскольку в запросе нельзя написать Пока ДатыНеРавны Цикл, прямое решение «в лоб» через запрос невозможно.

Решение 1: Программный цикл (рекомендуемый метод)

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


// Предположим, ТЗДанных уже заполнена колонками Дата и Срок
ВсеДатыРавны = Ложь;
Пока Не ВсеДатыРавны Цикл
    МаксимальнаяДата = ТЗДанных.Итог("Дата") / ТЗДанных.Количество(); // Грубая проверка
    // На самом деле лучше найти реальный максимум
    МаксимальнаяДата = '00010101';
    Для Каждого СтрокаТЗ Из ТЗДанных Цикл
        Если СтрокаТЗ.Дата > МаксимальнаяДата Тогда
            МаксимальнаяДата = СтрокаТЗ.Дата;
        КонецЕсли;
    КонецДля;
    
    ВсеДатыРавны = Истина;
    Для Каждого СтрокаТЗ Из ТЗДанных Цикл
        Пока СтрокаТЗ.Дата < МаксимальнаяДата Цикл
            СтрокаТЗ.Дата = ДобавитьМесяц(СтрокаТЗ.Дата, СтрокаТЗ.Срок); // Или другая логика
            ВсеДатыРавны = Ложь;
        КонецЦикла;
    КонецДля;
КонецЦикла;

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

Решение 2: Имитация цикла через вспомогательную таблицу чисел

Существует технический прием, позволяющий имитировать «цикл» в запросе. Он заключается в использовании заранее подготовленной таблицы, содержащей последовательность чисел (0, 1, 2, 3... N). Разберем этот метод по шагам.

  1. Создание таблицы итераций: Создадим временную таблицу, в которой будет столько строк, сколько максимально возможных «кругов» прибавления срока мы допускаем (например, 1000).
  2. Декартово произведение: Соединим нашу исходную таблицу с таблицей чисел без условия связи (Cross Join). Это размножит каждую исходную строку на N строк.
  3. Вычисление вариантов: Для каждой строки вычислим дату по формуле: ДОБАВИТЬКДАТЕ(ИсходнаяДата, ДЕНЬ, ЧислоИтерации * Срок).
  4. Поиск пересечения: Сгруппируем результат по вычисленной дате и выберем ту дату, которая встречается во всех исходных группах.

Проанализируем пример такого запроса:


ВЫБРАТЬ
    Цифры.Номер КАК Коэффициент
ПОМЕСТИТЬ ВТ_Итератор
ИЗ
    (ВЫБРАТЬ 0 КАК Номер ОБЪЕДИНИТЬ ВЫБРАТЬ 1 ... /* и так далее до нужного предела */) КАК Цифры
;

ВЫБРАТЬ
    Т.ИдентификаторСтроки,
    ДОБАВИТЬКДАТЕ(Т.Дата, ДЕНЬ, Итератор.Коэффициент * Т.Срок) КАК ВозможнаяДата
ПОМЕСТИТЬ ВТ_ВсеВарианты
ИЗ
    ВТ_ИсходныеДанные КАК Т
        СОЕДИНЕНИЕ ВТ_Итератор КАК Итератор ПО (ИСТИНА)
;

ВЫБРАТЬ
    Варианты.ВозможнаяДата
ИЗ
    ВТ_ВсеВарианты КАК Варианты
СГРУППИРОВАТЬ ПО
    Варианты.ВозможнаяДата
ИМЕЮЩИЕ
    КОЛИЧЕСТВО(РАЗЛИЧНЫЕ Варианты.ИдентификаторСтроки) = &ОбщееКоличествоСтрок

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

Математический подход к задаче

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

Выясним, можно ли вычислить это в запросе без перебора? К сожалению, в 1С отсутствуют функции для работы с остатком от деления (MOD) или сложные математические функции в языке запросов (хотя в SQL они есть). Поэтому математическое решение без перебора в запросе 1С практически нереализуемо.

Ограничения и рекомендации

Проанализируем, почему эксперты в обсуждении называют такие запросы «извращением». Использование декартова произведения (умножения строк) в запросе создает колоссальную нагрузку на сервер СУБД. Если в исходной таблице 100 строк, а итератор содержит 1000 чисел, временная таблица будет содержать 100 000 записей. Это может привести к замедлению работы системы и нерациональному использованию памяти.

Основные выводы:

  1. Не пытайтесь реализовать неопределенный цикл в запросе. Если количество итераций неизвестно, запрос — неподходящий инструмент.
  2. Используйте программную обработку. Это стандарт индустрии 1С для подобных задач. Код проще отлаживать, он не создает избыточных временных таблиц в памяти SQL-сервера.
  3. Если запрос необходим (например, внутри СКД без возможности использования внешних наборов данных), используйте метод с таблицей чисел, но строго ограничивайте диапазон возможных дат.

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

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

← На главную