Как в 1С сформировать массив сотрудников, родившихся в текущем месяце, из неотсортированного списка?

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

Приветствуем вас! В повседневной практике разработчика 1С часто возникает задача отбора и фильтрации данных по временным интервалам. Сегодня мы детально разберем довольно распространенную прикладную задачу: как получить список сотрудников, празднующих свой день рождения в текущем месяце, если на входе у нас есть неупорядоченный массив данных. Мы не просто приведем готовый код, но и проанализируем различные сценарии, взвесим все «за» и «против» каждого подхода, а также рассмотрим важные технические нюансы и подводные камни, которые могут привести к ошибкам в реальной работе конфигурации.

Анализ исходной структуры данных: с чем мы работаем?

Прежде чем переходить к написанию кода, давайте проанализируем, в каком виде данные могут поступать на вход нашей функции. В обсуждениях на форумах часто упоминают «массив сотрудников с датой рождения», однако в платформе «1С:Предприятие 8» тип данных Массив не имеет встроенной структуры табличного типа. На практике под этим могут подразумеваться три основных варианта структуры:

  1. Массив структур: Каждый элемент массива представляет собой объект Структура с ключами Сотрудник (ссылка на справочник) и ДатаРождения (тип Дата). Это частый случай при передаче данных между клиентом и сервером.
  2. Таблица значений: Полноценная таблица с колонками «Сотрудник» и «ДатаРождения». Этот объект удобнее для поиска, но доступен только на стороне сервера.
  3. Массив ссылок: На входе есть только простой массив ссылок на элементы справочника Сотрудники, а сами даты рождения необходимо получить из базы данных.

Ниже мы подробно рассмотрим алгоритмы решения для каждого из этих случаев, чтобы вы могли выбрать наиболее оптимальный для вашей конфигурации.

Вариант А: Простой перебор в цикле (для небольших объемов данных)

Если на вход подается классический массив структур, а количество элементов в нем относительно невелико (до нескольких тысяч сотрудников), самым быстрым и экономичным решением будет обычный обход в цикле. Запуск тяжелого механизма запросов и СУБД в данном случае создаст избыточную нагрузку на сервер.

Давайте посмотрим на пример реализации такого алгоритма:


// Функция выполняет фильтрацию массива структур на клиенте или сервере
Функция ПолучитьИменинниковТекущегоМесяца(ВходнойМассив) Экспорт

    МассивИменинников = Новый Массив;
    ТекущийМесяц = Месяц(ТекущаяДата());

    Для Каждого Элемент Из ВходнойМассив Цикл
        
        // Проверяем заполненность даты рождения во избежание ошибок
        Если Не ЗначениеЗаполнено(Элемент.ДатаРождения) Тогда
            Продолжить;
        КонецЕсли;
        
        // Сравниваем месяц рождения с текущим месяцем
        Если Месяц(Элемент.ДатаРождения) = ТекущийМесяц Тогда
            МассивИменинников.Добавить(Элемент.Сотрудник);
        КонецЕсли;
        
    КонецЦикла;

    Возврат МассивИменинников;

КонецФункции

Преимущества данного метода: этот код абсолютно универсален. Он может выполняться как на стороне сервера, так и на тонком или толстом клиенте. Нам вообще не требуется обращаться к базе данных, если вся информация уже передана во входном параметре.

Вариант Б: Использование временной таблицы в запросе

Что делать, если входной массив очень большой (десятки тысяч сотрудников), или нам требуется связать эти данные с другими таблицами базы данных? В этом случае правильнее использовать СУБД через механизм запросов.

Однако здесь кроется важная техническая особенность: запрос в 1С не умеет напрямую работать с массивом структур в качестве параметра. Сначала нам необходимо программно сконструировать объект типа ТаблицаЗначений, жестко типизировать её колонки, заполнить данными из исходного массива и только затем передать в запрос для создания временной таблицы.

Разберем этот процесс по шагам:


