При работе с основными средствами часто возникает задача выгрузить в одной строке сразу две ключевые даты: дату принятия к учету и дату снятия с учета (списания/выбытия). Данные о событиях ОС хранятся в регистре сведений СобытияОС. По одному инвентарному номеру в этом регистре содержится несколько записей, соответствующих разным видам событий.
Если написать простой запрос с условием ГДЕ СобытияОС.Событие = &Событие1 ИЛИ СобытияОС.Событие = &Событие2, то СУБД вернет две отдельные строки. Разберем по шагам, как правильно составить запрос 1С, чтобы объединить данные по двум событиям в одну строку с двумя раздельными колонками.
Проанализируем ситуацию: каждая запись в регистре сведений хранит только одно конкретное событие и одну дату. Простой отбор через секцию ГДЕ фильтрует строки, но не превращает строки одного столбца в отдельные колонки одной записи. Чтобы получить одну строку с колонками ПринятоКУчету и СнятоСУчета, необходимо применить группировку с условным выбором, соединение таблицы самой с собой либо пакетный запрос с временными таблицами.
Рассмотрим наиболее эффективный метод с точки зрения производительности СУБД. В этом варианте таблица считывается за один проход без использования сложных соединений. Мы используем конструкцию ВЫБОР для фильтрации даты по конкретному событию и агрегатную функцию МАКСИМУМ для схлопывания результатов в одну строку при группировке по инвентарному номеру.
Посмотрим на пример текста запроса:
ВЫБРАТЬ
СобытияОС.ИнвНомер КАК ИнвНомер,
МАКСИМУМ(ВЫБОР
КОГДА СобытияОС.Событие = &СобытиеПринятие
ТОГДА СобытияОС.ДатаСобытия
ИНАЧЕ ДАТАВРЕМЯ(1, 1, 1)
КОНЕЦ) КАК ПринятоКУчету,
МАКСИМУМ(ВЫБОР
КОГДА СобытияОС.Событие = &СобытиеСнятие
ТОГДА СобытияОС.ДатаСобытия
ИНАЧЕ ДАТАВРЕМЯ(1, 1, 1)
КОНЕЦ) КАК СнятоСУчета
ИЗ
РегистрСведений.СобытияОС КАК СобытияОС
ГДЕ
СобытияОС.ИнвНомер = &СобИнвНомер
И СобытияОС.Событие В(&МассивСобытий)
ГРУППИРОВАТЬ ПО
СобытияОС.ИнвНомер
Разберем особенности работы этого запроса:
ВЫБОР подставит вместо даты системный литерал пустой даты ДАТАВРЕМЯ(1, 1, 1). Это исключает необходимость передавать отдельный параметр пустой даты из кода 1С.МАКСИМУМ совместно с ГРУППИРОВАТЬ ПО сворачивает все найденные события для указанного инвентарного номера в одну итоговую строку.СобытияОС.Событие В(&МассивСобытий) позволяет СУБД отсечь лишние записи еще на этапе чтения физической таблицы.Рассмотрим случай, когда принятие к учету должно присутствовать обязательно, а событие снятия с учета может отсутствовать. В такой ситуации мы можем вытащить регистр СобытияОС дважды и связать две таблицы по инвентарному номеру с помощью ЛЕВОЕ СОЕДИНЕНИЕ.
ВЫБРАТЬ
СобытияПринятие.ДатаСобытия КАК ПринятоКУчету,
ЕСТЬNULL(СобытияСнятие.ДатаСобытия, ДАТАВРЕМЯ(1, 1, 1)) КАК СнятоСУчета
ИЗ
РегистрСведений.СобытияОС КАК СобытияПринятие
ЛЕВОЕ СОЕДИНЕНИЕ РегистрСведений.СобытияОС КАК СобытияСнятие
ПО СобытияПринятие.ИнвНомер = СобытияСнятие.ИнвНомер
И (СобытияСнятие.Событие = &СобытиеСнятие)
ГДЕ
СобытияПринятие.ИнвНомер = &СобИнвНомер
И СобытияПринятие.Событие = &СобытиеПринятие
Важно: Обратите внимание на функцию ЕСТЬNULL. Если по объекту ОС нет записи о снятии с учета, левое соединение вернет значение NULL для колонки даты выбытия. Функция ЕСТЬNULL корректно заменит его на пустую дату ДАТАВРЕМЯ(1, 1, 1).
Выясним, как решить эту задачу в случае с периодическим регистром сведений, когда необходимо использовать виртуальные таблицы СрезПервых и СрезПоследних для защиты от дублирования записей при повторных событиях.
ВЫБРАТЬ
СобытияОССрезПервых.ИнвНомер КАК ИнвНомер,
СобытияОССрезПервых.Период КАК ДатаПринятия
ПОМЕСТИТЬ ВТ_ПринятиеКУчету
ИЗ
РегистрСведений.СобытияОС.СрезПервых(&Период, ИнвНомер = &СобИнвНомер И Событие = &СобытиеПринятие) КАК СобытияОССрезПервых
;
////////////////////////////////////////////////////////////////////////////////
ВЫБРАТЬ
СобытияОССрезПоследних.ИнвНомер КАК ИнвНомер,
СобытияОССрезПоследних.Период КАК ДатаСнятия
ПОМЕСТИТЬ ВТ_СнятиеСУчета
ИЗ
РегистрСведений.СобытияОС.СрезПоследних(&Период, ИнвНомер = &СобИнвНомер И Событие = &СобытиеСнятие) КАК СобытияОССрезПоследних
;
////////////////////////////////////////////////////////////////////////////////
ВЫБРАТЬ
Принятие.ДатаПринятия КАК ПринятоКУчету,
ЕСТЬNULL(Снятие.ДатаСнятия, ДАТАВРЕМЯ(1, 1, 1)) КАК СнятоСУчета
ИЗ
ВТ_ПринятиеКУчету КАК Принятие
ЛЕВОЕ СОЕДИНЕНИЕ ВТ_СнятиеСУчета КАК Снятие
ПО Принятие.ИнвНомер = Снятие.ИнвНомер
Пояснения к работе с виртуальными таблицами:
СрезПервых и СрезПоследних доступны только для периодических регистров сведений. Если регистр сведений непериодический, при обращении к ним платформа 1С выдаст ошибку.ИнвНомер и Событие рекомендуется передавать непосредственно в параметры виртуальных таблиц в скобках. Это существенно повышает скорость выполнения запроса.Проанализируем ключевые моменты, которые стоит учесть при написании подобных запросов в реальных конфигурациях:
ИнвНомер будет выполняться мгновенно только в том случае, если данный реквизит является измерением регистра сведений или для него включено свойство «Индексировать». В противном случае СУБД применит сканирование всей таблицы.Запрос.УстановитьПараметр("ПустаяДата", '00010101'), всегда используйте конструкцию языка запросов ДАТАВРЕМЯ(1, 1, 1).