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

Особенность работы с периодами в заключается в том, что система оперирует не только календарными датами, но и специальными объектами типа Дата, МоментВремени, а также предопределенными интервалами (например, НачалоДня() или КонецМесяца()). Мы рассмотрим все варианты — от ручного ввода дат до программного расчета границ периода на основе текущей сессии или пользовательских настроек.

Статья будет полезна как начинающим разработчикам, так и опытным программистам , которые хотят оптимизировать свои запросы. Все примеры приведены для актуальных версий платформы 1С:Предприятие 8.3 (включая последние релизы), но большинство подходов применимы и к более ранним версиям.

Базовый синтаксис указания периода в запросе

Самый простой способ ограничить выборку по дате — использовать конструкцию ГДЕ с сравнением полей типа Дата. Например, чтобы получить документы за конкретный день, достаточно написать:

ВЫБРАТЬ

Документ.Ссылка КАК Ссылка,

Документ.Дата КАК Дата

ИЗ

Документ.ПоступлениеТоваров КАК Документ

ГДЕ

Документ.Дата = &ДатаНачала

Где &ДатаНачала — параметр запроса, который можно передать из кода или интерактивно запросить у пользователя. Однако такой подход имеет ограничения:

  • 📅 Не учитывает время (если поле содержит ДатаВремя, сравнение будет точным до секунды).
  • 🔍 Не включает документы с датой позднее указанной (только точные совпадения).
  • ⚡ Может работать медленно на больших базах из-за отсутствия индексов по дате.

Для интервалов чаще используют диапазон с операторами >= и <=:

ВЫБРАТЬ

Документ.Номер,

Документ.Дата

ИЗ

Документ.РеализацияТоваровУслуг КАК Документ

ГДЕ

Документ.Дата >= &ДатаНачала

И Документ.Дата <= &ДатаОкончания

Такой запрос вернет все документы, попадающие в указанный промежуток включительно. Обратите внимание, что для полей типа ДатаВремя границы периода лучше задавать с учетом начала и конца дня:

ГДЕ

Документ.Дата >= НачалоДня(&ДатаНачала)

И Документ.Дата <= КонецДня(&ДатаОкончания)

💡

Если в запросе участвуют виртуальные таблицы (например, Документ.РеализацияТоваровУслуг.Обороты), всегда проверяйте, поддерживает ли таблица отбор по дате на уровне СУБД. В противном случае фильтрация будет выполняться на стороне 1С, что значительно замедлит работу.

Использование предопределенных функций для периодов

Платформа 1С:Предприятие предоставляет ряд встроенных функций для работы с датами, которые упрощают задание периодов. Их можно использовать как в самих запросах, так и при формировании параметров перед выполнением.

Функция Описание Пример использования
НачалоДня() Возвращает дату с временем 00:00:00 Дата >= НачалоДня(&ТекущаяДата)
КонецДня() Возвращает дату с временем 23:59:59 Дата <= КонецДня(&ТекущаяДата)
НачалоМесяца() Первый день месяца с временем 00:00:00 Дата >= НачалоМесяца(&ТекущаяДата)
КонецМесяца() Последний день месяца с временем 23:59:59 Дата <= КонецМесяца(&ТекущаяДата)
ДобавитьМесяц() Смещает дату на указанное количество месяцев Дата >= ДобавитьМесяц(ТекущаяДата(), -1)

Пример запроса, который получает данные за текущий месяц:

ВЫБРАТЬ

Товар.Наименование,

СУММА(ДокументОбороты.Количество) КАК Количество

ИЗ

Документ.РеализацияТоваровУслуг.Обороты(&ДатаНачала, &ДатаОкончания) КАК ДокументОбороты

ВНУТРЕННЕЕ СОЕДИНЕНИЕ Справочник.Номенклатура КАК Товар

ПО ДокументОбороты.Номенклатура = Товар.Ссылка

ГДЕ

ДокументОбороты.Дата >= НачалоМесяца(ТекущаяДата())

И ДокументОбороты.Дата <= КонецМесяца(ТекущаяДата())

СГРУППИРОВАТЬ ПО

Товар.Наименование

Особенно полезны эти функции при работе с виртуальными таблицами оборотов и остатков, где даты часто используются для расчета сальдо на начало или конец периода. Например, чтобы получить остатки товаров на конец предыдущего месяца:

ВЫБРАТЬ

Остатки.Номенклатура КАК Товар,

Остатки.КоличествоОстаток КАК Остаток

ИЗ

