Пересечение периодов 1С: проверка запросом и при записи

Дата: 19.09.2026 09:47:59
Запросы
Язык запросов
Даты
Периоды
Проведение документов
Комментарий:
0
Платформа:
8.3
Конфигурация:
Любая
Формы:
Любые

Пересечение периодов в 1С проверяют там, где одно и то же не может случиться дважды в одно время: сотрудник не бывает одновременно в отпуске и в командировке, автомобиль не выдают двум водителям на одни даты, у договора нет двух действующих тарифов на один день. Ниже — условие пересечения в одну строку, проверка по заданному периоду, поиск пересекающихся пар самосоединением с длиной пересечения в днях и контроль при проведении. Код для платформы 8.3, имена объектов условные.

Где встречается задача

  • Кадровые документы: отпуск, больничный, командировка.
  • Периоды действия: договоры, скидки, тарифы, доверенности.
  • Бронирование: переговорные, транспорт, оборудование.
  • Сверка данных, загруженных из Excel или другой системы.

Везде у записи есть дата начала и окончания, а вопрос один: есть ли у двух отрезков общая точка.

Пересечение периодов 1С: условие в одну строку

Частая ошибка — перебирать варианты: начало второго периода внутри первого, конец внутри первого, один внутри другого. В четырёх условиях через ИЛИ легко оставить дыру. Проще от обратного: периоды не пересекаются, только если один закончился раньше, чем начался другой. Отрицание этого и есть условие пересечения:

Начало1 <= Конец2 И Конец1 >= Начало2

Оно покрывает все варианты сразу:

СитуацияПериод 1Период 2Результат
Второй начинается внутри первого01.03–10.0305.03–15.03пересекаются
Второй заканчивается внутри первого01.03–10.0320.02–03.03пересекаются
Второй целиком внутри первого01.03–31.0310.03–12.03пересекаются
Первый целиком внутри второго10.03–12.0301.03–31.03пересекаются
Стык: конец одного равен началу другого01.03–10.0310.03–20.03пересекаются (общий день 10.03)
Второй позже первого01.03–10.0311.03–20.03не пересекаются

Включать ли границы

С нестрогими сравнениями <= и >= граница входит в период: отпуск по 10 марта включительно и командировка с 10 марта — конфликт. Для кадровых документов и договоров это правильно. В бронировании по времени обычно наоборот: переговорная занята с 10:00 до 11:00, встреча с 11:00 ей не мешает. Тогда окончание в период не входит, и сравнения строгие: Начало1 < Конец2 И Конец1 > Начало2.

Пустая дата окончания

Бессрочный договор часто хранят с пустой датой окончания, а пустая дата в 1С — это 01.01.0001, самая ранняя в сравнениях: для проверки такой период «закончился» в первом году нашей эры. Пустую границу заменяют максимальной датой: в запросе ДАТАВРЕМЯ(3999, 12, 31), во встроенном языке Дата(3999, 12, 31, 23, 59, 59). Про литералы и функции дат подробнее — в статье о датах в запросе 1С.

Проверка пересечения интервалов дат в запросе 1С

Простейший вариант пересечения периодов в запросе: есть период (отчёта, новой брони), и нужно найти всё, что с ним пересекается. Для примера возьмём документ «Командировка» с реквизитами Сотрудник, ДатаНачала и ДатаОкончания (тип Дата, состав «Дата»).

ВЫБРАТЬ
	Командировка.Ссылка КАК Документ,
	Командировка.Сотрудник КАК Сотрудник,
	Командировка.ДатаНачала КАК ДатаНачала,
	Командировка.ДатаОкончания КАК ДатаОкончания
ИЗ
	Документ.Командировка КАК Командировка
ГДЕ
	Командировка.Проведен
	И Командировка.ДатаНачала <= &КонецПериода
	И (Командировка.ДатаОкончания >= &НачалоПериода
			ИЛИ Командировка.ДатаОкончания = ДАТАВРЕМЯ(1, 1, 1))

Условие в скобках со сравнением с ДАТАВРЕМЯ(1, 1, 1) подхватывает бессрочные записи. Если дата окончания у вас обязательна, эту часть можно убрать. Отборы документов за период подробнее — в статье про запросы к документам.

Найти пересекающиеся периоды запросом: самосоединение

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

