В процессе разработки отчетов в 1С часто возникают задачи, требующие нетривиальной работы с датами и периодами. Классический пример — анализ текучести кадров, где условия отбора зависят от нескольких дат одновременно. Рассмотрим ситуацию: необходимо создать отчет в Системе Компоновки Данных (СКД), который для каждого месяца будет показывать количество принятых сотрудников, а также количество уволенных, причем уволенными считаются те, кто был принят в этом месяце, а уволен в этом же или в следующем месяце.
Например, для колонки "Июль" отчет должен показать:
Эта логика должна динамически применяться для каждого месяца, который попадает в период отчета. Разберем два эффективных способа решения этой задачи средствами запроса для СКД.
Этот метод является наиболее лаконичным для данной конкретной задачи и был выбран автором вопроса на форуме. Идея заключается в том, чтобы в одном запросе получить данные и о приеме, и об увольнении, соединив таблицу сотрудников саму с собой.
Проанализируем логику по шагам:
Сотрудники как основной источник. Из него мы получим всех сотрудников и их даты приема. Назовем этот экземпляр таблицы Сотрудники.Сотрудники еще раз, но уже как второй источник данных, который мы присоединим к первому. Назовем этот экземпляр Сотрудники1. Он будет служить исключительно для проверки условия увольнения.ЛЕВОЕ СОЕДИНЕНИЕ. Это позволит нам включить в результат всех сотрудников из основной таблицы (то есть всех принятых), даже если для них не найдется соответствующей записи об увольнении в заданном интервале.Сотрудники.сотрудник = Сотрудники1.сотрудник — это гарантирует, что мы сравниваем даты приема и увольнения одного и того же человека.Сотрудники1 должна попадать в нужный нам двухмесячный интервал. Этот интервал начинается с первого дня месяца приема и заканчивается последним днем следующего за ним месяца.Посмотрим на итоговый запрос:
В этом запросе мы используем функции языка запросов 1С для работы с датами:
НАЧАЛОПЕРИОДА(Дата, "МЕСЯЦ") — возвращает первый день месяца, к которому относится указанная дата.ДОБАВИТЬКДАТЕ(Дата, "МЕСЯЦ", 1) — добавляет один месяц к указанной дате.КОНЕЦПЕРИОДА(Дата, "МЕСЯЦ") — возвращает последний момент времени (23:59:59) последнего дня месяца.Вот как выглядит полный текст запроса:
ВЫБРАТЬ
НАЧАЛОПЕРИОДА(Сотрудники.датаприем, МЕСЯЦ) КАК МесяцПриема,
КОЛИЧЕСТВО(Сотрудники.сотрудник) КАК Принято,
КОЛИЧЕСТВО(Сотрудники1.сотрудник) КАК Уволено
ИЗ
Сотрудники КАК Сотрудники
ЛЕВОЕ СОЕДИНЕНИЕ Сотрудники КАК Сотрудники1
ПО Сотрудники.сотрудник = Сотрудники1.сотрудник
И (Сотрудники1.датаувольнения МЕЖДУ НАЧАЛОПЕРИОДА(Сотрудники.датаприем, МЕСЯЦ) И КОНЕЦПЕРИОДА(ДОБАВИТЬКДАТЕ(Сотрудники.датаприем, МЕСЯЦ, 1), МЕСЯЦ))
СГРУППИРОВАТЬ ПО
НАЧАЛОПЕРИОДА(Сотрудники.датаприем, МЕСЯЦ)
Как это работает?
Функция КОЛИЧЕСТВО(Сотрудники.сотрудник) посчитает всех сотрудников, сгруппированных по месяцу приема, так как для каждой записи из левой таблицы (Сотрудники) сотрудник всегда будет существовать. Это наш показатель "Принято".
Функция КОЛИЧЕСТВО(Сотрудники1.сотрудник) посчитает только те записи, для которых левое соединение нашло соответствие. Соответствие находится только тогда, когда сотрудник из Сотрудники1 не равен NULL, то есть когда его дата увольнения попала в заданный интервал МЕЖДУ .... Это и будет наш показатель "Уволено".
Этот подход элегантен и решает задачу в рамках одного запроса без лишних усложнений.
Этот метод является более универсальным и мощным, особенно когда отчет требует анализа по многим пересекающимся или сложным периодам. Суть подхода в том, чтобы создать "основу" отчета — виртуальную таблицу, содержащую все периоды (в нашем случае — месяцы), которые должны быть в отчете. А уже к этой таблице-календарю присоединять необходимые данные.
Разберем по шагам:
ВТ_Периоды, с одной колонкой Месяц. В этой колонке будут храниться даты, соответствующие первым дням каждого месяца в рамках периода отчета (например, 01.07.2023, 01.08.2023, 01.09.2023...). Такой календарь гарантирует, что в отчете будут присутствовать все месяцы, даже если в них не было движения сотрудников. На форуме был предложен сложный, но универсальный запрос для генерации такой таблицы на лету на основе таблицы цифр.ВТ_Периоды.Сотрудники с помощью ЛЕВОГО СОЕДИНЕНИЯ. Условие связи: месяц из календаря должен совпадать с месяцем приема сотрудника (ВТ_Периоды.Месяц = НАЧАЛОПЕРИОДА(Сотрудники.ДатаПриема, МЕСЯЦ)).Посмотрим на пример запроса для этого подхода:
Предположим, временная таблица ВТ_Периоды уже создана. Тогда основной запрос для СКД будет выглядеть так:
ВЫБРАТЬ
ВТ_Периоды.Месяц КАК ПериодОтчета,
Сотрудники.Ссылка КАК Сотрудник,
ВЫБОР
КОГДА Сотрудники.ДатаУвольнения МЕЖДУ ВТ_Периоды.Месяц И КОНЕЦПЕРИОДА(ДОБАВИТЬКДАТЕ(ВТ_Периоды.Месяц, МЕСЯЦ, 1), МЕСЯЦ)
ТОГДА 1
ИНАЧЕ 0
КОНЕЦ КАК БылУволенВНужныйПериод
ИЗ
ВТ_Периоды КАК ВТ_Периоды
ЛЕВОЕ СОЕДИНЕНИЕ Справочник.Сотрудники КАК Сотрудники
ПО НАЧАЛОПЕРИОДА(Сотрудники.ДатаПриема, МЕСЯЦ) = ВТ_Периоды.Месяц
Настройка ресурсов в СКД:
Этот запрос не группирует данные, он возвращает детальные записи. Всю магию агрегации мы переносим в настройки СКД на вкладку "Ресурсы":
Принято: Количество(Различные Сотрудник).Уволено: Сумма(БылУволенВНужныйПериод).Преимущества этого метода:
ВЫБОР ... КОГДА ...), не перекраивая всю структуру соединений в запросе.В итоге, оба подхода приводят к желаемому результату. Первый метод (self-join) — это быстрое и элегантное решение для конкретно этой задачи. Второй метод (с календарем) — более фундаментальный и масштабируемый, который стоит взять на вооружение для построения сложных аналитических отчетов в СКД.