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