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