РегистрНакопления.ТоварыНаСкладах.Остатки(

&КонецПредыдущегоМесяца,

Номенклатура В (

ВЫБРАТЬ

Товары.Ссылка

ИЗ

Справочник.Номенклатура КАК Товары

ГДЕ

Товары.ЭтоГруппа = ЛОЖЬ

)

) КАК Остатки

📊 Какой тип периодов вы чаще всего используете в запросах 1С?
Фиксированные даты (с по)
Текущий день/месяц/год
Динамические (относительно текущей даты)
Периоды из пользовательских настроек

Динамические периоды: относительные даты

В многих случаях период нужно задавать не жестко, а относительно текущей даты или других параметров. Например, получить данные за последние 30 дней, за прошлый квартал или с начала года. Для этого используют арифметические операции с датами и функции смещения.

Пример запроса, который выбирает документы за последние 7 дней:

ВЫБРАТЬ

Документ.Дата,

Документ.СуммаДокумента

ИЗ

Документ.ПоступлениеДенежныхСредств КАК Документ

ГДЕ

Документ.Дата >= НачалоДня(ТекущаяДата() - 7)

И Документ.Дата <= КонецДня(ТекущаяДата())

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

// Начало текущего квартала

НачалоКвартала = НачалоМесяца(ДобавитьМесяц(ТекущаяДата(), - (Месяц(ТекущаяДата()) - 1) % 3));

// Конец текущего квартала

КонецКвартала = КонецМесяца(ДобавитьМесяц(НачалоКвартала, 2));

Аналогично можно получить начало года:

НачалоГода = Дата(Год(ТекущаяДата()), 1, 1);

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

Как рассчитать начало финансового года, если он не совпадает с календарным?

Если финансовый год начинается, например, с 1 октября, используйте конструкцию:

Если Месяц(ТекущаяДата()) >= 10 Тогда

НачалоФинГода = Дата(Год(ТекущаяДата()), 10, 1);

Иначе

НачалоФинГода = Дата(Год(ТекущаяДата()) - 1, 10, 1);

КонецЕсли;

Периоды в виртуальных таблицах: особенности и ошибки

Виртуальные таблицы (Обороты, Остатки, ОборотноСальдоваяВедомость и др.) требуют особого подхода к указанию периодов. Здесь даты используются не только для отбора, но и для расчета агрегированных данных.

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

// Правильно:

Обороты = Документ.РеализацияТоваровУслуг.Обороты(НачалоДня(&ДатаНачала), КонецДня(&ДатаОкончания));

// Ошибка (если &ДатаНачала > &ДатаОкончания):

Обороты = Документ.РеализацияТоваровУслуг.Обороты(&ДатаОкончания, &ДатаНачала);

Еще одна распространенная проблема — неучет временной зоны при работе с датами в распределенных базах или облачных решениях. Если сервер и клиент находятся в разных часовых поясах, ТекущаяДата() может возвращать неожиданные значения. В таких случаях лучше явно указывать дату в UTC или использовать серверное время:

НачалоДня(НачалоДня(ТекущаяДатаСеанса()))

При работе с виртуальными таблицами оборотов полезно помнить, что:

  • 📊 Таблица Обороты() возвращает движения документов внутри указанного периода.
  • 📈 Таблица Остатки() возвращает остатки на конец периода (если не указан второй параметр).
  • ⚠️ Таблица ОборотноСальдоваяВедомость() может требовать указания периода с запасом (например, с начала года), даже если нужны данные только за месяц.

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

ВЫБРАТЬ

Остатки.Номенклатура КАК Товар,

Остатки.КоличествоОстаток КАК Остаток

ИЗ

РегистрНакопления.Товары.Остатки(&ДатаКонтроля) КАК Остатки

Дата начала <= дата окончания

Указаны начало и конец дня (для полей ДатаВремя)

Период не выходит за пределы доступных данных в базе

Для виртуальных таблиц учтены особенности расчета (например, остатки "на дату" vs "за период")

-->

Периоды из пользовательских настроек и форм

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

Допустим, у нас есть форма с полями ПериодС и ПериодПо. Чтобы использовать их в запросе, нужно:

  1. Получить значения из элементов формы.
  2. Проверить корректность (например, что дата начала не позже даты окончания).
  3. Передать их в запрос как параметры.

Пример кода на встроенном языке:

Процедура СформироватьОтчет(Команда)

ДатаНачала = НачалоДня(ЭлементыФормы.ПериодС.Значение);

ДатаОкончания = КонецДня(ЭлементыФормы.ПериодПо.Значение);

Если ДатаНачала > ДатаОкончания Тогда

ПоказатьПредупреждение("Дата начала не может быть позже даты окончания!");