// Пересекающиеся периоды одного сотрудника и длина пересечения в днях.
// Параметры: &НачалоПериода, &КонецПериода - период анализа.
// Дни пересечения считаются не дальше &КонецПериода.
ВЫБРАТЬ
	Командировка.Ссылка КАК Ссылка,
	Командировка.Сотрудник КАК Сотрудник,
	Командировка.ДатаНачала КАК ДатаНачала,
	ВЫБОР
		КОГДА Командировка.ДатаОкончания = ДАТАВРЕМЯ(1, 1, 1)
			ТОГДА ДАТАВРЕМЯ(3999, 12, 31)
		ИНАЧЕ Командировка.ДатаОкончания
	КОНЕЦ КАК ДатаОкончания
ПОМЕСТИТЬ ВТПериоды
ИЗ
	Документ.Командировка КАК Командировка
ГДЕ
	Командировка.Проведен
	И Командировка.ДатаНачала <= &КонецПериода
	И (Командировка.ДатаОкончания >= &НачалоПериода
			ИЛИ Командировка.ДатаОкончания = ДАТАВРЕМЯ(1, 1, 1))

ИНДЕКСИРОВАТЬ ПО
	Сотрудник
;

////////////////////////////////////////////////////////////////////////////////
ВЫБРАТЬ
	Т1.Сотрудник КАК Сотрудник,
	Т1.Ссылка КАК Документ,
	Т2.Ссылка КАК ПересекаетсяС,
	ВЫБОР
		КОГДА Т1.ДатаНачала > Т2.ДатаНачала
			ТОГДА Т1.ДатаНачала
		ИНАЧЕ Т2.ДатаНачала
	КОНЕЦ КАК НачалоПересечения,
	ВЫБОР
		КОГДА Т1.ДатаОкончания < Т2.ДатаОкончания
			ТОГДА Т1.ДатаОкончания
		ИНАЧЕ Т2.ДатаОкончания
	КОНЕЦ КАК ОкончаниеПересечения
ПОМЕСТИТЬ ВТПересечения
ИЗ
	ВТПериоды КАК Т1
		ВНУТРЕННЕЕ СОЕДИНЕНИЕ ВТПериоды КАК Т2
		ПО Т1.Сотрудник = Т2.Сотрудник
			И Т1.Ссылка <> Т2.Ссылка
			И Т1.ДатаНачала <= Т2.ДатаОкончания
			И Т1.ДатаОкончания >= Т2.ДатаНачала
;

////////////////////////////////////////////////////////////////////////////////
ВЫБРАТЬ
	ВТПересечения.Сотрудник КАК Сотрудник,
	ВТПересечения.Документ КАК Документ,
	ВТПересечения.ПересекаетсяС КАК ПересекаетсяС,
	ВТПересечения.НачалоПересечения КАК НачалоПересечения,
	ВТПересечения.ОкончаниеПересечения КАК ОкончаниеПересечения,
	РАЗНОСТЬДАТ(ВТПересечения.НачалоПересечения,
		ВЫБОР
			КОГДА ВТПересечения.ОкончаниеПересечения > &КонецПериода
				ТОГДА &КонецПериода
			ИНАЧЕ ВТПересечения.ОкончаниеПересечения
		КОНЕЦ, ДЕНЬ) + 1 КАК ДнейПересечения
ИЗ
	ВТПересечения КАК ВТПересечения

УПОРЯДОЧИТЬ ПО
	Сотрудник,
	НачалоПересечения

Разбор ключевых строк

  • ВТПериоды. Первый пакет приводит пустую дату окончания к 31.12.3999 и отбирает только документы, которые задевают период анализа. ИНДЕКСИРОВАТЬ ПО Сотрудник ускоряет соединение на больших объёмах.
  • Т1.Ссылка <> Т2.Ссылка. Без этого условия каждый документ «пересекается» сам с собой.
  • Т1.Сотрудник = Т2.Сотрудник. Конфликт имеет смысл только для одного человека. Если общий ресурс — переговорная или машина, соединяйте по ресурсу.
  • Начало и окончание пересечения. Пересечение начинается с более поздней из двух дат начала и заканчивается более ранней из двух дат окончания. Агрегатные МАКСИМУМ и МИНИМУМ считают по группе строк, а не по двум полям одной строки, поэтому здесь ВЫБОР.
  • РАЗНОСТЬДАТ(..., ДЕНЬ) + 1. С единицей ДЕНЬ функция отбрасывает время. Единица прибавляется, потому что обе границы включены: пересечение 10.03–10.03 — один день, а не ноль. Окончание для подсчёта обрезается концом периода анализа, иначе две бессрочные записи дадут около 720 тысяч дней. Дата 31.12.3999 в колонке ОкончаниеПересечения означает «бессрочно».
  • Каждая пара выводится дважды: «А с Б» и «Б с А», зато у каждого документа видно, с чем он спорит. Нужен только список проблемных документов — выберите РАЗЛИЧНЫЕ поле Документ из ВТПересечения.

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

