Как убрать дублирование строк в запросе 1С при соединении табличной части с регистром?

Программист 1С v8.3 (Управляемые формы) Управленческий учет IT и автоматизация бизнеса
← На главную

При работе с запросами в платформе 1С:Предприятие разработчики часто сталкиваются с ситуацией, когда результат выборки содержит дублирующиеся строки. Особенно остро эта проблема проявляется при соединении табличных частей документов с регистрами сведений или другими подчиненными таблицами. Выясним причину этой проблемы, проанализируем, почему стандартные подходы вроде использования ключевого слова РАЗЛИЧНЫЕ могут навредить, и разберем по шагам наиболее эффективные методы решения.

Почему происходит размножение строк в запросе?

Рассмотрим ситуацию, с которой столкнулся автор темы. В запросе соединяются табличная часть документа Документ.РСУ_РасходныйОрдер.ТЧОтгруженныеТМЦ и регистр сведений РегистрСведений.ПС_ОтсканированныеУпаковкиПодготовкаКОтгрузке. Связующим звеном выступает документ маршрута: ПС_ДокМаршрут = ДокОснование.

Здесь мы сталкиваемся с классической проблемой связи «один ко многим» (декартово произведение):

  1. В табличной части одного расходного ордера содержится список номенклатурных позиций (например, 5 строк с товарами).
  2. В регистре сведений по данному маршруту (который указан в ордере) зафиксировано множество записей — например, 20 записей по отсканированным упаковкам.
  3. При выполнении левого соединения СУБД сопоставляет каждую из 5 строк табличной части документа с каждой из 20 записей регистра. В результате вместо ожидаемых 5 строк мы получаем 100 строк в результате запроса (5 * 20). Каждая строка номенклатуры дублируется ровно столько раз, сколько записей по данному маршруту содержится в регистре.

Почему использование конструкции ВЫБРАТЬ РАЗЛИЧНЫЕ — это ошибка?

На первый взгляд, самым простым решением кажется использование конструкции ВЫБРАТЬ РАЗЛИЧНЫЕ (в SQL-эквиваленте — DISTINCT). Однако этот подход таит в себе серьезные опасности:

Проанализируем правильные и архитектурно верные способы решения этой задачи.

Решение 1. Использование временной таблицы со сжатием (группировкой) данных

Это наиболее эффективная и рекомендуемая практика в разработке на платформе 1С. Поскольку от регистра сведений нам требуется получить только одно конкретное значение (например, поле Автор), мы можем предварительно подготовить данные в пакете запросов.

Идея заключается в том, чтобы сначала выбрать из регистра сведений уникальные пары «Маршрут — Автор», свернуть их (сгруппировать), поместить во временную таблицу и уже ее соединять с табличной частью документа. Таким образом, на один маршрут гарантированно будет приходиться только одна строка.

Разберем по шагам создание такого пакетного запроса:


// Первый запрос пакета: подготавливаем уникальные данные из регистра
ВЫБРАТЬ
    РегистрПодготовка.ДокОснование КАК Маршрут,
    МАКСИМУМ(РегистрПодготовка.Автор) КАК АвторПодготовки
ПОМЕСТИТЬ ВТ_УникальныеАвторы
ИЗ
    РегистрСведений.ПС_ОтсканированныеУпаковкиПодготовкаКОтгрузке КАК РегистрПодготовка
ГДЕ
    РегистрПодготовка.ДокОснование ССЫЛКА Документ.Маршрут
СГРУППИРОВАТЬ ПО
    РегистрПодготовка.ДокОснование
;

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

В первом запросе пакета мы использовали агрегатную функцию МАКСИМУМ для поля Автор. Это гарантирует, что даже если по какому-то маршруту в регистре окажется несколько записей с разными авторами, СУБД выберет только одного (максимального по алфавиту) и вернет ровно одну строку для каждого уникального маршрута. При соединении во втором запросе дублирования строк номенклатуры не произойдет.

Решение 2. Использование вложенного запроса в левом соединении

Если по какой-то причине мы не хотим использовать пакетные запросы и временные таблицы (например, в простых динамических списках, где пакетные запросы не поддерживаются), аналогичного результата можно добиться с помощью вложенного запроса непосредственно в секции соединения.

Рассмотрим пример реализации такого запроса:


ВЫБРАТЬ
    ТЧ_Ордера.Ссылка.Автор КАК АвторРасходногоОрдера,
    НАЧАЛОПЕРИОДА(ТЧ_Ордера.Ссылка.Дата, МЕСЯЦ) КАК Дата,
    ТЧ_Ордера.Ссылка.ДокРасход.Номер КАК НомерДокРасход,
    ТЧ_Ордера.Ссылка КАК РасходныйОрдер,
    ТЧ_Ордера.Ссылка.ПС_ДокМаршрут КАК Маршрут,
    ПодготовкаОграниченная.Маршрут КАК ДокументПодготовки,
    ПодготовкаОграниченная.АвторПодготовки КАК АвторПодготовки,
    ТЧ_Ордера.Номенклатура КАК Номенклатура
ИЗ
    Документ.РСУ_РасходныйОрдер.ТЧОтгруженныеТМЦ КАК ТЧ_Ордера
    ЛЕВОЕ СОЕДИНЕНИЕ (
        ВЫБРАТЬ
            РегистрПодготовка.ДокОснование КАК Маршрут,
            МАКСИМУМ(РегистрПодготовка.Автор) КАК АвторПодготовки
        ИЗ
            РегистрСведений.ПС_ОтсканированныеУпаковкиПодготовкаКОтгрузке КАК РегистрПодготовка
        СГРУППИРОВАТЬ ПО
            РегистрПодготовка.ДокОснование
    ) КАК ПодготовкаОграниченная
        ПО ТЧ_Ордера.Ссылка.ПС_ДокМаршрут = ПодготовкаОграниченная.Маршрут
ГДЕ
    ТЧ_Ордера.Ссылка.Дата МЕЖДУ &ДатаНачала И &ДатаОкончания

Этот способ позволяет решить проблему дубликатов в один запрос, однако стоит помнить, что вложенные запросы сложнее поддаются оптимизации планировщиком СУБД, особенно на крупных базах данных.

Важные нюансы оптимизации запросов в 1С

При решении подобных задач важно обращать внимание на архитектуру кода и правила написания запросов, чтобы не вызвать перегрузку системы:

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

Таким образом, понимание механизмов связей «один ко многим» и умение использовать временные таблицы с группировкой позволяют легко решать проблемы с дублированием строк в запросах 1С, сохраняя высокую производительность и точность получаемых данных.

← На главную