В практике разработки часто возникает задача собрать данные о событиях (например, об отсутствиях сотрудников) из разных видов документов за определенный период. В этой статье мы подробно разберем, как правильно передавать списки значений в запрос, почему результаты запроса могут быть пустыми при использовании пакетов, как объединять таблицы и, самое главное, как безошибочно определять пересечение периодов.
Первая задача, с которой мы сталкиваемся — отбор данных по списку сотрудников. Если у нас есть массив ссылок или других значений, мы можем передать его в параметр запроса напрямую.
Рассмотрим пример кода, где мы собираем сотрудников из табличной части в массив и передаем его в запрос:
// Сбор данных в массив
МассивСотрудников = Новый Массив;
Для Каждого СтрокаТЧ Из Объект.ВыполненныеРаботы Цикл
МассивСотрудников.Добавить(СтрокаТЧ.Сотрудник);
КонецЦикла;
// Передача в запрос
Запрос.УстановитьПараметр("Сотрудник", МассивСотрудников);
В тексте запроса условие будет выглядеть так:
ГДЕ
Документ.Сотрудник В (&Сотрудник)
Важный нюанс: Хотя передача массива работает удобно, у этого метода есть технические ограничения. При выполнении запроса 1С преобразует массив в список параметров SQL. Если массив будет очень большим (тысячи элементов), запрос может завершиться ошибкой СУБД. В таких случаях более надежным способом является помещение списка во временную таблицу (МенеджерВременныхТаблиц) и использование соединения (ВНУТРЕННЕЕ СОЕДИНЕНИЕ).
Частая ошибка начинающих разработчиков связана с непониманием разницы между последовательным выполнением запросов (пакетом) и объединением результатов.
Допустим, у нас есть текст запроса, состоящий из трех частей, разделенных символом ;:
Запрос.Текст =
"ВЫБРАТЬ ... ИЗ Документ.А ... ; " +
"ВЫБРАТЬ ... ИЗ Документ.Б ... ; " +
"ВЫБРАТЬ ... ИЗ Документ.В ... ";
Если мы выполним конструкцию Запрос.Выполнить().Выгрузить(), то в результат попадет только последняя таблица (из Документа В). Предыдущие запросы будут выполнены, но их результат будет потерян для метода Выгрузить().
У нас есть два пути решения:
Метод Запрос.ВыполнитьПакет() возвращает массив результатов. Мы можем получить данные каждого запроса по индексу:
МассивРезультатов = Запрос.ВыполнитьПакет();
ТЗ_Прогулы = МассивРезультатов[0].Выгрузить();
ТЗ_Больничные = МассивРезультатов[1].Выгрузить();
ТЗ_Отпуска = МассивРезультатов[2].Выгрузить();
Если цель — получить единую таблицу со всеми отсутствиями, лучше использовать оператор ОБЪЕДИНИТЬ ВСЕ. Это превращает три запроса в один.
ВЫБРАТЬ
Прогулы.Сотрудник,
Прогулы.Начало,
Прогулы.Окончание
ИЗ
ЖурналДокументов.ПрогулыНеявки КАК Прогулы
ГДЕ ...
ОБЪЕДИНИТЬ ВСЕ
ВЫБРАТЬ
Больничный.Сотрудник,
Больничный.ДатаНачала,
Больничный.ДатаОкончания
ИЗ
Документ.БольничныйЛист КАК Больничный
ГДЕ ...
При использовании ОБЪЕДИНИТЬ ВСЕ важно соблюдать следующие правила:
Одной из самых критичных ошибок является неправильное условие проверки вхождения дат. Часто пишут так:
// ОШИБОЧНЫЙ ВАРИАНТ
ГДЕ
(Документ.ДатаНачала МЕЖДУ &НачалоПериода И &КонецПериода)
ИЛИ
(Документ.ДатаОкончания МЕЖДУ &НачалоПериода И &КонецПериода)
Давайте проанализируем, почему это плохо. Представьте, что мы ищем отсутствия за Февраль. А сотрудник ушел на больничный с Января по Март.
В результате документ не попадет в выборку, хотя сотрудник отсутствовал весь месяц. Добавление условий вида "Период внутри Документа" делает запрос громоздким и медленным.
Существует универсальная и наиболее производительная формула пересечения двух периодов (А и Б):
НачалоА ≤ ОкончаниеБ И ОкончаниеА ≥ НачалоБ
Применим это к нашему запросу:
ГДЕ
Документ.Сотрудник В (&Сотрудник)
И Документ.ДатаНачала <= &ДатаОкончания
И Документ.ДатаОкончания >= &ДатаНачала
Это условие покрывает абсолютно все варианты: частичное вхождение слева, справа, полное вхождение внутри периода и случай, когда документ перекрывает весь период поиска.
Хотя сбор данных непосредственно из документов (Больничный, Отпуск) возможен, с точки зрения архитектуры 1С это не всегда правильно, особенно в сложных конфигурациях (ЗУП, ERP).
Почему лучше использовать регистры:
Рекомендуем использовать регистры сведений, предназначенные для хранения фактического состояния. Например, РегистрСведений.СостоянияСотрудников (в ЗУП/ERP). Этот регистр уже содержит "очищенные" данные с учетом приоритетов и исправлений.
Пример запроса к регистру:
ВЫБРАТЬ
Состояния.Сотрудник,
Состояния.Состояние,
Состояния.Начало,
Состояния.Окончание
ИЗ
РегистрСведений.СостоянияСотрудников КАК Состояния
ГДЕ
Состояния.Сотрудник В(&Сотрудник)
И Состояния.Начало <= &ДатаОкончания
И Состояния.Окончание >= &ДатаНачала
Используя этот подход, мы получаем точные данные и значительно упрощаем код, избавляясь от громоздких объединений.