При работе с базами данных автосервисов, транспортных компаний или автопарков часто возникает задача анализа эксплуатации техники. Одна из типовых задач — определить рамки активности автомобиля: его минимальный и максимальный пробег по имеющимся документам (например, по документам типа Заказ-наряд). Это позволяет быстро оценить общую дистанцию, которую проехал автомобиль за время обслуживания, или выявить некорректно заполненные данные.
В этой статье мы подробно разберем, как решить эту задачу исключительно средствами языка запросов 1С. Мы рассмотрим процесс создания запроса с нуля через Конструктор, научимся правильно обрабатывать нулевые значения и разберем оптимальные архитектурные практики для обеспечения высокой скорости работы базы данных.
Основная сложность, с которой часто сталкиваются начинающие разработчики при работе в Конструкторе запросов — это необходимость выбрать одно и то же поле (в нашем случае — Пробег) дважды, чтобы применить к нему разные агрегатные функции (МИНИМУМ и МАКСИМУМ).
Рассмотрим по шагам, как правильно настроить такой запрос в Конструкторе:
Документ.ЗаказНаряд.Контрагент (или Автомобиль) в список выбранных полей.Пробег в список выбранных полей первый раз. Переименуем его на вкладке «Объединения/Псевдонимы» в ПробегМинимум.Пробег в список выбранных полей второй раз. Переименуем его в ПробегМаксимум. Конструктор запросов позволяет выбирать одно и то же поле неограниченное количество раз, присваивая им уникальные псевдонимы.Контрагент перенесем в левое окно («Группировочные поля»).ПробегМинимум и ПробегМаксимум перенесем в правое окно («Суммируемые поля»).ПробегМинимум выберем значение Минимум, а для поля ПробегМаксимум — значение Максимум.В результате работы Конструктора мы получим следующий лаконичный текст запроса:
ВЫБРАТЬ
ЗаказНаряд.Контрагент КАК Контрагент,
МИНИМУМ(ЗаказНаряд.Пробег) КАК ПробегМинимум,
МАКСИМУМ(ЗаказНаряд.Пробег) КАК ПробегМаксимум
ИЗ
Документ.ЗаказНаряд КАК ЗаказНаряд
ГДЕ
НЕ ЗаказНаряд.ПометкаУдаления
СГРУППИРОВАТЬ ПО
ЗаказНаряд.Контрагент
Этот базовый вариант отлично работает в простых базах данных, где все документы заполнены идеально и не содержат ошибок ввода.
В реальных базах данных пользователи часто забывают заполнить поле пробега в Заказ-наряде, оставляя его пустым, либо система по умолчанию записывает туда значение 0. Если мы применим базовый запрос к такой базе, то в колонке ПробегМинимум для большинства клиентов мы получим значение 0, что полностью лишает отчет практического смысла.
Проанализируем, как можно исключить нули из расчета минимального пробега.
Иногда разработчики пытаются заменить нули на очень большое число (например, 999999) прямо внутри функции минимума:
МИНИМУМ(ВЫБОР КОГДА ЗаказНаряд.Пробег = 0 ТОГДА 999999 ИНАЧЕ ЗаказНаряд.Пробег КОНЕЦ)
Почему так делать нельзя? Если у контрагента будет всего один документ Заказ-наряд, и пробег в нем равен 0, то функция МИНИМУМ вернет 999999. Более того, если мы захотим применить аналогичную логику ко всей таблице, это число может исказить другие расчеты или случайно попасть в максимальные значения, запутав пользователей.
Стандарты SQL и внутренний язык запросов 1С спроектированы так, что любые агрегатные функции (включая МИНИМУМ и МАКСИМУМ) полностью **игнорируют** значения типа NULL. Мы можем использовать это свойство в своих целях.
Если мы преобразуем нулевые значения пробега в NULL, функция МИНИМУМ просто не будет брать их в расчет при группировке. Для этого применим конструкцию ВЫБОР КОГДА ... ТОГДА:
ВЫБРАТЬ
ЗаказНаряд.Контрагент КАК Контрагент,
МИНИМУМ(ВЫБОР
КОГДА ЗаказНаряд.Пробег = 0
ТОГДА NULL
ИНАЧЕ ЗаказНаряд.Пробег
КОНЕЦ) КАК ПробегМинимум,
МАКСИМУМ(ЗаказНаряд.Пробег) КАК ПробегМаксимум
ИЗ
Документ.ЗаказНаряд КАК ЗаказНаряд
ГДЕ
НЕ ЗаказНаряд.ПометкаУдаления
СГРУППИРОВАТЬ ПО
ЗаказНаряд.Контрагент
В этом случае, если у контрагента есть три документа с пробегами 0, 5000 и 12000, система превратит 0 в NULL, проигнорирует его и вернет корректный минимум: 5000.
Бывают ситуации, когда пробег нужно анализировать не по всем Заказ-нарядам подряд, а только по тем, в которых выполнялись определенные работы (например, проводилось регламентное ТО или замена масла). В этом случае нам необходимо наложить условие на табличную часть документа.
Для того чтобы не перегружать основной запрос соединениями с табличными частями (что может привести к дублированию строк и замедлению вычислений), рекомендуется использовать механизм связи по условию В (ВЫБРАТЬ...). Рассмотрим пример такого запроса:
ВЫБРАТЬ
ЗаказНаряд.Контрагент КАК Контрагент,
МИНИМУМ(ВЫБОР
КОГДА ЗаказНаряд.Пробег = 0
ТОГДА NULL
ИНАЧЕ ЗаказНаряд.Пробег
КОНЕЦ) КАК ПробегМинимум,
МАКСИМУМ(ЗаказНаряд.Пробег) КАК ПробегМаксимум
ИЗ
Документ.ЗаказНаряд КАК ЗаказНаряд
ГДЕ
НЕ ЗаказНаряд.ПометкаУдаления
И ЗаказНаряд.Ссылка В
(ВЫБРАТЬ РАЗЛИЧНЫЕ
ТабличнаяЧастьРаботы.Ссылка
ИЗ
Документ.ЗаказНаряд.Работы КАК ТабличнаяЧастьРаботы
ГДЕ
ТабличнаяЧастьРаботы.Работа = &ЦелеваяРабота)
СГРУППИРОВАТЬ ПО
ЗаказНаряд.Контрагент
Использование конструкции ВЫБРАТЬ РАЗЛИЧНЫЕ во вложенном запросе гарантирует, что мы получим только уникальный список ссылок на документы, избегая избыточного дублирования записей при объединении таблиц.
Важно различать математические экстремумы (минимальный и максимальный пробег за всю историю) и хронологические показатели (пробег при первом визите клиента и пробег при последнем визите).
В реальной жизни пробег автомобиля должен расти линейно. Однако из-за ошибок ввода или скрутки одометра минимальный пробег в базе данных не всегда соответствует самому первому визиту. Если перед нами стоит задача получить именно хронологические данные (пробег на дату первого и последнего заказ-наряда), обычные функции МИНИМУМ и МАКСИМУМ для поля Пробег не подойдут.
Для решения этой задачи нам потребуется применить двухэтапный запрос с использованием временных таблиц:
Проанализируем этот алгоритм на примере следующего запроса:
ВЫБРАТЬ
ЗаказНаряд.Контрагент КАК Контрагент,
МИНИМУМ(ЗаказНаряд.Дата) КАК ДатаПервая,
МАКСИМУМ(ЗаказНаряд.Дата) КАК ДатаПоследняя
ПОМЕСТИТЬ ВТ_ДатыВизитов
ИЗ
Документ.ЗаказНаряд КАК ЗаказНаряд
ГДЕ
НЕ ЗаказНаряд.ПометкаУдаления
И ЗаказНаряд.Пробег > 0
СГРУППИРОВАТЬ ПО
ЗаказНаряд.Контрагент
;
////////////////////////////////////////////////////////////////////////////////
ВЫБРАТЬ
ВТ_ДатыВизитов.Контрагент КАК Контрагент,
Макс(ДокПервый.Пробег) КАК ПробегНачальный,
Макс(ДокПоследний.Пробег) КАК ПробегКонечный
ИЗ
ВТ_ДатыВизитов КАК ВТ_ДатыВизитов
ЛЕВОЕ СОЕДИНЕНИЕ Документ.ЗаказНаряд КАК ДокПервый
ПО ВТ_ДатыВизитов.Контрагент = ДокПервый.Contragent
И ВТ_ДатыВизитов.ДатаПервая = ДокПервый.Дата
ЛЕВОЕ СОЕДИНЕНИЕ Документ.ЗаказНаряд КАК ДокПоследний
ПО ВТ_ДатыВизитов.Контрагент = ДокПоследний.Contragent
И ВТ_ДатыВизитов.ДатаПоследняя = ДокПоследний.Дата
СГРУППИРОВАТЬ ПО
ВТ_ДатыВизитов.Контрагент
Обратите внимание: Мы используем агрегатную функцию МАКС при получении полей ПробегНачальный и ПробегКонечный. Это необходимо для того, чтобы избежать дублирования строк, если в одну и ту же секунду (на одну дату) по какому-то контрагенту было проведено несколько документов.
Запросы напрямую к таблицам документов — это ресурсоемкая операция для СУБД. В отличие от регистров накопления или сведений, документы не имеют предварительно рассчитанных итогов. Если в вашей базе сотни тысяч заказ-нарядов, выполнение такого отчета может занимать значительное время.
Чтобы ускорить выполнение запроса, придерживайтесь следующих правил:
Контрагент (или Автомобиль) в метаданных документа имеет свойство «Индексировать» или «Индексировать с доп. упорядочиванием». Это позволит СУБД мгновенно выполнять группировку, не сканируя всю физическую таблицу документов целиком.ГДЕ (например, анализировать пробег за текущий или прошлый год). Это значительно снизит нагрузку на дисковую подсистему сервера.ПробегФизическихЛиц или ИсторияПробегаАвтомобилей). При проведении документа Заказ-наряд данные о пробеге должны записываться в этот регистр. Запрос к такому регистру сведений будет отрабатывать в разы быстрее, так как СУБД будет работать с плоской и оптимизированной структурой данных.Используя описанные методики группировки, правильной фильтрации через преобразование нулевых значений в NULL и построения временных таблиц, вы сможете решать задачи любой сложности по анализу истории эксплуатации транспортных средств ваших клиентов.