// Процедура подготавливает ТЗ и передает её в запрос для фильтрации
Функция ПолучитьИменинниковЧерезЗапрос(ВходнойМассив) Экспорт

    // 1. Создаем таблицу значений с жесткой типизацией колонок
    ТаблицаСотрудников = Новый ТаблицаЗначений;
    
    // Описываем типы для колонок таблицы
    МассивТиповСотрудник = Новый Массив;
    МассивТиповСотрудник.Добавить(Тип("СправочникСсылка.Сотрудники"));
    ОписаниеТиповСотрудник = Новый ОписаниеТипов(МассивТиповСотрудник);
    
    МассивТиповДата = Новый Массив;
    МассивТиповДата.Добавить(Тип("Дата"));
    ОписаниеТиповДата = Новый ОписаниеТипов(МассивТиповДата, , , Новый КвалификаторыДаты(ЧастиДаты.Дата));
    
    ТаблицаСотрудников.Колонки.Добавить("Сотрудник", ОписаниеТиповСотрудник);
    ТаблицаСотрудников.Колонки.Добавить("ДатаРождения", ОписаниеТиповДата);

    // 2. Заполняем ТЗ данными из входящего массива структур
    Для Каждого СтрокаМассива Из ВходнойМассив Цикл
        НоваяСтрока = ТаблицаСотрудников.Добавить();
        ЗаполнитьЗначенияСвойств(НоваяСтрока, СтрокаМассива);
    КонецЦикла;

    // 3. Выполняем запрос с использованием временной таблицы
    Запрос = Новый Запрос;
    Запрос.МенеджерВременныхТаблиц = Новый МенеджерВременныхТаблиц;
    
    Запрос.Текст = 
        "ВЫБРАТЬ
        |	ВременныйИсточник.Сотрудник КАК Сотрудник,
        |	ВременныйИсточник.ДатаРождения КАК ДатаРождения
        |ПОМЕСТИТЬ ВТ_ИсходныеДанные
        |ИЗ
        |	&ТаблицаСотрудников КАК ВременныйИсточник
        |;
        |
        |////////////////////////////////////////////////////////////////////////////////
        |ВЫБРАТЬ
        |	ВТ_ИсходныеДанные.Сотрудник КАК Сотрудник
        |ИЗ
        |	ВТ_ИсходныеДанные КАК ВТ_ИсходныеДанные
        |ГДЕ
        |	МЕСЯЦ(ВТ_ИсходныеДанные.ДатаРождения) = &НомерТекущегоМесяца
        |	И ВТ_ИсходныеДанные.ДатаРождения <> ДАТАВРЕМЯ(1, 1, 1)";

    Запрос.УстановитьПараметр("ТаблицаСотрудников", ТаблицаСотрудников);
    Запрос.УстановитьПараметр("НомерТекущегоМесяца", Месяц(ТекущаяДата()));

    РезультатЗапроса = Запрос.Выполнить();
    Если Не РезультатЗапроса.Пустой() Тогда
        ТаблицаРезультата = РезультатЗапроса.Выгрузить();
        Возврат ТаблицаРезультата.ВыгрузитьКолонку("Сотрудник");
    Иначе
        Возврат Новый Массив;
    КонецЕсли;

КонецФункции

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

Вариант В: Оптимальный запрос напрямую к базе данных (Рекомендуемый подход)

Как совершенно справедливо заметили участники обсуждения на форуме, передавать массив вместе с датами рождения на вход функции зачастую избыточно и неэффективно. Даты рождения сотрудников уже хранятся в вашей информационной базе. Зачем тратить оперативную память сервера на извлечение, упаковку и передачу дублирующих данных?

Наиболее красивым, лаконичным и производительным решением будет передача в функцию только массива ссылок сотрудников. Всю остальную работу по извлечению дат рождения и их анализу мы переложим на плечи СУБД с помощью оптимизированного запроса.

Давайте посмотрим, как выглядит этот код:


