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