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

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

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

Разбор причины проблемы: Декартово произведение

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

Представим пример: в чеке две строки с одной и той же номенклатурой "Смартфон". В табличной части Товары это две записи. В табличной части СерийныеНомера для этого чека также числятся два серийных номера. Если в условии связи указать только ЧекККМТовары.Ссылка = ЧекККМСерийныеНомера.Ссылка, то система сопоставит каждую строку товаров с каждой строкой серийных номеров. В итоге вместо двух строк мы получим четыре (2 товара * 2 серийных номера). Если в одной ТЧ 3 строки, а в другой 3 — на выходе будет 9. Это и есть причина "удвоения" или "размножения" данных.

Почему не помогают РАЗЛИЧНЫЕ и СГРУППИРОВАТЬ ПО

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

Ошибка "группировка по таблицам верхнего уровня и таблицам" возникает из-за нарушения иерархической логики запроса. Когда в одном списке СГРУППИРОВАТЬ ПО смешиваются поля из вложенных таблиц и поля самого документа, система не может однозначно определить уровень детализации итогов. Проанализируем более современные и правильные подходы к решению этой задачи.

Решение №1: Использование поля КлючСвязи

В современных типовых конфигурациях (начиная с редакций УТ 10.3, Розница 1.0 и выше) для связи строк разных табличных частей внутри одного документа используется специальное поле — КлючСвязи. Это числовой идентификатор, который уникален в пределах одного документа для каждой логической строки.

Рассмотрим, как должно выглядеть правильное соединение таблиц в запросе:


ВНУТРЕННЕЕ СОЕДИНЕНИЕ Документ.ЧекККМ.СерийныеНомера КАК ЧекККМСерийныеНомера
ПО ЧекККМСерийныеНомера.Ссылка = ДокументЧекККМ.Ссылка
    И ЧекККМСерийныеНомера.КлючСвязи = ЧекККМТовары.КлючСвязи

Добавление условия И ЧекККМСерийныеНомера.КлючСвязи = ЧекККМТовары.КлючСвязи гарантирует, что конкретный серийный номер привяжется именно к той строке товара, к которой он относится, даже если в документе десять строк с одинаковой номенклатурой. Это исключает дублирование строк на уровне SQL-запроса.

Решение №2: Использование временных таблиц (современный стандарт)

Запрос, представленный в теме, относится к категории "тяжелых" и трудночитаемых. В современной практике 1С принято разбивать такие задачи на этапы с использованием Временных Таблиц. Это позволяет сгруппировать данные на каждом этапе и избежать декартова произведения при итоговом соединении.

Разберем по шагам, как перестроить логику:

  1. Выбираем товары: В первой временной таблице выбираем данные из ЧекККМ.Товары, накладываем фильтры по дате, кассе и проводим группировку.
  2. Выбираем серийные номера: Во второй временной таблице выбираем серийные номера, также группируя их по ссылке и ключу связи.
  3. Финальное соединение: В итоговом запросе соединяем две плоские временные таблицы.

Пример реализации через временные таблицы:


// Шаг 1: Получаем товары
ВЫБРАТЬ
    Товары.Ссылка КАК Ссылка,
    Товары.Номенклатура КАК Номенклатура,
    Товары.КлючСвязи КАК КлючСвязи,
    СУММА(Товары.Количество) КАК Количество
ПОМЕСТИТЬ ВТ_Товары
ИЗ
    Документ.ЧекККМ.Товары КАК Товары
ГДЕ
    Товары.Ссылка.Проведен
    И Товары.Ссылка.Дата МЕЖДУ &НачДата И &КонДата
СГРУППИРОВАТЬ ПО
    Товары.Ссылка,
    Товары.Номенклатура,
    Товары.КлючСвязи;

////////////////////////////////////////////////////////////////////////////////
// Шаг 2: Получаем серийные номера
ВЫБРАТЬ
    СерийныеНомера.Ссылка КАК Ссылка,
    СерийныеНомера.КлючСвязи КАК КлючСвязи,
    СерийныеНомера.СерийныйНомер КАК СерийныйНомер
ПОМЕСТИТЬ ВТ_Серии
ИЗ
    Документ.ЧекККМ.СерийныеНомера КАК СерийныеНомера
ГДЕ
    СерийныеНомера.Ссылка.Проведен
    И СерийныеНомера.Ссылка.Дата МЕЖДУ &НачДата И &КонДата;

////////////////////////////////////////////////////////////////////////////////
// Шаг 3: Итоговая сборка данных без дублей
ВЫБРАТЬ
    ВТ_Товары.Номенклатура,
    ВТ_Серии.СерийныйНомер,
    ВТ_Товары.Количество
ИЗ
    ВТ_Товары КАК ВТ_Товары
    ЛЕВОЕ СОЕДИНЕНИЕ ВТ_Серии КАК ВТ_Серии
    ПО ВТ_Товары.Ссылка = ВТ_Серии.Ссылка
        И ВТ_Товары.КлючСвязи = ВТ_Серии.КлючСвязи

Оптимизация работы с обращениями через точку

Проанализируем совет из сообщения №4 форума. Автор сообщения рекомендует делать запрос не к таблице документа с последующим соединением с табличной частью, а напрямую к табличной части. Это существенно упрощает код. В 1С обращение к реквизитам шапки из таблицы строки (например, ЧекККМТовары.Ссылка.Склад) автоматически создает соединение на уровне SQL, но делает текст запроса в конфигураторе гораздо короче и понятнее.

Вместо громоздких конструкций вида:


ИЗ
    Документ.ЧекККМ КАК ДокументЧекККМ
    ВНУТРЕННЕЕ СОЕДИНЕНИЕ Документ.ЧекККМ.Товары КАК ЧекККМТовары
    ПО (ЧекККМТовары.Ссылка = ДокументЧекККМ.Ссылка)

Лучше использовать более лаконичный вариант:


ИЗ
    Документ.ЧекККМ.Товары КАК ЧекККМТовары
ГДЕ
    ЧекККМТовары.Ссылка.Проведен
    И ЧекККМТовары.Ссылка.Склад = &Склад

Работа с видами операций (Продажа и Возврат)

В исходном запросе автора используется интересная логика подсчета через СУММА(ВЫБОР КОГДА...). Это позволяет в одном отчете схлопнуть продажи и возвраты, где возврат идет с минусом. Рассмотрим, как это реализовать правильно, чтобы дубли не искажали математику:


СУММА(ВЫБОР
    КОГДА ЧекККМТовары.Ссылка.ВидОперации = ЗНАЧЕНИЕ(Перечисление.ВидыОперацийЧекККМ.Продажа)
        ТОГДА 1
    ИНАЧЕ -1
КОНЕЦ) КАК КоличествоПоЧекам

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

Выясним причину использования ПУСТАЯТАБЛИЦА

В тексте запроса автора встречается конструкция ПУСТАЯТАБЛИЦА.(...). Это устаревший механизм, использовавшийся для формирования структуры вложенных таблиц в результатах запроса (так называемые выборки с обходом по группировкам или иерархией). В современном коде 1С использование вложенных таблиц в ВЫБРАТЬ считается дурным тоном (Legacy), так как это создает огромную нагрузку на память сервера приложений и крайне неэффективно передается по сети.

Вместо этого мы рекомендуем получать "плоскую" таблицу данных и обрабатывать иерархию уже программно при выводе в отчет или дерево значений.

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

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

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

← На главную