Как реализовать поиск данных в регистре по периоду без учета года

Программист 1С v8.3 (Обычные формы) Управленческий учет Промышленность, строительство и АПК
← На главную

При автоматизации учета часто возникает задача привязки данных (например, кодов ТН ВЭД, сезонных цен или коэффициентов) к определенному периоду внутри года. Основная сложность заключается в том, что стандартный тип данных Дата в платформе 1С всегда содержит год, а бизнес-логика требует, чтобы код «01.03 — 31.05» действовал ежегодно, независимо от того, какой сейчас год на календаре. Рассмотрим, как эффективно решить эту задачу, избежав типичных ошибок производительности.

Проблема хранения сезонных данных

Если мы создадим в регистре сведения колонки ДатаНачала и ДатаОкончания с типом Дата, нам придется каждый год переписывать записи, актуализируя год. Это неудобно и ведет к избыточности данных. Проанализируем несколько способов реализации «вечного» календаря в 1С.

Способ 1. Использование «Виртуального года» (Рекомендуемый)

Это самый простой и надежный метод. Мы договариваемся, что все даты в регистре хранятся с одним фиксированным годом (например, 2000-м или 0001-м). Это позволяет использовать стандартные функции языка запросов и сохраняет типизацию данных.

Реализация:

  1. В регистре сведений создаем измерения или ресурсы ДатаС и ДатаПо типа Дата.
  2. При записи данных программно приводим любую дату к 2000 году.
  3. В запросе дату документа также приводим к 2000 году.

Разберем пример кода для подготовки параметра запроса:


ТекущаяДатаДокумента = Объект.Дата;
// Создаем дату с тем же днем и месяцем, но 2000-м годом
ДатаДляСравнения = Дата(2000, Месяц(ТекущаяДатаДокумента), День(ТекущаяДатаДокумента));

Запрос = Новый Запрос;
Запрос.Текст = 
    "ВЫБРАТЬ
    |   ТНВЭД.КодТНВЭД
    |ИЗ
    |   РегистрСведений.КодыТНВЭД КАК ТНВЭД
    |ГДЕ
    |   ТНВЭД.Номенклатура = &Номенклатура
    |   И &ДатаДляСравнения МЕЖДУ ТНВЭД.ДатаС И ТНВЭД.ДатаПо";

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

Способ 2. Числовой индекс даты (MMDD)

Если вы хотите избежать работы с типом Дата из-за его привязки к календарю, можно использовать целые числа. Числовой индекс вычисляется по формуле: Месяц * 100 + День. В этом случае 1 марта превращается в 301 (3 * 100 + 1), а 31 декабря в 1231.

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

Посмотрим на пример формирования условия в запросе:


ИндексДаты = Месяц(ДатаДок) * 100 + День(ДатаДок);

// В регистре храним числа в ресурсах "ДеньНачалаИндекс" и "ДеньКонцаИндекс"
Запрос.Текст = 
    "ВЫБРАТЬ
    |   КодТНВЭД
    |ИЗ
    |   РегистрСведений.ип_дКодыТНВЭД
    |ГДЕ
    |   Номенклатура = &Номенклатура
    |   И &ИндексДаты >= ДеньНачалаИндекс 
    |   И &ИндексДаты <= ДеньКонцаИндекс";

Способ 3. Обработка переходящих периодов (Новый год)

Особую сложность представляют периоды, которые начинаются в одном году, а заканчиваются в другом (например, с 15 декабря по 15 января). Если использовать простое сравнение МЕЖДУ, такой период не будет найден, так как 1215 больше, чем 115.

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

Логическая схема в запросе:


"ГДЕ
|   Номенклатура = &Номенклатура И
|   (ВЫБОР 
|       КОГДА ДеньНачалаИндекс <= ДеньКонцаИндекс 
|       ТОГДА &ИндексДаты МЕЖДУ ДеньНачалаИндекс И ДеньКонцаИндекс
|       ИНАЧЕ &ИндексДаты >= ДеньНачалаИндекс ИЛИ &ИндексДаты <= ДеньКонцаИндекс
|   КОНЕЦ)"

Эта конструкция универсальна: она корректно отработает и обычные летние периоды, и «зимние» переходящие сроки.

Способ 4. Разбор строки (Вариант автора темы)

В ходе обсуждения автор предложил вариант с хранением дат в виде строки "ДД.ММ" и последующим разбором в цикле 1С. Рассмотрим, почему этот метод не является оптимальным, но разберем его логику для понимания работы с имеющимися данными.

Автор использовал следующий алгоритм:

  1. Выгрузить все записи по номенклатуре из регистра.
  2. В цикле Пока Выборка.Следующий() разбивать строку с помощью функций Лев() и Прав().
  3. Сравнивать отдельно месяц и день.

Пример кода автора:


Пока Выборка.Следующий Цикл
    ДатаСМ = Прав(Выборка.С, 2); // Получаем месяц начала
    ДатаСД = Лев(Выборка.С, 2);  // Получаем день начала
    // ... аналогично для даты окончания и текущей даты
    Если ДатаМ >= ДатаСМ И ДатаМ <= ДатаПоМ Тогда
        // Сложные вложенные условия для дней...
        Примечание = Выборка.КодТНВЭД;
    КонецЕсли;
КонецЦикла;

Внимание: Основной недостаток этого метода — производительность. Если у вас 10 000 позиций номенклатуры и для каждой нужно найти код в цикле, отчет или проведение документа будет работать крайне медленно. Старайтесь всегда переносить логику фильтрации в тело запроса (на сторону SQL-сервера).

Рекомендации по оптимизации

Выясним, как сделать работу с такими данными максимально быстрой:

Таким образом, для реализации сезонности в 1С лучше всего подходит метод фиксированного года или числового индекса. Это обеспечивает высокую скорость работы системы и чистоту программного кода.

← На главную