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