Почему дублируются результаты запроса в 1С и как это исправить?

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

Проблема дублирования строк в результатах запроса — одна из самых распространенных трудностей, с которыми сталкиваются разработчики при написании сложных отчетов или обработок. Особенно часто это проявляется, когда в одном запросе соединяются данные из нескольких регистров сведений. Разберем подробно, почему возникают лишние записи и как привести структуру запроса к правильному виду.

Причина 1: Декартово произведение при перечислении таблиц через запятую

Проанализируем первую ошибку, которую совершают новички. Рассмотрим фрагмент кода из обсуждения:


ИЗ 
    РегистрСведений.Поставка КАК Поставка, 
    РегистрСведений.СпецификацияКонтракта КАК СпецификацияКонтракта,
    РегистрСведений.ВыбраныйНомерКонтракта КАК ВыбраныйНомерКонтракта

В этом случае, если таблицы не связаны в секции ГДЕ или через явное СОЕДИНЕНИЕ, платформа выполняет так называемое декартово произведение. Это означает, что каждая строка из первой таблицы будет сопоставлена с каждой строкой из второй и третьей таблиц. Если в первой таблице 10 записей, а во второй — 5, на выходе мы получим 50 строк. Выясним причину: отсутствие четкого условия связи заставляет систему выводить все возможные комбинации данных.

Решение: Всегда используйте явные соединения (ЛЕВОЕ СОЕДИНЕНИЕ, ВНУТРЕННЕЕ СОЕДИНЕНИЕ) с указанием ключевых полей в секции ПО.

Причина 2: Связь «один ко многим»

Это наиболее глубокая проблема. Даже если мы используем ВНУТРЕННЕЕ СОЕДИНЕНИЕ, дубли могут остаться. Разберем по шагам, почему это происходит. Предположим, у нас есть контракт №123. В таблице Поставка по этому контракту числится 3 строки (три разные позиции поставки). В таблице СпецификацияКонтракта по этому же контракту числится 2 строки.

При соединении этих таблиц по полю НомерКонтракта система найдет для каждой из 3-х строк поставки по 2 соответствия в спецификации. В итоге получится 3 * 2 = 6 строк. Данные по поставке продублируются дважды для каждой строки спецификации.

Проанализируем ситуацию: чтобы избежать «размножения» строк, одна из таблиц (или обе) должна содержать уникальную запись для ключа связи. Если нам нужно получить общую сумму по спецификации и детализацию по поставкам, мы не можем просто их соединить «в лоб».

Метод решения: Использование временных таблиц и группировок

Для корректного решения задачи нам необходимо предварительно подготовить данные. Рассмотренный в теме подход с использованием ПОМЕСТИТЬ во временную таблицу является правильным вектором развития. Рассмотрим алгоритм:

  1. Свертка данных: Сгруппируйте данные в первой таблице по ключевому полю, чтобы получить одну строку на один контракт.
  2. Использование временной таблицы: Поместите результат во временную таблицу.
  3. Соединение с основной таблицей: Соединяйте детальные записи (например, поставки) с уже сгруппированными данными.

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


ВЫБРАТЬ
    ДопСоглашения.НомерКонтракта КАК НомерКонтракта,
    СУММА(ВЫБОР
            КОГДА ДопСоглашения.Наименование = "Да"
                ТОГДА ДопСоглашения.Количество
            ИНАЧЕ -ДопСоглашения.Количество
        КОНЕЦ) КАК ИтоговоеКоличествоДоп
ПОМЕСТИТЬ ВтСуммаДопСоглашений
ИЗ
    РегистрСведений.ДополнительныеСоглашения КАК ДопСоглашения
СГРУППИРОВАТЬ ПО
    ДопСоглашения.НомерКонтракта;

////////////////////////////////////////////////////////////////////////////////
ВЫБРАТЬ
    Поставка.ПоставкаНомерКонтракта,
    Поставка.ПоставкаНаименованиеПозиции,
    Спецификация.Количество + ЕСТЬNULL(ВтСуммаДопСоглашений.ИтоговоеКоличествоДоп, 0) КАК ВсегоСУчетомДоп
ИЗ
    РегистрСведений.Поставка КАК Поставка
        ЛЕВОЕ СОЕДИНЕНИЕ РегистрСведений.СпецификацияКонтракта КАК Спецификация
        ПО Поставка.ПоставкаНомерКонтракта = Спецификация.СпецификацияКонтрактаНомерКонтракта
        ЛЕВОЕ СОЕДИНЕНИЕ ВтСуммаДопСоглашений КАК ВтСуммаДопСоглашений
        ПО Поставка.ПоставкаНомерКонтракта = ВтСуммаДопСоглашений.НомерКонтракта

В данном примере мы сначала «схлопнули» все дополнительные соглашения до одной строки на контракт, а затем присоединили эту цифру к основным данным. Однако, если Спецификация также содержит несколько строк, дубли останутся. В таком случае Спецификация также должна быть предварительно сгруппирована.

Причина 3: Избыточные таблицы фильтрации

В исходном запросе автора используются регистры ВыбраныйНомерКонтракта и ВыбраныйНомерПоставки. Соединение с ними также может порождать дубли, если в этих регистрах по какой-то причине оказалось больше одной записи для текущего сеанса. Кроме того, это создает лишнюю нагрузку на СУБД.

Рекомендация: Вместо хранения выбора пользователя в физических таблицах базы данных, передавайте эти значения в запрос как параметры. Это не только уберет риск дублирования из-за некорректных данных в «буферных» регистрах, но и ускорит выполнение кода.


// Вместо соединения с регистром "ВыбраныйНомерКонтракта"
ГДЕ
    Поставка.ПоставкаНомерКонтракта = &ВыбранныйКонтракт

Причина 4: Отсутствие учета уникальных измерений

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

Выясним причину: соединение происходит по неполному ключу. Чтобы записи сопоставлялись «один к одному», необходимо в условии ПО перечислить все измерения, которые определяют уникальность строки в обоих регистрах.

Использование ключевого слова РАЗЛИЧНЫЕ

Если ваша задача — просто получить список уникальных номеров или наименований, и вы не занимаетесь суммированием количественных показателей, самым быстрым способом избавиться от дублей будет использование модификатора РАЗЛИЧНЫЕ (в SQL-аналоге DISTINCT).


ВЫБРАТЬ РАЗЛИЧНЫЕ
    Поставка.ПоставкаНомерКонтракта,
    Поставка.ПоставкаЦенаЗаЕд
ИЗ
    РегистрСведений.Поставка КАК Поставка

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

Итоги и рекомендации

Подводя итог нашему разбору, выделим основные правила работы без дублей:

  1. Никогда не перечисляйте таблицы через запятую в секции ИЗ. Используйте Конструктор запроса, вкладка «Связи».
  2. Если одна таблица содержит детальные записи, а другая — итоговые, всегда делайте СГРУППИРОВАТЬ ПО для детальной таблицы перед соединением.
  3. Проверяйте уникальность ключей. Если вы соединяете по НомерКонтракта, убедитесь, что в соединяемой таблице этот номер встречается только один раз.
  4. Используйте ВременныеТаблицы для промежуточных расчетов. Это делает структуру запроса прозрачной и позволяет контролировать количество строк на каждом этапе.
  5. Заменяйте «регистры-фильтры» на Параметры запроса (&Параметр).

Следуя этим правилам, вы сможете строить надежные и быстрые запросы, обеспечивая точность данных в ваших отчетах 1С.

← На главную