Как в запросе 1С получить даты двух разных событий для одного инвентарного номера ОС?

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

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

Если написать простой запрос с условием ГДЕ СобытияОС.Событие = &Событие1 ИЛИ СобытияОС.Событие = &Событие2, то СУБД вернет две отдельные строки. Разберем по шагам, как правильно составить запрос 1С, чтобы объединить данные по двум событиям в одну строку с двумя раздельными колонками.

Причина сложности стандартного отбора

Проанализируем ситуацию: каждая запись в регистре сведений хранит только одно конкретное событие и одну дату. Простой отбор через секцию ГДЕ фильтрует строки, но не превращает строки одного столбца в отдельные колонки одной записи. Чтобы получить одну строку с колонками ПринятоКУчету и СнятоСУчета, необходимо применить группировку с условным выбором, соединение таблицы самой с собой либо пакетный запрос с временными таблицами.

Способ 1. Группировка и условные выражения ВЫБОР с агрегатной функцией МАКСИМУМ (Рекомендуемый)

Рассмотрим наиболее эффективный метод с точки зрения производительности СУБД. В этом варианте таблица считывается за один проход без использования сложных соединений. Мы используем конструкцию ВЫБОР для фильтрации даты по конкретному событию и агрегатную функцию МАКСИМУМ для схлопывания результатов в одну строку при группировке по инвентарному номеру.

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


ВЫБРАТЬ
	СобытияОС.ИнвНомер КАК ИнвНомер,
	МАКСИМУМ(ВЫБОР
			КОГДА СобытияОС.Событие = &СобытиеПринятие
				ТОГДА СобытияОС.ДатаСобытия
			ИНАЧЕ ДАТАВРЕМЯ(1, 1, 1)
		КОНЕЦ) КАК ПринятоКУчету,
	МАКСИМУМ(ВЫБОР
			КОГДА СобытияОС.Событие = &СобытиеСнятие
				ТОГДА СобытияОС.ДатаСобытия
			ИНАЧЕ ДАТАВРЕМЯ(1, 1, 1)
		КОНЕЦ) КАК СнятоСУчета
ИЗ
	РегистрСведений.СобытияОС КАК СобытияОС
ГДЕ
	СобытияОС.ИнвНомер = &СобИнвНомер
	И СобытияОС.Событие В(&МассивСобытий)
ГРУППИРОВАТЬ ПО
	СобытияОС.ИнвНомер

Разберем особенности работы этого запроса:

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

Способ 2. Использование ЛЕВОГО СОЕДИНЕНИЯ таблицы самой с собой

Рассмотрим случай, когда принятие к учету должно присутствовать обязательно, а событие снятия с учета может отсутствовать. В такой ситуации мы можем вытащить регистр СобытияОС дважды и связать две таблицы по инвентарному номеру с помощью ЛЕВОЕ СОЕДИНЕНИЕ.


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

Важно: Обратите внимание на функцию ЕСТЬNULL. Если по объекту ОС нет записи о снятии с учета, левое соединение вернет значение NULL для колонки даты выбытия. Функция ЕСТЬNULL корректно заменит его на пустую дату ДАТАВРЕМЯ(1, 1, 1).

Способ 3. Пакет запросов с временными таблицами

Выясним, как решить эту задачу в случае с периодическим регистром сведений, когда необходимо использовать виртуальные таблицы СрезПервых и СрезПоследних для защиты от дублирования записей при повторных событиях.


ВЫБРАТЬ
	СобытияОССрезПервых.ИнвНомер КАК ИнвНомер,
	СобытияОССрезПервых.Период КАК ДатаПринятия
ПОМЕСТИТЬ ВТ_ПринятиеКУчету
ИЗ
	РегистрСведений.СобытияОС.СрезПервых(&Период, ИнвНомер = &СобИнвНомер И Событие = &СобытиеПринятие) КАК СобытияОССрезПервых
;

////////////////////////////////////////////////////////////////////////////////
ВЫБРАТЬ
	СобытияОССрезПоследних.ИнвНомер КАК ИнвНомер,
	СобытияОССрезПоследних.Период КАК ДатаСнятия
ПОМЕСТИТЬ ВТ_СнятиеСУчета
ИЗ
	РегистрСведений.СобытияОС.СрезПоследних(&Период, ИнвНомер = &СобИнвНомер И Событие = &СобытиеСнятие) КАК СобытияОССрезПоследних
;

////////////////////////////////////////////////////////////////////////////////
ВЫБРАТЬ
	Принятие.ДатаПринятия КАК ПринятоКУчету,
	ЕСТЬNULL(Снятие.ДатаСнятия, ДАТАВРЕМЯ(1, 1, 1)) КАК СнятоСУчета
ИЗ
	ВТ_ПринятиеКУчету КАК Принятие
		ЛЕВОЕ СОЕДИНЕНИЕ ВТ_СнятиеСУчета КАК Снятие
		ПО Принятие.ИнвНомер = Снятие.ИнвНомер

Пояснения к работе с виртуальными таблицами:

  1. Виртуальные таблицы СрезПервых и СрезПоследних доступны только для периодических регистров сведений. Если регистр сведений непериодический, при обращении к ним платформа 1С выдаст ошибку.
  2. Отборы по полю ИнвНомер и Событие рекомендуется передавать непосредственно в параметры виртуальных таблиц в скобках. Это существенно повышает скорость выполнения запроса.

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

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

← На главную