В практике разработки на платформе 1С часто возникают задачи, когда необходимо сопоставить данные из двух независимых источников, основываясь на временных метках. Типичный пример — определение ответственного лица, работающего в компании на момент выхода определенной версии ПО, когда у нас есть только дата приема сотрудника и дата релиза. Разберем, как правильно реализовать такую логику одним запросом, и проанализируем технические нюансы работы с датами.
Рассмотрим ситуацию: у нас есть два непериодических регистра сведений. В первом (Платформы) хранятся названия программных продуктов и даты их выпуска. Во втором (ОтветственныеЛица) зафиксированы даты приема сотрудников. Сложность заключается в том, что в таблице ответственных нет даты увольнения, а значит, сотрудник считается ответственным с момента приема и до тех пор, пока не будет принят следующий специалист.
Для решения этой задачи нам необходимо имитировать механизм "Среза последних" для непериодических данных. Нам нужно для каждой платформы найти такую запись в таблице ответственных, у которой ДатаПриема максимально близка к ДатаРелиза, но не превышает ее.
Этот метод считается классическим для языка запросов 1С. Он наиболее читаем и эффективно использует индексы таблиц. Мы разделим логику на три этапа: получение всех возможных кандидатов, поиск самой актуальной даты приема и финальная выборка ФИО.
Проанализируем шаги реализации:
Рассмотрим пример кода, реализующий этот алгоритм:
// Шаг 1 и 2: Получаем таблицу Платформа - АктуальнаяДатаПриема
ВЫБРАТЬ
ДатыПлатформ.Название КАК Платформа,
МАКСИМУМ(Ответственные.ДатаПриема) КАК ДатаПриема
ПОМЕСТИТЬ ВТ_АктуальныеДаты
ИЗ
РегистрСведений.Платформы КАК ДатыПлатформ
ВНУТРЕННЕЕ СОЕДИНЕНИЕ РегистрСведений.ОтветственныеЛица КАК Ответственные
ПО ДатыПлатформ.ДатаРелиза >= Ответственные.ДатаПриема
СГРУППИРОВАТЬ ПО
ДатыПлатформ.Название;
////////////////////////////////////////////////////////////////////////////////
// Шаг 3: Получаем итоговые данные с ФИО
ВЫБРАТЬ
ВТ_АктуальныеДаты.Платформа КАК Платформа,
Ответственные.ФИО КАК Ответственный
ИЗ
ВТ_АктуальныеДаты КАК ВТ_АктуальныеДаты
ВНУТРЕННЕЕ СОЕДИНЕНИЕ РегистрСведений.ОтветственныеЛица КАК Ответственные
ПО ВТ_АктуальныеДаты.ДатаПриема = Ответственные.ДатаПриема
В данном решении мы используем ВНУТРЕННЕЕ СОЕДИНЕНИЕ. Обратите внимание: если релиз платформы состоялся раньше, чем был принят первый сотрудник, такая платформа не попадет в выборку. Если вам нужно вывести все платформы (даже без ответственных), замените соединение на ЛЕВОЕ СОЕДИНЕНИЕ и используйте функцию ЕСТЬNULL(Ответственные.ФИО, "Не назначен").
Иногда удобнее сначала определить "период действия" каждой записи сотрудника. Мы можем вычислить для каждого ответственного не только дату начала работы, но и дату ее окончания (момент приема следующего сотрудника). Рассмотрим, как это сделать программно в запросе.
Выясним причину, почему этот способ может быть полезен: он позволяет избежать многократных соединений в финальной части запроса, если нужно проверять вхождение даты в интервал (условие МЕЖДУ).
Пример формирования интервалов:
ВЫБРАТЬ
Слева.ФИО КАК ФИО,
Слева.ДатаПриема КАК НачалоИнтервала,
МИНИМУМ(ЕСТЬNULL(Справа.ДатаПриема, ДАТАВРЕМЯ(3999, 12, 31))) КАК КонецИнтервала
ПОМЕСТИТЬ ВТ_ИнтервалыРаботы
ИЗ
РегистрСведений.ОтветственныеЛица КАК Слева
ЛЕВОЕ СОЕДИНЕНИЕ РегистрСведений.ОтветственныеЛица КАК Справа
ПО Слева.ДатаПриема < Справа.ДатаПриема
СГРУППИРОВАТЬ ПО
Слева.ФИО,
Слева.ДатаПриема;
////////////////////////////////////////////////////////////////////////////////
ВЫБРАТЬ
Платформы.Название КАК Платформа,
ВТ_ИнтервалыРаботы.ФИО КАК Ответственный
ИЗ
РегистрСведений.Платформы КАК Платформы
ЛЕВОЕ СОЕДИНЕНИЕ ВТ_ИнтервалыРаботы КАК ВТ_ИнтервалыРаботы
ПО (Платформы.ДатаРелиза >= ВТ_ИнтервалыРаботы.НачалоИнтервала)
И (Платформы.ДатаРелиза < ВТ_ИнтервалыРаботы.КонецИнтервала)
В этом примере мы используем коррелированное соединение таблицы самой с собой. Для каждой записи сотрудника мы ищем минимальную дату среди всех дат приема, которые больше текущей. Если такой даты нет (это последний принятый сотрудник), мы устанавливаем "бесконечную" дату — 31 декабря 3999 года.
При работе с подобными запросами в 1С необходимо учитывать несколько критических моментов, которые могут повлиять на корректность данных:
Дата всегда включает время. Даже если в конфигураторе установлено "Состав даты: Дата", на уровне СУБД время хранится как 00:00:00. Если в один из регистров попадет дата со временем 12:00:00, а в другой — 00:00:00 того же дня, простое сравнение может дать неверный результат. Рекомендуется использовать функцию НАЧАЛОПЕРИОДА(Дата, ДЕНЬ) для приведения всех значений к единому моменту времени.МАКСИМУМ(ДатаПриема) запрос вернет обе фамилии для одной платформы, что приведет к дублированию строк в итоговой таблице. Чтобы этого избежать, структура данных должна содержать дополнительный уникальный ключ (например, время приема или номер приказа), который также нужно будет использовать в группировке.ДатаРелиза и ДатаПриема должны быть индексированы. В регистрах сведений измерения индексируются автоматически, а ресурсы — нет. Если дата в вашем регистре является ресурсом, скорость выполнения запроса на больших таблицах существенно снизится.ЕСТЬNULL при левых соединениях. Это гарантирует, что ваша таблица значений на форме будет заполнена корректными строками, а не значениями NULL, которые могут вызвать ошибки в коде при дальнейшей обработке.Подведем итог: для решения задачи сопоставления дат "в лоб" лучше всего подходит Способ №1 с использованием временной таблицы и агрегатной функции МАКСИМУМ. Он является стандартом разработки в среде 1С и гарантирует предсказуемый результат при соблюдении уникальности дат.