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