Почему запрос 1С возвращает удвоенный результат и как это исправить

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

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

Причина 1: Декартово произведение (использование запятой в блоке ИЗ)

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


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

Разберем, что здесь происходит. Использование запятой между таблицами СпецификацияКонтракта и ВыбраныйНомерКонтракта означает оператор CROSS JOIN (перекрестное соединение). Это значит, что каждая строка из первой таблицы будет соединена с каждой строкой из второй таблицы. Если в спецификации 10 строк, а в выбранных номерах — 2, то на выходе мы получим 20 строк еще до того, как сработает левое соединение с остатками.

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

Причина 2: Связь «Многие-ко-многим» и пропущенные измерения

Выясним причину, почему данные могут удваиваться, даже если мы используем правильное ЛЕВОЕ СОЕДИНЕНИЕ. Это часто случается, когда таблицы соединяются не по полному набору уникальных идентификаторов (измерений), а по частичному.

Рассмотрим ситуацию, описанную в теме: у нас есть номер контракта и номенклатура. В таблице спецификации две строки для одного контракта (например, «Ведро» и «Антисептик»). В таблице поставок по этому же контракту тоже две строки (те же «Ведро» и «Антисептик»).

Если мы соединим их только по полю НомерКонтракта, система выдаст следующий набор:

  1. Спецификация(Ведро) + Поставка(Ведро)
  2. Спецификация(Ведро) + Поставка(Антисептик) — лишняя строка!
  3. Спецификация(Антисептик) + Поставка(Ведро) — лишняя строка!
  4. Спецификация(Антисептик) + Поставка(Антисептик)

Вместо 2 логических строк мы получили 4. Это происходит потому, что для системы НомерКонтракта не является уникальным ключом в обеих таблицах.

Решение: Соединяйте таблицы по всем измерениям, которые определяют уникальность записи. В данном случае условие должно выглядеть так:


ЛЕВОЕ СОЕДИНЕНИЕ ВремТабПоставкаОстаток КАК ВремТабПоставкаОстаток 
ПО СпецификацияКонтракта.НомерКонтракта = ВремТабПоставкаОстаток.НомерКонтракта
И СпецификацияКонтракта.Номенклатура = ВремТабПоставкаОстаток.Номенклатура

Причина 3: Неправильное использование группировки

Проанализируем первый запрос автора темы. В нем используется СГРУППИРОВАТЬ ПО, но отсутствуют агрегатные функции (такие как СУММА, МАКСИМУМ или КОЛИЧЕСТВО). В 1С группировка без агрегатных функций работает просто как выборка различных (уникальных) записей. Однако, если в полях, по которым идет группировка, есть хоть малейшее различие (например, разный остаток в разных строках), система выдаст обе строки.

Посмотрим на пример правильной подготовки временной таблицы с остатками, чтобы данные не «раздували» основной запрос:


ВЫБРАТЬ
    Поставка.ПоставкаНомерКонтракта КАК ПоставкаНомерКонтракта,
    СУММА(Поставка.Осталось) КАК Осталось
ПОМЕСТИТЬ ВремТабПоставкаОстаток
ИЗ
    РегистрСведений.Поставка КАК Поставка
СГРУППИРОВАТЬ ПО
    Поставка.ПоставкаНомерКонтракта;

Используя СУММА, мы гарантируем, что для каждого номера контракта во временной таблице будет строго одна строка. Это исключает дублирование при последующем левом соединении к спецификации.

Причина 4: Периодичность регистров сведений

Хотя в обсуждении это упоминалось косвенно, важно помнить: если ваш регистр Поставка является периодическим, то простое обращение к физической таблице РегистрСведений.Поставка вернет все записи за всю историю изменений. Если по одному контракту остаток менялся 5 раз, вы получите 5 строк.

Решение: Используйте виртуальную таблицу СрезПоследних. Это позволит получить только актуальные данные на текущий момент.


ИЗ
    РегистрСведений.Поставка.СрезПоследних(&Период, ) КАК Поставка

Рекомендации по итогам анализа ситуации

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

  1. Проверяйте связи: Избегайте использования запятых в секции ИЗ. Используйте ЛЕВОЕ СОЕДИНЕНИЕ.
  2. Агрегируйте данные: Если вам нужно присоединить числовые показатели (остатки, суммы) из таблицы, где одному ключу связи может соответствовать несколько строк, сначала сгруппируйте эти данные во временной таблице с использованием функции СУММА.
  3. Соблюдайте полноту условий: Если в регистрах есть несколько измерений (Контракт, Номенклатура, Склад), связь в ПО должна идти по всем этим измерениям.
  4. Используйте виртуальные таблицы: Для получения остатков в регистрах накопления или актуальных значений в регистрах сведений используйте специально предназначенные для этого таблицы (Остатки, СрезПоследних).
  5. Обрабатывайте NULL: При использовании левого соединения всегда применяйте функцию ЕСТЬNULL(Поле, 0), чтобы отсутствие данных в правой таблице не приводило к ошибкам в расчетах.

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

← На главную