Возврат;

КонецЕсли;

Запрос = Новый Запрос;

Запрос.Текст =

"ВЫБРАТЬ

| Документ.Дата КАК Дата,

| Документ.Сумма КАК Сумма

|ИЗ

| Документ.ПоступлениеДенежныхСредств КАК Документ

|ГДЕ

| Документ.Дата >= &ДатаНачала

| И Документ.Дата <= &ДатаОкончания";

Запрос.УстановитьПараметр("ДатаНачала", ДатаНачала);

Запрос.УстановитьПараметр("ДатаОкончания", ДатаОкончания);

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

КонецПроцедуры

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

Настройки = РегистрыСведений.ПользовательскиеНастройки.ПолучитьПоследние(

Новый Структура("Пользователь, ТипНастройки",

ТекущийПользователь(), "ПериодОтчета"));

ДатаНачала = Настройки.ПериодС;

ДатаОкончания = Настройки.ПериодПо;

Для регламентных задач (например, ночных расчетов) период часто задается относительно даты выполнения задачи:

// Рассчитать данные за вчерашний день

ДатаНачала = НачалоДня(ТекущаяДата() - 1);

ДатаОкончания = КонецДня(ТекущаяДата() - 1);

💡

Всегда валидируйте периоды, полученные из внешних источников (форм, файлов, веб-сервисов). Даже если пользователь не может ввести некорректную дату в поле формы, проверка защитит от ошибок при программном изменении значений.

Оптимизация запросов с периодами

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

  1. Используйте индексы. Убедитесь, что поля, по которым идет отбор по дате, проиндексированы в конфигурации. Для регистров накопления индексы создаются автоматически, но для документов может потребоваться ручная настройка.
  2. Избегайте функций над полями. Запрос вида ГДЕ ГОД(Документ.Дата) = 2023 не сможет использовать индексы. Лучше писать ГДЕ Документ.Дата >= Дата(2023, 1, 1) И Документ.Дата <= Дата(2023, 12, 31).
  3. Ограничивайте период. Не запрашивайте данные за "всю историю", если нужны только последние несколько месяцев. Чем уже период, тем быстрее выполнится запрос.
  4. Используйте виртуальные таблицы с умом. Например, если нужны остатки на конкретную дату, лучше взять таблицу Остатки(), чем рассчитывать их вручную через обороты.

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

ВЫБРАТЬ

Документ.Дата,

Документ.Контрагент

ИЗ

Документ.РеализацияТоваровУслуг КАК Документ

ГДЕ

МЕСЯЦ(Документ.Дата) = 5 // Не использует индексы!

И ГОД(Документ.Дата) = 2023

Оптимизированный вариант:

ВЫБРАТЬ

Документ.Дата,

Документ.Контрагент

ИЗ

Документ.РеализацияТоваровУслуг КАК Документ

ГДЕ

Документ.Дата >= Дата(2023, 5, 1)

И Документ.Дата <= Дата(2023, 5, 31)

Для сложных отчетов, где периоды используются в нескольких запросах, имеет смысл вынести расчет границ в отдельную функцию:

Функция ПолучитьПериодОтчета(ТипПериода, ТекущаяДата = Неопределено)

Если ТекущаяДата = Неопределено Тогда

ТекущаяДата = ТекущаяДата();

КонецЕсли;

Если ТипПериода = "Месяц" Тогда

Возврат Новый Структура("С, По",

НачалоМесяца(ТекущаяДата),

КонецМесяца(ТекущаяДата));

ИначеЕсли ТипПериода = "Квартал" Тогда

Возврат Новый Структура("С, По",

НачалоКвартала(ТекущаяДата),

КонецКвартала(ТекущаяДата));

КонецЕсли;

КонецФункции

💡

Если запрос все равно выполняется долго, проверьте план выполнения в консоли запросов (меню "Все функции" → "План запроса"). Это поможет выявить узкие места, например, полное сканирование таблиц вместо использования индексов.

Типичные ошибки и их решения

Даже опытные разработчики иногда сталкиваются с проблемами при работе с периодами в запросах. Рассмотрим самые распространенные ошибки и способы их исправления.