Контроль пересечения при записи документа

Отчёт ловит проблемы задним числом. Чтобы их не создавать, проверку ставят в обработчик ПередЗаписью модуля объекта документа. Она срабатывает при проведении и при записи уже проведённого документа. Непроведённый черновик с пересечением сохранить можно, провести нельзя.

// Модуль объекта документа «Командировка». Выполняется на сервере.

Процедура ПередЗаписью(Отказ, РежимЗаписи, РежимПроведения)

	Если ОбменДанными.Загрузка Тогда
		Возврат;
	КонецЕсли;

	// Непроведённый черновик можно записать и с пересечением. Проведение и запись
	// уже проведённого документа (программная Записать() без параметра) проверяем.
	Если РежимЗаписи = РежимЗаписиДокумента.ОтменаПроведения
		Или (РежимЗаписи = РежимЗаписиДокумента.Запись И Не Проведен) Тогда
		Возврат;
	КонецЕсли;

	ПроверитьПересечениеПериодов(Отказ);

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

Процедура ПроверитьПересечениеПериодов(Отказ)

	Если Не ЗначениеЗаполнено(Сотрудник) Или Не ЗначениеЗаполнено(ДатаНачала) Тогда
		// Из формы пустые реквизиты остановит проверка заполнения при проведении;
		// при программном проведении вызывайте ПроверитьЗаполнение() перед Записать().
		Возврат;
	КонецЕсли;

	Если ЗначениеЗаполнено(ДатаОкончания) И ДатаОкончания < ДатаНачала Тогда
		СообщитьОбОшибке("Дата окончания меньше даты начала.", "ДатаОкончания", Отказ);
		Возврат;
	КонецЕсли;

	// Документы одного сотрудника проверяются по очереди: блокировка держится
	// до конца транзакции записи. Справочник.Сотрудники - условное имя.
	Блокировка = Новый БлокировкаДанных;
	ЭлементБлокировки = Блокировка.Добавить("Справочник.Сотрудники");
	ЭлементБлокировки.УстановитьЗначение("Ссылка", Сотрудник);
	ЭлементБлокировки.Режим = РежимБлокировкиДанных.Исключительный;
	Блокировка.Заблокировать();

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

	Запрос.УстановитьПараметр("Ссылка", Ссылка);
	Запрос.УстановитьПараметр("Сотрудник", Сотрудник);
	Запрос.УстановитьПараметр("Начало", ДатаНачала);
	Запрос.УстановитьПараметр("Окончание",
		?(ЗначениеЗаполнено(ДатаОкончания), ДатаОкончания, Дата(3999, 12, 31, 23, 59, 59)));

	// Конфликт ищем среди всех документов сотрудника, а не только доступных пользователю.
	УстановитьПривилегированныйРежим(Истина);
	Выборка = Запрос.Выполнить().Выбрать();
	УстановитьПривилегированныйРежим(Ложь);

	Если Не Выборка.Следующий() Тогда
		Возврат;
	КонецЕсли;

	ОкончаниеСтрокой = ?(ЗначениеЗаполнено(Выборка.ДатаОкончания),
		Формат(Выборка.ДатаОкончания, "ДЛФ=D"), "без даты окончания");
	ТекстОшибки = СтрШаблон("Период пересекается с документом %1 (%2 - %3).",
		Выборка.Ссылка, Формат(Выборка.ДатаНачала, "ДЛФ=D"), ОкончаниеСтрокой);
	СообщитьОбОшибке(ТекстОшибки, "ДатаНачала", Отказ);

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

Процедура СообщитьОбОшибке(Текст, Поле, Отказ)

	Сообщение = Новый СообщениеПользователю;
	Сообщение.Текст = Текст;
	Сообщение.Поле = Поле;
	Сообщение.УстановитьДанные(ЭтотОбъект);
	Сообщение.Сообщить();
	Отказ = Истина;

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

