При разработке отчетов в 1С программисты часто сталкиваются с классической проблемой SQL-запросов: задвоением данных. Эта ситуация возникает, когда мы пытаемся соединить таблицу «Заказы» с таблицей «Поступления». Поскольку на один заказ может быть оформлено несколько документов поступления, при левом соединении (ЛЕВОЕ СОЕДИНЕНИЕ) строка заказа копируется столько раз, сколько документов поступления найдено в базе. В итоге сумма заказа в отчете увеличивается в несколько раз, что делает данные недостоверными.
В этой статье мы подробно разберем, почему это происходит, и рассмотрим несколько эффективных способов решения проблемы: от манипуляций с временными таблицами до использования возможностей Системы компоновки данных (СКД).
Рассмотрим ситуацию на примере. У нас есть Заказ поставщику №1 на 100 единиц товара. По этому заказу было два поступления: Приобретение №1 на 40 единиц и Приобретение №2 на 60 единиц. Если мы напишем простой запрос с левым соединением по номенклатуре, СУБД вернет нам две строки:
При попытке рассчитать общий итог по полю «Заказано», отчет покажет нам 200 единиц вместо реальных 100. Это происходит из-за природы JOIN, который создает декартово произведение строк, удовлетворяющих условию связи.
Проанализируем самый надежный метод решения этой задачи в рамках языка запросов 1С. Идея заключается в том, чтобы «привязать» количество из заказа только к одному (первому попавшемуся) документу поступления, а для всех остальных строк этого же заказа вывести ноль.
Рассмотрим алгоритм реализации по шагам:
Посмотрим на пример кода, реализующий эту логику:
// Шаг 1: Получаем список всех поступлений и их связей с заказами
ВЫБРАТЬ
ПриобретениеТовары.Ссылка КАК Поступление,
ПриобретениеТовары.ЗаказПоставщику КАК Заказ,
ПриобретениеТовары.Номенклатура КАК Номенклатура,
ПриобретениеТовары.Количество КАК КоличествоПоступило
ПОМЕСТИТЬ ВТ_Поступления
ИЗ
Документ.ПриобретениеТоваровУслуг.Товары КАК ПриобретениеТовары
ГДЕ
ПриобретениеТовары.Ссылка.Проведен;
////////////////////////////////////////////////////////////////////////////////
// Шаг 2: Для каждого заказа находим ОДНО уникальное поступление (например, первое по ссылке)
ВЫБРАТЬ
ВТ_Поступления.Заказ КАК Заказ,
ВТ_Поступления.Номенклатура КАК Номенклатура,
МИНИМУМ(ВТ_Поступления.Поступление) КАК ПервоеПоступление
ПОМЕСТИТЬ ВТ_ПервыеПоступления
ИЗ
ВТ_Поступления КАК ВТ_Поступления
СГРУППИРОВАТЬ ПО
ВТ_Поступления.Заказ,
ВТ_Поступления.Номенклатура;
////////////////////////////////////////////////////////////////////////////////
// Шаг 3: Соединяем данные и выводим количество заказа только для первой строки
ВЫБРАТЬ
ВТ_Поступления.Заказ КАК Заказ,
ВТ_Поступления.Поступление КАК Поступление,
ВТ_Поступления.Номенклатура КАК Номенклатура,
ВТ_Поступления.КоличествоПоступило КАК КоличествоПоступило,
ВЫБОР
КОГДА ВТ_ПервыеПоступления.ПервоеПоступление ЕСТЬ NULL
ТОГДА 0
ИНАЧЕ ЗаказТовары.Количество
КОНЕЦ КАК КоличествоЗаказано
ИЗ
ВТ_Поступления КАК ВТ_Поступления
ЛЕВОЕ СОЕДИНЕНИЕ ВТ_ПервыеПоступления КАК ВТ_ПервыеПоступления
ПО ВТ_Поступления.Поступление = ВТ_ПервыеПоступления.ПервоеПоступление
И ВТ_Поступления.Заказ = ВТ_ПервыеПоступления.Заказ
И ВТ_Поступления.Номенклатура = ВТ_ПервыеПоступления.Номенклатура
ЛЕВОЕ СОЕДИНЕНИЕ Документ.ЗаказПоставщику.Товары КАК ЗаказТовары
ПО ВТ_Поступления.Заказ = ЗаказТовары.Ссылка
И ВТ_Поступления.Номенклатура = ЗаказТовары.Номенклатура
В результате этого запроса поле КоличествоЗаказано заполнится значением только в одной строке для каждого заказа. В остальных строках (для других поступлений по этому же заказу) будет стоять 0. При расчете итогов сумма будет корректной.
Проанализируем другой подход, который часто называют «разнесением данных». Если LEFT JOIN порождает дубли, мы можем полностью отказаться от него в пользу объединения двух независимых выборок. Этот метод считается более производительным при работе с большими объемами данных, так как он исключает сложные условия связи.
Рассмотрим структуру такого запроса:
ВЫБРАТЬ
ЗаказТовары.Ссылка КАК Документ,
ЗаказТовары.Номенклатура КАК Номенклатура,
ЗаказТовары.Количество КАК КоличествоЗаказано,
0 КАК КоличествоПоступило
ИЗ
Документ.ЗаказПоставщику.Товары КАК ЗаказТовары
ОБЪЕДИНИТЬ ВСЕ
ВЫБРАТЬ
ПриобретениеТовары.ЗаказПоставщику,
ПриобретениеТовары.Номенклатура,
0,
ПриобретениеТовары.Количество
ИЗ
Документ.ПриобретениеТоваровУслуг.Товары КАК ПриобретениеТовары
После объединения мы помещаем результат во временную таблицу и делаем итоговую выборку с группировкой по Номенклатура и Документ (Заказ). В этом случае данные по заказу и данные по поступлению будут находиться в разных строках таблицы, и никакое «перемножение» не возникнет в принципе.
Выясним, как можно решить проблему, если вы строите отчет на СКД. В СКД есть мощный механизм агрегации, который позволяет вычислять ресурсы по разным группировкам. Если вам необходимо видеть и детальные записи поступлений, и корректный итог по заказу, выполните следующие настройки:
КоличествоЗаказано в ресурсы.СУММА(КоличествоЗаказано) укажите МАКСИМУМ(КоличествоЗаказано).ЗаказПоставщику.Это заставит систему брать только одно максимальное значение заказа для каждой группы, даже если внутри этой группы десять строк с поступлениями. Для расчета общих итогов по всему отчету можно использовать более сложное выражение:
Сумма(ВычислитьВыражениеСГруппировкойМассив("Максимум(КоличествоЗаказано)", "ЗаказПоставщику"))
Эта функция сначала вычислит максимум для каждого заказа, соберет эти значения в массив и только потом просуммирует их. Это «золотой стандарт» разработки сложных отчетов в 1С.
Проанализировав ситуацию, можно выделить несколько важных советов для предотвращения подобных ошибок:
ЗаказыПоставщикам. Это не только быстрее, но и исключает многие проблемы с дублями.Номенклатура, Характеристика, Склад, а иногда и НомерСтроки (если одна и та же номенклатура может быть в заказе двумя разными строками).ЕСТЬNULL(Поле, 0) при работе с числовыми данными в левых соединениях, чтобы избежать ошибок при суммировании пустых значений.Выбор конкретного метода зависит от ваших задач. Если нужен быстрый результат в консоли запросов — используйте метод с МИНИМУМ(Ссылка). Если вы разрабатываете сложный отчет — лучше настроить агрегаты в СКД. Это сохранит гибкость отчета и позволит пользователям делать расшифровки без потери точности данных.