Почему в СКД возникает ошибка "поле не входит в группу Отбор.КоличествоВОтборе", если в консоли запрос отрабатывает?

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

При разработке отчетов с использованием Системы Компоновки Данных (СКД) мы часто сталкиваемся с ситуациями, когда запросы, прекрасно работающие в консоли запросов, при запуске через СКД выдают ошибки. Одна из таких распространенных ошибок – "поле не входит в группу". Давайте подробно разберем, почему это происходит и как мы можем решить эту проблему, основываясь на конкретном примере.

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


// Пакет 1: Получаем количество номенклатуры в отборе
ВЫБРАТЬ
    КОЛИЧЕСТВО(РАЗЛИЧНЫЕ Номенклатура.Ссылка) КАК КоличествоВОтборе
ПОМЕСТИТЬ Отбор
ИЗ
    Справочник.Номенклатура КАК Номенклатура
ГДЕ
    Номенклатура.Ссылка В(&Номенклатура)
;
////////////////////////////////////////////////////////////////////////////////
// Пакет 2: Получаем количество номенклатуры по каждой реализации (для всего списка номенклатуры)
ВЫБРАТЬ
    РеализацияТоваровУслугТовары.Ссылка КАК Ссылка,
    КОЛИЧЕСТВО(РАЗЛИЧНЫЕ РеализацияТоваровУслугТовары.Номенклатура) КАК Количество
ПОМЕСТИТЬ РеализацииОтбор
ИЗ
    Документ.РеализацияТоваровУслуг.Товары КАК РеализацияТоваровУслугТовары
ГДЕ
    РеализацияТоваровУслугТовары.Номенклатура В(&Номенклатура)
СГРУППИРОВАТЬ ПО
    РеализацияТоваровУслугТовары.Ссылка
;
////////////////////////////////////////////////////////////////////////////////
// Пакет 3: Основной отбор по количеству
ВЫБРАТЬ
    РеализацияТоваровУслугТовары.Ссылка КАК Ссылка,
    КОЛИЧЕСТВО(РАЗЛИЧНЫЕ РеализацияТоваровУслугТовары.Номенклатура) КАК Количество,
    Отбор.КоличествоВОтборе КАК КоличествоВОтборе
ПОМЕСТИТЬ Реализации
ИЗ
    Документ.РеализацияТоваровУслуг.Товары КАК РеализацияТоваровУслугТовары,
    Отбор КАК Отбор
СГРУППИРОВАТЬ ПО
    РеализацияТоваровУслугТовары.Ссылка,
    Отбор.КоличествоВОтборе
ИМЕЮЩИЕ
    КОЛИЧЕСТВО(РАЗЛИЧНЫЕ РеализацияТоваровУслугТовары.Номенклатура) = Отбор.КоличествоВОтборе
;
////////////////////////////////////////////////////////////////////////////////
// Пакет 4: Финальная выборка результата
ВЫБРАТЬ
    РеализацииОтбор.Ссылка КАК Ссылка
ИЗ
    РеализацииОтбор КАК РеализацииОтбор
        ЛЕВОЕ СОЕДИНЕНИЕ Реализации КАК Реализации
        ПО РеализацииОтбор.Ссылка = Реализации.Ссылка,
    Отбор КАК Отбор
ГДЕ
    РеализацииОтбор.Количество = Отбор.КоличествоВОтборе
    И НЕ Реализации.Ссылка ЕСТЬ NULL

При выполнении этого пакета запросов в консоли запросов мы, скорее всего, не получим никаких ошибок, и он отработает корректно. Однако при попытке использовать этот же запрос в СКД, особенно в третьем пакете, возникает ошибка: "поле не входит в группу Отбор.КоличествоВОтборе".

Причина возникновения ошибки в СКД

Основная причина такого поведения заключается в том, что СКД не просто выполняет наш запрос "как есть", а активно модифицирует его, добавляя собственные условия группировки, отборы и сортировки в зависимости от настроек отчета. Это происходит в фоновом режиме, чтобы обеспечить гибкость и динамичность отчетов.

