В конфигурациях "Зарплата и Управление Персоналом" (ЗУП 3.1) часто возникает задача получения табеля или сводных данных о времени работы сотрудника за месяц. Одной из сложностей является структура регистров накопления и сведений, где данные за один период могут быть разбиты на несколько строк разными документами-регистраторами (например, начисление зарплаты и больничный лист).
Разберем задачу, с которой столкнулся автор: необходимо получить данные из регистра сведений ДанныеОВремениДляРасчетаСреднегоФСС. В этом регистре есть ресурсы на каждый день месяца (День1, День2 ... День31) с типом Булево. Проблема заключается в том, что в выборке появляются дубли строк по одному сотруднику и месяцу, и нам нужно "свернуть" их в одну строку, правильно обработав логику пересечения отработанного и неотработанного времени.
Регистр ДанныеОВремениДляРасчетаСреднегоФСС, как и многие другие "плоские" регистры в ЗУП, хранит данные в разрезе регистраторов. Это означает, что если в одном месяце были введены:
То в регистре появятся две (или более) записи. В одной будут стоять флаги ИСТИНА в колонках ОтработанДеньX, а в другой — ИСТИНА в колонках НеотработанДеньX (или Болезнь...).
Наша задача — объединить эти строки так, чтобы получить полную картину по дням в одной строке результата запроса.
Самый эффективный способ решения такой задачи в языке запросов 1С — это использование группировки и агрегатных функций. В SQL и 1С:Предприятии логическое значение ИСТИНА условно "больше", чем ЛОЖЬ.
Поэтому, чтобы объединить несколько строк, где в одной строке день отмечен как пустой (Ложь), а в другой как заполненный (Истина), мы можем использовать функцию МАКСИМУМ().
Рассмотрим пример запроса, который собирает все отметки в одну строку:
ВЫБРАТЬ
Данные.ФизическоеЛицо КАК ФизическоеЛицо,
Данные.Месяц КАК Месяц,
// Агрегируем данные по каждому дню
МАКСИМУМ(Данные.ОтработанДень1) КАК ОтработанДень1,
МАКСИМУМ(Данные.ОтработанДень2) КАК ОтработанДень2,
// ... и так далее для всех дней отработанного времени ...
МАКСИМУМ(Данные.НеотработанДень1) КАК НеотработанДень1,
МАКСИМУМ(Данные.НеотработанДень2) КАК НеотработанДень2
// ... и так далее для всех дней неотработанного времени ...
ИЗ
РегистрСведений.ДанныеОВремениДляРасчетаСреднегоФСС КАК Данные
ГДЕ
Данные.ФизическоеЛицо = &ФизическоеЛицо
И Данные.Месяц = &Месяц
СГРУППИРОВАТЬ ПО
Данные.ФизическоеЛицо,
Данные.Месяц
Как это работает: Если хотя бы в одной строке регистратора день отмечен как ИСТИНА, функция МАКСИМУМ вернет ИСТИНА для всей группы строк. Это аналог логического "ИЛИ".
Часто бывает ситуация, когда на один день попадает и явка (из табеля или начисления зарплаты), и отсутствие (больничный введен позже). Нам нужно определить итоговое состояние дня. Обычно отсутствие (неотработанное время) имеет приоритет над явкой.
Давайте реализуем логику: "Если день неотработан, то ставим флаг отсутствия, иначе смотрим флаг наличия работы".
Это можно сделать прямо внутри запроса, используя конструкцию ВЫБОР над агрегатными функциями:
ВЫБРАТЬ
Данные.ФизическоеЛицо,
Данные.Месяц,
// Формируем итоговый статус дня
ВЫБОР
КОГДА МАКСИМУМ(Данные.НеотработанДень1) ТОГДА "Не работал"
КОГДА МАКСИМУМ(Данные.ОтработанДень1) ТОГДА "Работал"
ИНАЧЕ "Выходной"
КОНЕЦ КАК СтатусДень1,
// Аналогично для второго дня
ВЫБОР
КОГДА МАКСИМУМ(Данные.НеотработанДень2) ТОГДА "Не работал"
КОГДА МАКСИМУМ(Данные.ОтработанДень2) ТОГДА "Работал"
ИНАЧЕ "Выходной"
КОНЕЦ КАК СтатусДень2
// ... продолжайте для всех 31 дней
ИЗ
РегистрСведений.ДанныеОВремениДляРасчетаСреднегоФСС КАК Данные
ГДЕ
Данные.Месяц = &Месяц
СГРУППИРОВАТЬ ПО
Данные.ФизическоеЛицо,
Данные.Месяц
Этот подход позволяет получить одну чистую строку данных без необходимости делать сложные соединения таблиц.
Автор темы в итоге пришел к решению через помещение данных во временные таблицы и их последующее соединение. Этот метод имеет право на жизнь, если логика выборки слишком сложна для одной группировки (например, нужно отфильтровать виды времени по разным сложным условиям перед объединением).
Разберем алгоритм, который использовал автор:
НеОтработанныеДни.ГДЕ НЕ ...). Помещаем в таблицу ОтработанныеДни.ЛЕВОЕ СОЕДИНЕНИЕ таблицы ОтработанныеДни с таблицей НеОтработанныеДни.ВЫБОР (CASE), чтобы определить, какое значение брать.Пример конструкции финального выбора (для одного дня):
ВЫБОР
// Если в таблице неявок есть запись об этом дне - берем её
КОГДА ВрТЗ_Неявки.НеотработанДень1 ТОГДА ИСТИНА
// Иначе берем данные из таблицы явок
ИНАЧЕ ТаблицаЯвок.ОтработанДень1
КОНЕЦ КАК ИтоговыйДень1
Важное предостережение: Хотя этот метод работает, код получается очень объемным (в примере автора — более 1000 строк кода из-за перечисления всех 31 дней). Это затрудняет поддержку и чтение запроса. Первый метод с МАКСИМУМ() и СГРУППИРОВАТЬ ПО является предпочтительным с точки зрения производительности и чистоты кода.
Стоит также отметить совет одного из участников обсуждения: в типовых конфигурациях ЗУП 3.1 существует множество общих модулей, предназначенных для получения данных учета времени.
Вместо написания прямого запроса к "сырому" регистру, часто лучше воспользоваться программным интерфейсом (API) конфигурации. Это может быть модуль УчетВремени или функции расчета среднего заработка. Такой подход гарантирует, что при обновлении 1С и изменении структуры регистров ваш код не перестанет работать.
Для объединения строк регистра по дням:
СГРУППИРОВАТЬ ПО.МАКСИМУМ().ВЫБОР КОГДА ... ТОГДА ... внутри полей выборки, оперируя результатами агрегатных функций.