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