В нашем случае, проблема кроется в третьем пакете запроса, в котором мы используем секцию ИМЕЮЩИЕ (HAVING) и одновременно выбираем поле Отбор.КоличествоВОтборе в списке полей ВЫБРАТЬ. Хотя это поле также присутствует в секции СГРУППИРОВАТЬ ПО, внутренний анализатор СКД может некорректно его интерпретировать при трансформации запроса. СКД ожидает, что поля в секции ИМЕЮЩИЕ должны быть либо агрегатными функциями, либо полями, по которым производится группировка на ее, СКД, уровне. Когда мы включаем Отбор.КоличествоВОтборе в список выбираемых полей, СКД может посчитать, что это поле должно быть обработано иначе, что приводит к конфликту с ее внутренними правилами агрегации и группировки.

Решение проблемы: Корректировка основного запроса

Чтобы обойти эту особенность СКД, мы можем внести небольшое, но важное изменение в третий пакет запроса. Суть решения заключается в том, чтобы убрать поле Отбор.КоличествоВОтборе из списка выбираемых полей (секция ВЫБРАТЬ), но при этом оставить его в секции СГРУППИРОВАТЬ ПО и ИМЕЮЩИЕ. Это позволит запросу корректно выполнить группировку и отбор на уровне временной таблицы, не сбивая с толку СКД при последующей обработке.

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


ВЫБРАТЬ
    РеализацияТоваровУслугТовары.Ссылка КАК Ссылка,
    КОЛИЧЕСТВО(РАЗЛИЧНЫЕ РеализацияТоваровУслугТовары.Номенклатура) КАК Количество
ПОМЕСТИТЬ Реализации
ИЗ
    Документ.РеализацияТоваровУслуг.Товары КАК РеализацияТоваровУслугТовары,
    Отбор КАК Отбор
СГРУППИРОВАТЬ ПО
    РеализацияТоваровУслугТовары.Ссылка,
    Отбор.КоличествоВОтборе
ИМЕЮЩИЕ
    КОЛИЧЕСТВО(РАЗЛИЧНЫЕ РеализацияТоваровУслугТовары.Номенклатура) = Отбор.КоличествоВОтборе

Обратите внимание, что мы удалили строку Отбор.КоличествоВОтборе КАК КоличествоВОтборе из секции ВЫБРАТЬ. После этого изменения, СКД перестает анализировать это поле как "выбираемое" для финального вывода данных из этой временной таблицы и корректно обрабатывает его в составе группировки и условия ИМЕЮЩИЕ. В результате, запрос успешно отрабатывает в СКД, и мы получаем ожидаемый результат.

