Приветствуем вас! В повседневной практике разработчика 1С часто возникает задача отбора и фильтрации данных по временным интервалам. Сегодня мы детально разберем довольно распространенную прикладную задачу: как получить список сотрудников, празднующих свой день рождения в текущем месяце, если на входе у нас есть неупорядоченный массив данных. Мы не просто приведем готовый код, но и проанализируем различные сценарии, взвесим все «за» и «против» каждого подхода, а также рассмотрим важные технические нюансы и подводные камни, которые могут привести к ошибкам в реальной работе конфигурации.
Прежде чем переходить к написанию кода, давайте проанализируем, в каком виде данные могут поступать на вход нашей функции. В обсуждениях на форумах часто упоминают «массив сотрудников с датой рождения», однако в платформе «1С:Предприятие 8» тип данных Массив не имеет встроенной структуры табличного типа. На практике под этим могут подразумеваться три основных варианта структуры:
Структура с ключами Сотрудник (ссылка на справочник) и ДатаРождения (тип Дата). Это частый случай при передаче данных между клиентом и сервером.Сотрудники, а сами даты рождения необходимо получить из базы данных.Ниже мы подробно рассмотрим алгоритмы решения для каждого из этих случаев, чтобы вы могли выбрать наиболее оптимальный для вашей конфигурации.
Если на вход подается классический массив структур, а количество элементов в нем относительно невелико (до нескольких тысяч сотрудников), самым быстрым и экономичным решением будет обычный обход в цикле. Запуск тяжелого механизма запросов и СУБД в данном случае создаст избыточную нагрузку на сервер.
Давайте посмотрим на пример реализации такого алгоритма:
// Функция выполняет фильтрацию массива структур на клиенте или сервере
Функция ПолучитьИменинниковТекущегоМесяца(ВходнойМассив) Экспорт
МассивИменинников = Новый Массив;
ТекущийМесяц = Месяц(ТекущаяДата());
Для Каждого Элемент Из ВходнойМассив Цикл
// Проверяем заполненность даты рождения во избежание ошибок
Если Не ЗначениеЗаполнено(Элемент.ДатаРождения) Тогда
Продолжить;
КонецЕсли;
// Сравниваем месяц рождения с текущим месяцем
Если Месяц(Элемент.ДатаРождения) = ТекущийМесяц Тогда
МассивИменинников.Добавить(Элемент.Сотрудник);
КонецЕсли;
КонецЦикла;
Возврат МассивИменинников;
КонецФункции
Преимущества данного метода: этот код абсолютно универсален. Он может выполняться как на стороне сервера, так и на тонком или толстом клиенте. Нам вообще не требуется обращаться к базе данных, если вся информация уже передана во входном параметре.
Что делать, если входной массив очень большой (десятки тысяч сотрудников), или нам требуется связать эти данные с другими таблицами базы данных? В этом случае правильнее использовать СУБД через механизм запросов.
Однако здесь кроется важная техническая особенность: запрос в 1С не умеет напрямую работать с массивом структур в качестве параметра. Сначала нам необходимо программно сконструировать объект типа ТаблицаЗначений, жестко типизировать её колонки, заполнить данными из исходного массива и только затем передать в запрос для создания временной таблицы.
Разберем этот процесс по шагам:
// Процедура подготавливает ТЗ и передает её в запрос для фильтрации
Функция ПолучитьИменинниковЧерезЗапрос(ВходнойМассив) Экспорт
// 1. Создаем таблицу значений с жесткой типизацией колонок
ТаблицаСотрудников = Новый ТаблицаЗначений;
// Описываем типы для колонок таблицы
МассивТиповСотрудник = Новый Массив;
МассивТиповСотрудник.Добавить(Тип("СправочникСсылка.Сотрудники"));
ОписаниеТиповСотрудник = Новый ОписаниеТипов(МассивТиповСотрудник);
МассивТиповДата = Новый Массив;
МассивТиповДата.Добавить(Тип("Дата"));
ОписаниеТиповДата = Новый ОписаниеТипов(МассивТиповДата, , , Новый КвалификаторыДаты(ЧастиДаты.Дата));
ТаблицаСотрудников.Колонки.Добавить("Сотрудник", ОписаниеТиповСотрудник);
ТаблицаСотрудников.Колонки.Добавить("ДатаРождения", ОписаниеТиповДата);
// 2. Заполняем ТЗ данными из входящего массива структур
Для Каждого СтрокаМассива Из ВходнойМассив Цикл
НоваяСтрока = ТаблицаСотрудников.Добавить();
ЗаполнитьЗначенияСвойств(НоваяСтрока, СтрокаМассива);
КонецЦикла;
// 3. Выполняем запрос с использованием временной таблицы
Запрос = Новый Запрос;
Запрос.МенеджерВременныхТаблиц = Новый МенеджерВременныхТаблиц;
Запрос.Текст =
"ВЫБРАТЬ
| ВременныйИсточник.Сотрудник КАК Сотрудник,
| ВременныйИсточник.ДатаРождения КАК ДатаРождения
|ПОМЕСТИТЬ ВТ_ИсходныеДанные
|ИЗ
| &ТаблицаСотрудников КАК ВременныйИсточник
|;
|
|////////////////////////////////////////////////////////////////////////////////
|ВЫБРАТЬ
| ВТ_ИсходныеДанные.Сотрудник КАК Сотрудник
|ИЗ
| ВТ_ИсходныеДанные КАК ВТ_ИсходныеДанные
|ГДЕ
| МЕСЯЦ(ВТ_ИсходныеДанные.ДатаРождения) = &НомерТекущегоМесяца
| И ВТ_ИсходныеДанные.ДатаРождения <> ДАТАВРЕМЯ(1, 1, 1)";
Запрос.УстановитьПараметр("ТаблицаСотрудников", ТаблицаСотрудников);
Запрос.УстановитьПараметр("НомерТекущегоМесяца", Месяц(ТекущаяДата()));
РезультатЗапроса = Запрос.Выполнить();
Если Не РезультатЗапроса.Пустой() Тогда
ТаблицаРезультата = РезультатЗапроса.Выгрузить();
Возврат ТаблицаРезультата.ВыгрузитьКолонку("Сотрудник");
Иначе
Возврат Новый Массив;
КонецЕсли;
КонецФункции
Этот способ незаменим, когда в рамках одного сложного запроса вам нужно не только отфильтровать именинников, но и, например, соединить их с актуальными кадровыми срезами, получить их действующие подразделения, оклады или проверить признак увольнения.
Как совершенно справедливо заметили участники обсуждения на форуме, передавать массив вместе с датами рождения на вход функции зачастую избыточно и неэффективно. Даты рождения сотрудников уже хранятся в вашей информационной базе. Зачем тратить оперативную память сервера на извлечение, упаковку и передачу дублирующих данных?
Наиболее красивым, лаконичным и производительным решением будет передача в функцию только массива ссылок сотрудников. Всю остальную работу по извлечению дат рождения и их анализу мы переложим на плечи СУБД с помощью оптимизированного запроса.
Давайте посмотрим, как выглядит этот код:
// Функция выбирает именинников по массиву ссылок, обращаясь напрямую к СУБД
Функция ПолучитьИменинниковИзБазы(МассивСсылокСотрудников) Экспорт
Если МассивСсылокСотрудников.Количество() = 0 Тогда
Возврат Новый Массив;
КонецЕсли;
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| Сотрудники.Ссылка КАК Сотрудник
|ИЗ
| Справочник.Сотрудники КАК Сотрудники
|ГДЕ
| Сотрудники.Ссылка В(&МассивСсылокСотрудников)
| И МЕСЯЦ(Сотрудники.ФизическоеЛицо.ДатаРождения) = &НомерТекущегоМесяца
| И Сотрудники.ФизическоеЛицо.ДатаРождения <> ДАТАВРЕМЯ(1, 1, 1)";
Запрос.УстановитьПараметр("МассивСсылокСотрудников", МассивСсылокСотрудников);
Запрос.УстановитьПараметр("НомерТекущегоМесяца", Месяц(ТекущаяДата()));
Результат = Запрос.Выполнить();
Если Не Результат.Пустой() Тогда
Возврат Результат.Выгрузить().ВыгрузитьКолонку("Сотрудник");
Иначе
Возврат Новый Массив;
КонецЕсли;
КонецФункции
Важное примечание: в современных типовых конфигурациях фирмы «1С» (таких как ЗУП 3.1, ERP 2.5, КА 2.5) персональные данные, включая дату рождения, хранятся не в самом справочнике Сотрудники, а в связанном справочнике ФизическиеЛица. Поэтому в тексте запроса мы обращаемся к реквизиту через точку: Сотрудники.ФизическоеЛицо.ДатаРождения. Это гарантирует корректность работы алгоритма в реальных прикладных решениях.
При реализации подобных задач новички часто допускают архитектурные ошибки, приводящие к некорректному поведению программы в промышленной эксплуатации. Давайте разберем основные проблемы и способы их решения.
В системе «1С:Предприятие 8» пустая (незаполненная) дата по умолчанию имеет значение '00010101' (1 января 1 года). Если у сотрудника в карточке не заполнена дата рождения, то встроенная функция платформы Месяц() вернет от этой пустой даты значение 1 (Январь).
Следствие: Если ваш код выполняется в январе, все сотрудники с незаполненной датой рождения ошибочно попадут в список именинников! Чтобы этого избежать, всегда добавляйте явную проверку: ДатаРождения <> ДАТАВРЕМЯ(1, 1, 1) как в условиях запроса, так и при программном переборе в цикле.
Использование метода ВыгрузитьКолонку("Сотрудник") очень удобно, так как оно сразу возвращает готовый одномерный массив. Однако стоит учитывать, что при этом платформа сначала полностью выгружает результат запроса в оперативную память сервера в виде таблицы значений, а затем копирует данные в массив, выполняя двойную работу.
Если вы обрабатываете списки из десятков тысяч записей, гораздо экономичнее обходить выборку в цикле без выгрузки всей таблицы в память:
Выборка = РезультатЗапроса.Выбрать();
МассивРезультат = Новый Массив;
Пока Выборка.Следующий() Цикл
МассивРезультат.Добавить(Выборка.Сотрудник);
КонецЦикла;
В исходном вопросе на форуме указано, что массив передается "без сортировки". Если в результате работы вашего алгоритма вам необходимо вывести именинников в хронологическом порядке (например, от 1 до 31 числа месяца), то при использовании запроса это решается одной строчкой:
УПОРЯДОЧИТЬ ПО ДЕНЬ(Сотрудники.ФизическоеЛицо.ДатаРождения)
Если же вы используете ручной перебор массива структур, то отсортировать итоговый массив встроенными методами не удастся — вам придется сначала переносить данные во временную таблицу значений или список значений для применения метода Сортировать().
Используйте предложенные варианты в зависимости от ваших условий: простой перебор идеален для небольших локальных массивов на клиенте, а прямые запросы к базе данных — лучший выбор с точки зрения производительности СУБД и чистоты вашего программного кода.