Как проверить вхождение дат в заданные интервалы периодов с помощью запроса 1С?

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

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

Вариант 1. Комплексная проверка строк документа через пакетный запрос

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

Для решения задачи разберем по шагам построение пакетного запроса:

  1. Помещаем во временную таблицу ВТДанныеДокумента строки проверяемого документа.
  2. Выбираем разрешенные интервалы из табличной части справочника только по тем сценариям, которые присутствуют в документе, и помещаем их в ВТПериоды.
  3. Выполняем левое соединение данных документа с таблицей периодов по совпадению сценария и с помощью агрегатной функции СУММА и конструкции ВЫБОР КОГДА ... МЕЖДУ ... подсчитываем количество вхождений даты в интервалы.
  4. Итоговой выборкой отбираем те строки, где сумма вхождений равна нулю.

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


Запрос = Новый Запрос;
Запрос.Текст = 
    "ВЫБРАТЬ
    |	ЗаявкаНаРасход.Сценарий КАК Сценарий,
    |	ЗаявкаНаРасход.Подразделение КАК Подразделение,
    |	ЗаявкаНаРасход.Период КАК Дата
    |ПОМЕСТИТЬ ВТДанныеДокумента
    |ИЗ
    |	Документ.ЗаявкаНаРасход.ДанныеДок КАК ЗаявкаНаРасход
    |ГДЕ
    |	ЗаявкаНаРасход.Ссылка = &Ссылка
    |;
    |
    |////////////////////////////////////////////////////////////////////////////////
    |ВЫБРАТЬ
    |	СценарииПериоды.ДатаНачала КАК ДатаНачала,
    |	СценарииПериоды.ДатаОкончания КАК ДатаОкончания,
    |	СценарииПериоды.Ссылка КАК Сценарий
    |ПОМЕСТИТЬ ВТПериоды
    |ИЗ
    |	Справочник.Сценарии.Периоды КАК СценарииПериоды
    |ГДЕ
    |	СценарииПериоды.Ссылка В
    |		(ВЫБРАТЬ РАЗЛИЧНЫЕ
    |			ВТДанныеДокумента.Сценарий КАК Сценарий
    |		ИЗ
    |			ВТДанныеДокумента КАК ВТДанныеДокумента)
    |;
    |
    |////////////////////////////////////////////////////////////////////////////////
    |ВЫБРАТЬ
    |	СУММА(ВЫБОР
    |			КОГДА ВТДанныеДокумента.Дата МЕЖДУ ВТПериоды.ДатаНачала И ВТПериоды.ДатаОкончания
    |				ТОГДА 1
    |			ИНАЧЕ 0
    |		КОНЕЦ) КАК ВхождениеВПериод,
    |	ВТДанныеДокумента.Дата КАК Дата,
    |	ВТДанныеДокумента.Сценарий КАК Сценарий
    |ПОМЕСТИТЬ ВТВхождениеВПериодДаты
    |ИЗ
    |	ВТДанныеДокумента КАК ВТДанныеДокумента
    |		ЛЕВОЕ СОЕДИНЕНИЕ ВТПериоды КАК ВТПериоды
    |		ПО ВТДанныеДокумента.Сценарий = ВТПериоды.Сценарий
    |СГРУППИРОВАТЬ ПО
    |	ВТДанныеДокумента.Дата,
    |	ВТДанныеДокумента.Сценарий
    |;
    |
    |////////////////////////////////////////////////////////////////////////////////
    |ВЫБРАТЬ
    |	ВТВхождениеВПериодДаты.Дата КАК Дата,
    |	ВТВхождениеВПериодДаты.Сценарий КАК Сценарий
    |ИЗ
    |	ВТВхождениеВПериодДаты КАК ВТВхождениеВПериодДаты
    |ГДЕ
    |	ВТВхождениеВПериодДаты.ВхождениеВПериод = 0";

Запрос.УстановитьПараметр("Ссылка", Ссылка);
МассивРезультатов = Запрос.ВыполнитьПакет();
РезультатПроверки = МассивРезультатов[3];

Если НЕ РезультатПроверки.Пустой() Тогда
    Отказ = Истина;
    Сообщить("Дата одной из строк не входит в массив разрешенных периодов!");
КонецЕсли;

Вариант 2. Быстрая проверка единичной даты

Если требуется проверить всего одну конкретную дату (например, реквизит шапки документа), запрос можно существенно упростить, выполнив отбор непосредственно по табличной части нужного элемента справочника с условием ГДЕ:


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

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

Результат = Запрос.Выполнить();
Если Результат.Пустой() Тогда
    Сообщить("Дата не входит ни в один из доступных периодов!");
КонецЕсли;

Вариант 3. Соединение таблиц по условию диапазона (Non-Equi Join)

В случаях, когда проверяются документы из базы и требуется сразу отобрать ссылки, даты которых попадают в зарегистрированные интервалы, используют левое соединение с условием МЕЖДУ в секции ПО:


ВЫБРАТЬ РАЗЛИЧНЫЕ
	АвансовыйОтчет.Ссылка КАК Ссылка
ИЗ
	Документ.АвансовыйОтчет КАК АвансовыйОтчет
		ЛЕВОЕ СОЕДИНЕНИЕ Справочник.НастройкаИнтервалов.ТабличнаяЧасть КАК НастройкаИнтерваловТабличнаяЧасть
		ПО (АвансовыйОтчет.Дата МЕЖДУ НастройкаИнтерваловТабличнаяЧасть.НачалоИнтервала И НастройкаИнтерваловТабличнаяЧасть.КонецИнтервала)
ГДЕ
	НЕ НастройкаИнтерваловТабличнаяЧасть.Ссылка ЕСТЬ NULL

Важные нюансы работы с датами и оптимизация

Разберем типичные ошибки и особенности, которые необходимо учитывать при реализации подобных проверок:

  1. Учет времени: В платформе 1С тип Дата всегда содержит время. Если реквизит ДатаОкончания записан как начало дня (01.05.2024 00:00:00), а проверяемая дата содержит время внутри дня (01.05.2024 14:30:00), оператор МЕЖДУ вернет Ложь. Чтобы избежать ошибки, границу интервала в запросе следует приводить к концу дня через функцию КОНЕЦПЕРИОДА(..., ДЕНЬ) либо проверять условие строго до начала следующего дня с помощью функции ДОБАВИТЬКДАТЕ.
  2. Пустые и неограниченные границы: Если дата окончания периода не заполнена (в базе хранится 0001-01-01 00:00:00), проверка через МЕЖДУ будет работать некорректно. В таких случаях необходимо обрабатывать пустые даты конструкцией ВЫБОР КОГДА ... или разделять условия на отдельные проверки >= и <=.
  3. Производительность неэквивалентных соединений: Соединение таблиц по условиям диапазона (МЕЖДУ, >=, <=) мешает СУБД строить оптимальные планы через Hash Join или Merge Join, вынуждая использовать вложенные циклы (Nested Loops). На больших объемах данных соединение следует выполнять по ключевым полям (например, по ссылке на сценарий или подразделение), а проверку диапазона выносить в условие ВЫБОР или предикат ГДЕ СУЩЕСТВУЕТ.
← На главную