Альтернативные подходы и общие рекомендации для работы с СКД

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

  1. Использование вложенных запросов

    Часто проблемы с агрегатными функциями и условиями ИМЕЮЩИЕ можно решить, переписав запрос с использованием вложенных запросов. Суть метода заключается в том, чтобы сначала во вложенном запросе рассчитать все необходимые агрегатные функции, а затем в основном запросе наложить условия на уже рассчитанные поля. Это делает логику запроса более прозрачной для СКД.

    Пример концепции:

    
    ВЫБРАТЬ
        ОсновнойЗапрос.Ссылка,
        ОсновнойЗапрос.Количество
    ИЗ
        (ВЫБРАТЬ
            РеализацияТоваровУслугТовары.Ссылка КАК Ссылка,
            КОЛИЧЕСТВО(РАЗЛИЧНЫЕ РеализацияТоваровУслугТовары.Номенклатура) КАК Количество
        ИЗ
            Документ.РеализацияТоваровУслуг.Товары КАК РеализацияТоваровУслугТовары
        СГРУППИРОВАТЬ ПО
            РеализацияТоваровУслугТовары.Ссылка) КАК ОсновнойЗапрос
            ЛЕВОЕ СОЕДИНЕНИЕ Отбор КАК Отбор
            ПО ИСТИНА // Или по любому другому подходящему условию
    ГДЕ
        ОсновнойЗапрос.Количество = Отбор.КоличествоВОтборе
    
  2. Перенос условий из ИМЕЮЩИЕ в ГДЕ основного запроса

    Если мы можем сначала подготовить данные во временной таблице, рассчитав там все агрегаты, то условия, которые изначально стояли в ИМЕЮЩИЕ, можно перенести в секцию ГДЕ финального запроса. Этот подход делает каждый шаг запроса более атомарным и понятным для СКД.

    Пример реализации:

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

    Иногда СКД может некорректно обрабатывать поля из-за отсутствия явных указаний в настройках отчета. Мы можем помочь СКД, явно указав роли для полей на вкладке "Наборы данных" -> "Поля" -> "Роли" в схеме компоновки данных. Установка флажка "Обязательное" для полей, которые критически важны для логики отчета (например, для полей, используемых в отборах или группировках), может подсказать СКД, что это поле должно всегда присутствовать в итоговом запросе, что может повлиять на его автоматическую генерацию и предотвратить ошибки.

  4. Важность финального SELECT

    Критически важно помнить, что СКД всегда работает с результатом последнего запроса в пакете. Все предыдущие запросы в пакете должны служить только для создания и наполнения временных таблиц (с использованием ПОМЕСТИТЬ). Финальный запрос, который будет использоваться СКД для получения данных отчета, не должен помещать данные во временную таблицу; он должен выводить их с помощью ВЫБРАТЬ. В нашем исходном примере (пакет 4) финальный запрос как раз выбирает данные, а не помещает во временную таблицу, что является правильным подходом.

  5. Использование параметров вместо временных таблиц для одиночных значений

    В данном случае, временная таблица Отбор фактически играет роль параметра, содержащего одно агрегированное значение. Более простым и надежным способом было бы получение этого значения в коде 1С (например, в модуле объекта отчета или перед вызовом отчета) и передача его как обычного параметра в запрос СКД. Это значительно упростило бы запрос, сделало бы его более понятным для СКД и устранило бы необходимость в лишних временных таблицах.

    Пример:

    1. Вычисляем КоличествоВОтборе в коде 1С:
      
      Запрос = Новый Запрос;
      Запрос.Текст = 
          "ВЫБРАТЬ
          |    КОЛИЧЕСТВО(РАЗЛИЧНЫЕ Номенклатура.Ссылка) КАК КоличествоВОтборе
          |ИЗ
          |    Справочник.Номенклатура КАК Номенклатура
          |ГДЕ
          |    Номенклатура.Ссылка В(&Номенклатура)";
      Запрос.УстановитьПараметр("Номенклатура", СписокНоменклатурыДляОтбора);
      Выборка = Запрос.Выполнить().Выбрать();
      Если Выборка.Следующий() Тогда
          КоличествоВОтбореПараметр = Выборка.КоличествоВОтборе;
      КонецЕсли;
      
    2. Передаем это значение как параметр в СКД.
    3. В запросе СКД используем этот параметр напрямую в секции ИМЕЮЩИЕ или ГДЕ.
    4. 
      ВЫБРАТЬ
          РеализацияТоваровУслугТовары.Ссылка КАК Ссылка,
          КОЛИЧЕСТВО(РАЗЛИЧНЫЕ РеализацияТоваровУслугТовары.Номенклатура) КАК Количество
      ИЗ
          Документ.РеализацияТоваровУслуг.Товары КАК РеализацияТоваровУслугТовары
      СГРУППИРОВАТЬ ПО
          РеализацияТоваровУслугТовары.Ссылка
      ИМЕЮЩИЕ
          КОЛИЧЕСТВО(РАЗЛИЧНЫЕ РеализацияТоваровУслугТовары.Номенклатура) = &КоличествоВОтбореПараметр
      

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

← На главную