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

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

В конфигурациях "Зарплата и Управление Персоналом" (ЗУП 3.1) часто возникает задача получения табеля или сводных данных о времени работы сотрудника за месяц. Одной из сложностей является структура регистров накопления и сведений, где данные за один период могут быть разбиты на несколько строк разными документами-регистраторами (например, начисление зарплаты и больничный лист).

Разберем задачу, с которой столкнулся автор: необходимо получить данные из регистра сведений ДанныеОВремениДляРасчетаСреднегоФСС. В этом регистре есть ресурсы на каждый день месяца (День1, День2 ... День31) с типом Булево. Проблема заключается в том, что в выборке появляются дубли строк по одному сотруднику и месяцу, и нам нужно "свернуть" их в одну строку, правильно обработав логику пересечения отработанного и неотработанного времени.

Суть проблемы: почему появляются дубли строк?

Регистр ДанныеОВремениДляРасчетаСреднегоФСС, как и многие другие "плоские" регистры в ЗУП, хранит данные в разрезе регистраторов. Это означает, что если в одном месяце были введены:

  1. Документ "Начисление зарплаты" (фиксирует отработанное время).
  2. Документ "Больничный лист" (фиксирует неотработанное время).

То в регистре появятся две (или более) записи. В одной будут стоять флаги ИСТИНА в колонках ОтработанДеньX, а в другой — ИСТИНА в колонках НеотработанДеньX (или Болезнь...).

Наша задача — объединить эти строки так, чтобы получить полную картину по дням в одной строке результата запроса.

Решение 1: Правильный подход через агрегатные функции (Свертка)

Самый эффективный способ решения такой задачи в языке запросов 1С — это использование группировки и агрегатных функций. В SQL и 1С:Предприятии логическое значение ИСТИНА условно "больше", чем ЛОЖЬ.

Поэтому, чтобы объединить несколько строк, где в одной строке день отмечен как пустой (Ложь), а в другой как заполненный (Истина), мы можем использовать функцию МАКСИМУМ().

Рассмотрим пример запроса, который собирает все отметки в одну строку:


ВЫБРАТЬ
    Данные.ФизическоеЛицо КАК ФизическоеЛицо,
    Данные.Месяц КАК Месяц,
    // Агрегируем данные по каждому дню
    МАКСИМУМ(Данные.ОтработанДень1) КАК ОтработанДень1,
    МАКСИМУМ(Данные.ОтработанДень2) КАК ОтработанДень2,
    // ... и так далее для всех дней отработанного времени ...
    МАКСИМУМ(Данные.НеотработанДень1) КАК НеотработанДень1,
    МАКСИМУМ(Данные.НеотработанДень2) КАК НеотработанДень2
    // ... и так далее для всех дней неотработанного времени ...
ИЗ
    РегистрСведений.ДанныеОВремениДляРасчетаСреднегоФСС КАК Данные
ГДЕ
    Данные.ФизическоеЛицо = &ФизическоеЛицо
    И Данные.Месяц = &Месяц

СГРУППИРОВАТЬ ПО
    Данные.ФизическоеЛицо,
    Данные.Месяц

Как это работает: Если хотя бы в одной строке регистратора день отмечен как ИСТИНА, функция МАКСИМУМ вернет ИСТИНА для всей группы строк. Это аналог логического "ИЛИ".

Решение 2: Учет приоритета (Вытеснение)

Часто бывает ситуация, когда на один день попадает и явка (из табеля или начисления зарплаты), и отсутствие (больничный введен позже). Нам нужно определить итоговое состояние дня. Обычно отсутствие (неотработанное время) имеет приоритет над явкой.

Давайте реализуем логику: "Если день неотработан, то ставим флаг отсутствия, иначе смотрим флаг наличия работы".

Это можно сделать прямо внутри запроса, используя конструкцию ВЫБОР над агрегатными функциями:


ВЫБРАТЬ
    Данные.ФизическоеЛицо,
    Данные.Месяц,
    // Формируем итоговый статус дня
    ВЫБОР 
        КОГДА МАКСИМУМ(Данные.НеотработанДень1) ТОГДА "Не работал"
        КОГДА МАКСИМУМ(Данные.ОтработанДень1) ТОГДА "Работал"
        ИНАЧЕ "Выходной" 
    КОНЕЦ КАК СтатусДень1,
    
    // Аналогично для второго дня
    ВЫБОР 
        КОГДА МАКСИМУМ(Данные.НеотработанДень2) ТОГДА "Не работал"
        КОГДА МАКСИМУМ(Данные.ОтработанДень2) ТОГДА "Работал"
        ИНАЧЕ "Выходной" 
    КОНЕЦ КАК СтатусДень2
    // ... продолжайте для всех 31 дней
ИЗ
    РегистрСведений.ДанныеОВремениДляРасчетаСреднегоФСС КАК Данные
ГДЕ
    Данные.Месяц = &Месяц
СГРУППИРОВАТЬ ПО
    Данные.ФизическоеЛицо,
    Данные.Месяц

Этот подход позволяет получить одну чистую строку данных без необходимости делать сложные соединения таблиц.

Решение 3: Объединение через временные таблицы (Сложный путь)

Автор темы в итоге пришел к решению через помещение данных во временные таблицы и их последующее соединение. Этот метод имеет право на жизнь, если логика выборки слишком сложна для одной группировки (например, нужно отфильтровать виды времени по разным сложным условиям перед объединением).

Разберем алгоритм, который использовал автор:

  1. Выбираем все записи, где есть "Болезнь" или "Неотработанное время", и помещаем их во временную таблицу НеОтработанныеДни.
  2. Выбираем записи с "Отработанным временем", исключая дни, которые уже помечены как неотработанные (тут важно быть осторожным с условиями ГДЕ НЕ ...). Помещаем в таблицу ОтработанныеДни.
  3. Делаем ЛЕВОЕ СОЕДИНЕНИЕ таблицы ОтработанныеДни с таблицей НеОтработанныеДни.
  4. В итоговой выборке используем условие ВЫБОР (CASE), чтобы определить, какое значение брать.

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


ВЫБОР 
    // Если в таблице неявок есть запись об этом дне - берем её
    КОГДА ВрТЗ_Неявки.НеотработанДень1 ТОГДА ИСТИНА
    // Иначе берем данные из таблицы явок
    ИНАЧЕ ТаблицаЯвок.ОтработанДень1 
КОНЕЦ КАК ИтоговыйДень1

Важное предостережение: Хотя этот метод работает, код получается очень объемным (в примере автора — более 1000 строк кода из-за перечисления всех 31 дней). Это затрудняет поддержку и чтение запроса. Первый метод с МАКСИМУМ() и СГРУППИРОВАТЬ ПО является предпочтительным с точки зрения производительности и чистоты кода.

Использование функций БСП и ЗУП

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

Вместо написания прямого запроса к "сырому" регистру, часто лучше воспользоваться программным интерфейсом (API) конфигурации. Это может быть модуль УчетВремени или функции расчета среднего заработка. Такой подход гарантирует, что при обновлении 1С и изменении структуры регистров ваш код не перестанет работать.

Резюме

Для объединения строк регистра по дням:

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