⚠️ Внимание: Если в запросе используются виртуальные таблицы оборотов или остатков, убедитесь, что передаваемые даты попадают в диапазон доступных данных в базе. Например, запрос остатков на дату, предшествующую дате начального остатка, вернет некорректные результаты.
Ошибка Причина Решение
Запрос возвращает пустой результат при корректных данных Не учтено время в полях ДатаВремя (например, сравнение с ТекущаяДата() без обрезки времени) Использовать НачалоДня()/КонецДня() для границ периода
Ошибка "Некорректный период" В виртуальную таблицу переданы даты в обратном порядке (ДатаНачала > ДатаОкончания) Проверять порядок дат перед выполнением запроса
Запрос выполняется слишком долго Отсутствуют индексы по полям дат или используются функции над полями в условии Переписать условие без функций (например, заменить ГОД(Дата) = 2023 на диапазон дат)
Несовпадение данных с бухгалтерскими отчетами В запросе не учтены документы с датой равной границе периода (например, Дата < КонецМесяца вместо Дата <= КонецМесяца) Всегда использовать >= и <= для включения границ
Ошибка при работе с распределенной базой Не учтена разница во временных зонах между сервером и клиентом Использовать ТекущаяДатаСеанса() или явно указывать даты в UTC

Еще одна распространенная проблема — несоответствие периодов в связанных запросах. Например, если в основном запросе используется период с 1 по 31 января, а во вложенном подзапросе — с 1 по 30 января, результаты могут оказаться несогласованными. Всегда синхронизируйте границы периодов во всех частях запроса.

Пример ошибки с временными зонами:

// На клиенте (Москва, UTC+3):

ДатаНачала = ТекущаяДата(); // 2023-10-01 12:00:00

// На сервере (UTC):

// Та же дата будет интерпретирована как 2023-10-01 09:00:00

Решение — явно привести дату к нужной зоне:

ДатаНачала = НачалоДня(ТекущаяДатаСеанса()); // Использует дату сеанса

FAQ: Частые вопросы по периодам в запросах 1С

Как получить данные за "последний полный месяц"?

Используйте комбинацию функций НачалоМесяца() и ДобавитьМесяц():

ДатаНачала = НачалоМесяца(ДобавитьМесяц(ТекущаяДата(), -1));

ДатаОкончания = КонецМесяца(ДобавитьМесяц(ТекущаяДата(), -1));

Это вернет период с 1 по последнее число прошлого месяца.

Почему запрос с периодом работает медленно, хотя данных мало?

Возможные причины:

  • Отсутствуют индексы по полю даты в таблице.
  • В условии используются функции над полем (например, ГОД(Дата)), что блокирует использование индексов.
  • Запрос обращается к виртуальным таблицам без ограничения по дате на уровне СУБД.

Проверьте план выполнения запроса в консоли запросов.

Как передать период в запрос из отчета СКД?

В схеме компоновки данных период можно получить из параметров или пользовательских полей. Пример:

  1. В настройках СКД добавьте параметры ДатаНачала и ДатаОкончания.
  2. В запросе СКД используйте их как &ДатаНачала и &ДатаОкончания.
  3. При формировании отчета передайте значения параметров из формы.

Пример кода:

Отчет.КомпоновщикНастроек.Настройки.ПараметрыДанных.УстановитьЗначениеПараметра("ДатаНачала", НачалоМесяца(ТекущаяДата()));

Отчет.КомпоновщикНастроек.Настройки.ПараметрыДанных.УстановитьЗначениеПараметра("ДатаОкончания", КонецМесяца(ТекущаяДата()));

Можно ли в одном запросе использовать разные периоды для разных таблиц?

Да, но это может привести к неожиданным результатам. Например:

ВЫБРАТЬ

Товары.Наименование,

Обороты.Количество

ИЗ

Справочник.Номенклатура КАК Товары

ЛЕВОЕ СОЕДИНЕНИЕ Документ.РеализацияТоваровУслуг.Обороты(

&ДатаНачала1, &ДатаОкончания1) КАК Обороты

ПО Товары.Ссылка = Обороты.Номенклатура

ГДЕ

Обороты.Дата >= &ДатаНачала2 // Другой период!

В этом случае условие ГДЕ будет применено после соединения, что может исказить результаты. Лучше использовать одинаковые периоды или разбивать запрос на части.

Как работать с периодами в распределенных базах (УРИБ, УРБД)?

В распределенных базах данные могут быть разнесены по узлам с разными временными зонами. Рекомендации:

  • Используйте ТекущаяДатаСеанса() вместо ТекущаяДата().
  • Храните даты в UTC и преобразуйте их при отображении.
  • Для регламентных задач синхронизируйте время выполнения между узлами.

Пример преобразования:

// Преобразовать локальную дату в UTC

ДатаUTC = Дата(Год(ЛокальнаяДата), Месяц(ЛокальнаяДата), День(ЛокальнаяДата),

Час(ЛокальнаяДата) - СмещениеЧасов, Минута(ЛокальнаяДата), 0);