Что здесь важно:

  • ОбменДанными.Загрузка — при загрузке по обмену прикладные проверки пропускают, иначе встанет синхронизация.
  • Условие по РежимЗаписи пропускает только отмену проведения и запись непроведённого черновика. Запись проведённого документа без перепроведения (ДокОбъект.Записать() из обработки) тоже проверяется.
  • Командировка.Ссылка <> &Ссылка — без этого условия перепроводимый документ найдёт сам себя. Новому документу с пустой ссылкой оно не мешает.
  • Параметр &Окончание для бессрочного документа — максимальная дата, иначе новая бессрочная запись никогда не найдёт конфликтов.
  • УстановитьПривилегированныйРежим(Истина) — без него запрос идёт с правами пользователя. При ограничении доступа на уровне записей он упадёт с ошибкой доступа, а с РАЗРЕШЕННЫЕ молча пропустит документы, которые пользователь не видит.
  • ПЕРВЫЕ 1 — для отказа хватит одного конфликта. Чтобы показать все, уберите ПЕРВЫЕ 1 и обойдите выборку циклом.
  • СтрШаблон доступна с платформы 8.3.6. В конфигурациях на БСП 3.x вместо процедуры СообщитьОбОшибке можно вызвать ОбщегоНазначения.СообщитьПользователю(ТекстОшибки, ЭтотОбъект, "ДатаНачала", , Отказ) — этот метод сам установит Отказ = Истина.

Параллельное проведение

Сам по себе запрос не спасает от гонки в клиент-серверной базе: две командировки одного сотрудника проводятся одновременно, каждый запрос не видит чужой, ещё не зафиксированный документ, и обе проходят. Поэтому перед запросом стоит исключительная управляемая блокировка по сотруднику. ПередЗаписью выполняется в транзакции записи, блокировка держится до её конца: второй документ ждёт и после фиксации первого уже видит конфликт. Для переговорной или машины блокируйте ссылку на ресурс. Нужен управляемый режим блокировки данных в свойствах конфигурации.

Проверка во встроенном языке, без запроса

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

// Общий модуль ПериодыКлиентСервер (флаги «Клиент (управляемое приложение)» и «Сервер»).
// К базе данных не обращается, поэтому доступен в любом контексте.

// Истина, если периоды [Начало1; Конец1] и [Начало2; Конец2] имеют общую точку.
// Пустая дата окончания означает «без ограничения».
Функция ПериодыПересекаются(Начало1, Конец1, Начало2, Конец2) Экспорт

	Возврат Начало1 <= ГраницаОкончания(Конец2)
		И Начало2 <= ГраницаОкончания(Конец1);

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

// Количество общих дней двух периодов (время отбрасывается), 0 - если общих дней нет.
Функция ДнейПересечения(Начало1, Конец1, Начало2, Конец2) Экспорт

	НачалоОбщее = НачалоДня(Макс(Начало1, Начало2));
	ОкончаниеОбщее = НачалоДня(Мин(ГраницаОкончания(Конец1), ГраницаОкончания(Конец2)));

	Если НачалоОбщее > ОкончаниеОбщее Тогда
		Возврат 0;
	КонецЕсли;

	Возврат (ОкончаниеОбщее - НачалоОбщее) / 86400 + 1;

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

Функция ГраницаОкончания(ДатаОкончания)

	Если ЗначениеЗаполнено(ДатаОкончания) Тогда
		Возврат ДатаОкончания;
	КонецЕсли;

	Возврат Дата(3999, 12, 31, 23, 59, 59);

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

Пример использования — строки табличной части не должны накладываться друг на друга:

// Модуль объекта документа, например внутри ОбработкаПроверкиЗаполнения(Отказ, ПроверяемыеРеквизиты).
// Строки табличной части «Периоды» не должны накладываться друг на друга.
ЧислоСтрок = Периоды.Количество();
Для Индекс1 = 0 По ЧислоСтрок - 2 Цикл
	Строка1 = Периоды[Индекс1];
	Для Индекс2 = Индекс1 + 1 По ЧислоСтрок - 1 Цикл
		Строка2 = Периоды[Индекс2];
		Если ПериодыКлиентСервер.ПериодыПересекаются(Строка1.ДатаНачала, Строка1.ДатаОкончания,
				Строка2.ДатаНачала, Строка2.ДатаОкончания) Тогда
			Сообщение = Новый СообщениеПользователю;
			Сообщение.Текст = СтрШаблон("Строки %1 и %2 пересекаются по датам.",
				Формат(Строка1.НомерСтроки, "ЧГ=0"), Формат(Строка2.НомерСтроки, "ЧГ=0"));
			// Индекс строки в пути к полю считается от нуля.
			Сообщение.Поле = СтрШаблон("Периоды[%1].ДатаНачала", Формат(Индекс2, "ЧН=0; ЧГ=0"));
			Сообщение.УстановитьДанные(ЭтотОбъект);
			Сообщение.Сообщить();
			Отказ = Истина;
		КонецЕсли;
	КонецЦикла;