// Функция выбирает именинников по массиву ссылок, обращаясь напрямую к СУБД
Функция ПолучитьИменинниковИзБазы(МассивСсылокСотрудников) Экспорт

    Если МассивСсылокСотрудников.Количество() = 0 Тогда
        Возврат Новый Массив;
    КонецЕсли;

    Запрос = Новый Запрос;
    Запрос.Текст = 
        "ВЫБРАТЬ
        |	Сотрудники.Ссылка КАК Сотрудник
        |ИЗ
        |	Справочник.Сотрудники КАК Сотрудники
        |ГДЕ
        |	Сотрудники.Ссылка В(&МассивСсылокСотрудников)
        |	И МЕСЯЦ(Сотрудники.ФизическоеЛицо.ДатаРождения) = &НомерТекущегоМесяца
        |	И Сотрудники.ФизическоеЛицо.ДатаРождения <> ДАТАВРЕМЯ(1, 1, 1)";

    Запрос.УстановитьПараметр("МассивСсылокСотрудников", МассивСсылокСотрудников);
    Запрос.УстановитьПараметр("НомерТекущегоМесяца", Месяц(ТекущаяДата()));

    Результат = Запрос.Выполнить();
    Если Не Результат.Пустой() Тогда
        Возврат Результат.Выгрузить().ВыгрузитьКолонку("Сотрудник");
    Иначе
        Возврат Новый Массив;
    КонецЕсли;

КонецФункции

Важное примечание: в современных типовых конфигурациях фирмы «1С» (таких как ЗУП 3.1, ERP 2.5, КА 2.5) персональные данные, включая дату рождения, хранятся не в самом справочнике Сотрудники, а в связанном справочнике ФизическиеЛица. Поэтому в тексте запроса мы обращаемся к реквизиту через точку: Сотрудники.ФизическоеЛицо.ДатаРождения. Это гарантирует корректность работы алгоритма в реальных прикладных решениях.

Критически важные подводные камни, о которых должен знать каждый разработчик

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

  1. Проблема незаполненных дат рождения (Эффект января):

    В системе «1С:Предприятие 8» пустая (незаполненная) дата по умолчанию имеет значение '00010101' (1 января 1 года). Если у сотрудника в карточке не заполнена дата рождения, то встроенная функция платформы Месяц() вернет от этой пустой даты значение 1 (Январь).

    Следствие: Если ваш код выполняется в январе, все сотрудники с незаполненной датой рождения ошибочно попадут в список именинников! Чтобы этого избежать, всегда добавляйте явную проверку: ДатаРождения <> ДАТАВРЕМЯ(1, 1, 1) как в условиях запроса, так и при программном переборе в цикле.

  2. Производительность на больших объемах данных:

    Использование метода ВыгрузитьКолонку("Сотрудник") очень удобно, так как оно сразу возвращает готовый одномерный массив. Однако стоит учитывать, что при этом платформа сначала полностью выгружает результат запроса в оперативную память сервера в виде таблицы значений, а затем копирует данные в массив, выполняя двойную работу.

    Если вы обрабатываете списки из десятков тысяч записей, гораздо экономичнее обходить выборку в цикле без выгрузки всей таблицы в память:

    
    Выборка = РезультатЗапроса.Выбрать();
    МассивРезультат = Новый Массив;
    Пока Выборка.Следующий() Цикл
        МассивРезультат.Добавить(Выборка.Сотрудник);
    КонецЦикла;
    
  3. Сортировка по дням рождения:

    В исходном вопросе на форуме указано, что массив передается "без сортировки". Если в результате работы вашего алгоритма вам необходимо вывести именинников в хронологическом порядке (например, от 1 до 31 числа месяца), то при использовании запроса это решается одной строчкой:

    УПОРЯДОЧИТЬ ПО ДЕНЬ(Сотрудники.ФизическоеЛицо.ДатаРождения)

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

Используйте предложенные варианты в зависимости от ваших условий: простой перебор идеален для небольших локальных массивов на клиенте, а прямые запросы к базе данных — лучший выбор с точки зрения производительности СУБД и чистоты вашего программного кода.

← На главную