Почему в запросе 1С оператор ОБЪЕДИНИТЬ не сворачивает строки и как правильно объединить данные

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

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

Разберем подробно, почему возникает такая ситуация, чем оператор ОБЪЕДИНИТЬ отличается от горизонтального соединения таблиц и как правильно написать запрос для получения корректного результата.

Выясним причину: разница между ОБЪЕДИНИТЬ и СОЕДИНЕНИЕ

Главная причина, по которой данные выходят в разных строках, заключается в путанице между принципами работы конструкций языка запросов 1С:

  1. ОБЪЕДИНИТЬ (вертикальное объединение): добавляет результат второго запроса снизу к первому запросу. При этом общее количество колонок остается прежним, а количество строк увеличивается. Если в первой части выборки поля второго регистра заполняются пустыми значениями (например, ДАТАВРЕМЯ(1, 1, 1)), а во второй части — наоборот, то СУБД вернет именно две разные строки.
  2. СОЕДИНЕНИЕ (горизонтальное соединение): пристыковывает колонки из второй таблицы сбоку к первой по ключевым полям (например, по пользователю и дате). При этом количество полей увеличивается, а данные по одному пользователю сворачиваются в одну строку.

Рассмотрим два основных способа решения этой задачи: использование связи таблиц через ЛЕВОЕ СОЕДИНЕНИЕ (или ПОЛНОЕ СОЕДИНЕНИЕ) и схему с группировкой через временную таблицу.

Способ 1. Использование левого или полного соединения

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

Обратите внимание: если в поле Период хранится точная дата и время (до секунд), прямой связи по полю Период может не произойти, если время в записях разное. Чтобы запись за день сопоставлялась корректно, приведем даты к началу дня с помощью функции НАЧАЛОПЕРИОДА.

Посмотрим на пример запроса с левым соединением:


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

Важные особенности этого подхода:

  1. Мы связали таблицы по полям Пользователь и совпадению дат с точностью до дня с помощью НАЧАЛОПЕРИОДА(..., ДЕНЬ).
  2. Используем функцию ЕСТЬNULL, чтобы при отсутствии данных во второй таблице подставлялась пустая дата, а не значение NULL.
  3. Если сотрудник мог отсутствовать, но при этом не имел записи о приходе, предпочтительнее использовать ПОЛНОЕ СОЕДИНЕНИЕ, а поля Пользователь и Период выбирать через ЕСТЬNULL(Приход.Пользователь, Учет.Пользователь).

Способ 2. Объединение во временную таблицу с последующей группировкой

Если данные в регистрах имеют сложную структуру или за день может быть несколько записей, надежным вариантом будет использование конструкции ОБЪЕДИНИТЬ ВСЕ с последующей группировкой по дате и пользователю.

Разберем по шагам, почему при обычной группировке дат Конструктор запросов устанавливает агрегатную функцию КОЛИЧЕСТВО. Конструктор по умолчанию применяет её для несопоставленных полей. Чтобы сворачивание дат работало корректно, к полям типа Дата необходимо вручную применить агрегатные функции МАКСИМУМ или МИНИМУМ.

Рассмотрим пример построения пакета запросов:


ВЫБРАТЬ
	НАЧАЛОПЕРИОДА(АС_ВремяПриходаНаРаботу.Период, ДЕНЬ) КАК Период,
	АС_ВремяПриходаНаРаботу.Пользователь КАК Пользователь,
	АС_ВремяПриходаНаРаботу.Время КАК Приход,
	ДАТАВРЕМЯ(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))
ИЗ
	РегистрСведений.АС_УчетРабочегоВремени КАК АС_УчетРабочегоВремени
ГДЕ
	АС_УчетРабочегоВремени.Период МЕЖДУ &НачалоПериода И &КонецПериода
;

////////////////////////////////////////////////////////////////////////////////
ВЫБРАТЬ
	ВТ.Период КАК Период,
	ВТ.Пользователь КАК Пользователь,
	МАКСИМУМ(ВТ.Приход) КАК Приход,
	МАКСИМУМ(ВТ.НачалоЖурнал) КАК НачалоЖурнал,
	МАКСИМУМ(ВТ.КонецЖурнал) КАК КонецЖурнал
ИЗ
	ВТ_ИсходныеДанные КАК ВТ
ГРУППИРОВАТЬ ПО
	ВТ.Период,
	ВТ.Пользователь
УПОРЯДОЧИТЬ ПО
	Пользователь,
	Период

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

  1. Всегда указывайте ключевое слово ВСЕ при объединении (ОБЪЕДИНИТЬ ВСЕ). Обычное ОБЪЕДИНИТЬ заставляет СУБД выполнять дополнительную операцию поиска и удаления дублей (DISTINCT) по всем полям, что увеличивает нагрузку на сервер.
  2. Для правильного сворачивания записей за день всегда приводите даты в секциях группировки и связей к единому формату с помощью НАЧАЛОПЕРИОДА(..., ДЕНЬ).
  3. Для выбора корректного значения даты из нескольких строк при группировке используйте агрегатные функции МАКСИМУМ или МИНИМУМ вместо базовой функции подсчета количества.
← На главную