КонецЦикла;

Сообщение привязано к строке табличной части, а "ЧГ=0" убирает пробел в номерах от 1000. Двойной цикл сравнивает каждую пару строк один раз. На десятках строк это незаметно, на тысячах лучше передать табличную часть в запрос временной таблицей и сделать то же самосоединение — см. статью о таблице значений в запросе.

Типичные ошибки

  • Проверяют только, попадают ли начало или конец нового периода в старый. Пропускается случай, когда новый период целиком накрывает старый: 01.03–31.03 против 10.03–12.03. Условие из двух сравнений такой дыры не имеет.
  • Бессрочные записи не находятся. Пустая дата окончания сравнивается как 01.01.0001. Лечится заменой на ДАТАВРЕМЯ(3999, 12, 31) или условием ИЛИ ДатаОкончания = ДАТАВРЕМЯ(1, 1, 1).
  • Программная запись обходит контроль. Проверка стоит только на РежимЗаписи = Проведение, а обработка меняет даты проведённого документа через Записать() без параметра: документ остаётся проведённым, пересечение появляется.
  • Документ конфликтует сам с собой. При перепроведении появляется сообщение «Период пересекается с документом…» со ссылкой на тот же документ. Забыто условие Ссылка <> &Ссылка.
  • Выпадает последний день. В реквизитах есть время, а конец периода передан как НачалоДня(). Для параметра окончания используйте КонецДня().
  • Проверка с запросом вызвана в клиентской процедуре формы. Модуль не компилируется с ошибкой «Тип не определен (Запрос)»: на клиенте нет ни запросов, ни объектов базы. Запрос — только на сервере.
  • Самосоединение без отбора по периоду. На базе с многолетней историей запрос будет работать очень долго. Ограничивайте выборку периодом анализа и соединяйте по индексированному полю. Если отчёт всё равно медленный, посмотрите что делать, когда 1С тормозит.

Частые вопросы

Как найти общий отрезок, в котором пересекаются сразу несколько периодов?

Возьмите самую позднюю дату начала и самую раннюю дату окончания: в запросе это МАКСИМУМ(ДатаНачала) и МИНИМУМ(ВЫБОР КОГДА ДатаОкончания = ДАТАВРЕМЯ(1, 1, 1) ТОГДА ДАТАВРЕМЯ(3999, 12, 31) ИНАЧЕ ДатаОкончания КОНЕЦ) с группировкой, например по сотруднику. Без замены пустой даты бессрочная запись станет минимумом. Если максимум начала не больше минимума окончания, общий отрезок лежит ровно между этими датами, иначе хотя бы два периода набора не пересекаются.

Периоды 01.03–10.03 и 10.03–20.03 пересекаются?

С условием на <= — да, общий день 10 марта. Если день окончания в период не входит (аренда до 10-го, следующий арендатор с 10-го), переходите на строгие сравнения сразу во всех проверках, иначе отчёт и контроль при проведении разойдутся.

Где лучше ставить проверку: в ПередЗаписью или в ОбработкаПроверкиЗаполнения?

У документа ОбработкаПроверкиЗаполнения вызывается при интерактивном проведении из формы и при явном ПроверитьЗаполнение(), а при записи черновика и программной записи — нет. Зато она выполняется вне транзакции записи. ПередЗаписью вызывается при любой записи, загрузку по обмену пропускает сам код условием на ОбменДанными.Загрузка. Если документы проводят и из кода, выбирайте ПередЗаписью: блокировка от параллельного проведения тоже работает только в транзакции.

Можно ли проверять пересечение в типовой конфигурации без снятия с поддержки?

Да, через расширение конфигурации: в расширении добавляется обработчик события модуля объекта документа, основная конфигурация остаётся на поддержке.

Когда проще заказать

Если контроль пересечений нужно встроить в типовую конфигурацию через расширение, сделать отчёт по конфликтам или защитить проверку от одновременного проведения, это удобнее поручить программисту, который знает вашу базу. Опишите, какие документы и какие даты не должны накладываться, и разместите задачу на 1c-only.ru — оценка бесплатная, ответ в течение рабочего дня. Подробнее об услуге — отчёты и обработки 1С.

По теме

Не хотите разбираться сами?

Адаптируем это решение под вашу базу. Оценка бесплатно.

Оставить задачу

Станьте частью сообщества!
Войдите или зарегистрируйтесь, и вы сможете участвовать в обсуждениях.


1С, 1С:Предприятие — товарные знаки ООО «1С». Сайт не является официальным представительством фирмы «1С» и не аффилирован с ней.