Как объединить данные из разных документов в запросе и корректно проверить вхождение в период?

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

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

1. Передача массива в параметры запроса

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

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


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

// Передача в запрос
Запрос.УстановитьПараметр("Сотрудник", МассивСотрудников);

В тексте запроса условие будет выглядеть так:


ГДЕ 
    Документ.Сотрудник В (&Сотрудник)

Важный нюанс: Хотя передача массива работает удобно, у этого метода есть технические ограничения. При выполнении запроса 1С преобразует массив в список параметров SQL. Если массив будет очень большим (тысячи элементов), запрос может завершиться ошибкой СУБД. В таких случаях более надежным способом является помещение списка во временную таблицу (МенеджерВременныхТаблиц) и использование соединения (ВНУТРЕННЕЕ СОЕДИНЕНИЕ).

2. Пакетные запросы против Объединения

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

Допустим, у нас есть текст запроса, состоящий из трех частей, разделенных символом ;:


Запрос.Текст = 
    "ВЫБРАТЬ ... ИЗ Документ.А ... ; " +
    "ВЫБРАТЬ ... ИЗ Документ.Б ... ; " +
    "ВЫБРАТЬ ... ИЗ Документ.В ... ";

Если мы выполним конструкцию Запрос.Выполнить().Выгрузить(), то в результат попадет только последняя таблица (из Документа В). Предыдущие запросы будут выполнены, но их результат будет потерян для метода Выгрузить().

У нас есть два пути решения:

Способ А: ВыполнитьПакет

Метод Запрос.ВыполнитьПакет() возвращает массив результатов. Мы можем получить данные каждого запроса по индексу:


МассивРезультатов = Запрос.ВыполнитьПакет();
ТЗ_Прогулы = МассивРезультатов[0].Выгрузить();
ТЗ_Больничные = МассивРезультатов[1].Выгрузить();
ТЗ_Отпуска = МассивРезультатов[2].Выгрузить();

Способ Б: ОБЪЕДИНИТЬ ВСЕ (Рекомендуемый)

Если цель — получить единую таблицу со всеми отсутствиями, лучше использовать оператор ОБЪЕДИНИТЬ ВСЕ. Это превращает три запроса в один.


ВЫБРАТЬ
    Прогулы.Сотрудник,
    Прогулы.Начало,
    Прогулы.Окончание
ИЗ
    ЖурналДокументов.ПрогулыНеявки КАК Прогулы
ГДЕ ...

ОБЪЕДИНИТЬ ВСЕ

ВЫБРАТЬ
    Больничный.Сотрудник,
    Больничный.ДатаНачала,
    Больничный.ДатаОкончания
ИЗ
    Документ.БольничныйЛист КАК Больничный
ГДЕ ...

При использовании ОБЪЕДИНИТЬ ВСЕ важно соблюдать следующие правила:

  1. Количество полей во всех запросах должно быть одинаковым.
  2. Порядок полей должен совпадать (например: 1-я колонка всегда Сотрудник, 2-я — Дата начала).
  3. Типы данных в соответствующих колонках должны быть совместимы.

3. Правильная логика пересечения периодов

Одной из самых критичных ошибок является неправильное условие проверки вхождения дат. Часто пишут так:


// ОШИБОЧНЫЙ ВАРИАНТ
ГДЕ
    (Документ.ДатаНачала МЕЖДУ &НачалоПериода И &КонецПериода)
    ИЛИ
    (Документ.ДатаОкончания МЕЖДУ &НачалоПериода И &КонецПериода)

Давайте проанализируем, почему это плохо. Представьте, что мы ищем отсутствия за Февраль. А сотрудник ушел на больничный с Января по Март.

В результате документ не попадет в выборку, хотя сотрудник отсутствовал весь месяц. Добавление условий вида "Период внутри Документа" делает запрос громоздким и медленным.

Существует универсальная и наиболее производительная формула пересечения двух периодов (А и Б):

НачалоА ≤ ОкончаниеБ И ОкончаниеА ≥ НачалоБ

Применим это к нашему запросу:


ГДЕ
    Документ.Сотрудник В (&Сотрудник)
    И Документ.ДатаНачала <= &ДатаОкончания
    И Документ.ДатаОкончания >= &ДатаНачала

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

4. Архитектурный подход: Документы или Регистры?

Хотя сбор данных непосредственно из документов (Больничный, Отпуск) возможен, с точки зрения архитектуры 1С это не всегда правильно, особенно в сложных конфигурациях (ЗУП, ERP).

Почему лучше использовать регистры:

  1. Сторнирование и исправления: Документ "Больничный лист" может быть введен, а затем исправлен или сторнирован другим документом. Запрос к таблице документов вернет и старую (неверную), и новую запись.
  2. Вытеснение: Если сотрудник заболел во время отпуска, больничный вытесняет отпуск. В документах эти периоды могут просто накладываться друг на друга.
  3. Удобство: Вместо объединения 5-10 видов документов проще обратиться к одной таблице.

Рекомендуем использовать регистры сведений, предназначенные для хранения фактического состояния. Например, РегистрСведений.СостоянияСотрудников (в ЗУП/ERP). Этот регистр уже содержит "очищенные" данные с учетом приоритетов и исправлений.

Пример запроса к регистру:


ВЫБРАТЬ
    Состояния.Сотрудник,
    Состояния.Состояние,
    Состояния.Начало,
    Состояния.Окончание
ИЗ
    РегистрСведений.СостоянияСотрудников КАК Состояния
ГДЕ
    Состояния.Сотрудник В(&Сотрудник)
    И Состояния.Начало <= &ДатаОкончания
    И Состояния.Окончание >= &ДатаНачала

Используя этот подход, мы получаем точные данные и значительно упрощаем код, избавляясь от громоздких объединений.

← На главную