Как исправить ошибку «В запросе с предложениями РАЗЛИЧНЫЕ или ОБЪЕДИНИТЬ нельзя использовать вложенные таблицы» в 1С?

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

При разработке отчетов и обработок в платформе 1С:Предприятие разработчики часто сталкиваются с ошибкой времени выполнения:

«В запросе с предложениями РАЗЛИЧНЫЕ или ОБЪЕДИНИТЬ нельзя использовать вложенные таблицы»

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

Почему возникает данная ошибка

Проанализируем исходный фрагмент запроса, который привел к сбою:


ВЫБРАТЬ РАЗЛИЧНЫЕ
    ПоступлениеНаСчет.Ссылка,
    ПоступлениеНаСчет.РасшифровкаПлатежа.(
        ПризнакАванса
    )
ИЗ
    Документ.ПоступлениеНаСчет КАК ПоступлениеНаСчет
ГДЕ
    ПоступлениеНаСчет.Проведен
    И ПоступлениеНаСчет.Дата МЕЖДУ &Дата1 И &Дата2
    И ПоступлениеНаСчет.РасшифровкаПлатежа.ПризнакАванса = ИСТИНА

Разберем логику работы платформы и СУБД:

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

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

Способ 1. Удаление ключевого слова РАЗЛИЧНЫЕ

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


ВЫБРАТЬ
    ПоступлениеНаСчет.Ссылка,
    ПоступлениеНаСчет.РасшифровкаПлатежа.(
        ПризнакАванса
    )
ИЗ
    Документ.ПоступлениеНаСчет КАК ПоступлениеНаСчет
ГДЕ
    ПоступлениеНаСчет.Проведен
    И ПоступлениеНаСчет.Дата МЕЖДУ &Дата1 И &Дата2
    И ПоступлениеНаСчет.РасшифровкаПлатежа.ПризнакАванса = ИСТИНА

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

Способ 2. Обращение к реальной таблице табличной части (рекомендуемый)

В реляционной модели данных 1С каждая табличная часть хранится как отдельная таблица базы данных. Вместо обращения к объекту документа и раскрытия вложенных таблиц правильнее сразу выбрать в качестве источника саму табличную часть: Документ.ПоступлениеНаСчет.РасшифровкаПлатежа.

Посмотрим на исправленный и оптимизированный вариант запроса:


ВЫБРАТЬ РАЗЛИЧНЫЕ
    ПоступлениеНаСчет.Ссылка КАК Ссылка,
    ПоступлениеНаСчет.ПризнакАванса КАК ПризнакАванса
ИЗ
    Документ.ПоступлениеНаСчет.РасшифровкаПлатежа КАК ПоступлениеНаСчет
ГДЕ
    ПоступлениеНаСчет.Ссылка.Проведен
    И ПоступлениеНаСчет.Ссылка.Дата МЕЖДУ &Дата1 И &Дата2
    И ПоступлениеНаСчет.ПризнакАванса

Разберем ключевые отличия и преимущества этого подхода:

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

Оптимизация: избавляемся от запросов в цикле

Рассмотрим вторую часть проблемы из обсуждения. Часто после получения списка документов разработчики запускают цикл со вложенными запросами для поиска связанных документов (например, сопоставления платежа и акта отгрузки). Выполнение запросов в цикле считается грубой ошибкой разработки в 1С, так как это многократно увеличивает количество обращений к СУБД, вызывает таймауты и блокировки таблиц.

Если перед нами стоит задача найти только те документы поступления, к которым подходит строго один акт (а если актов больше одного — пропустить документ), перенесем всю логику сопоставления в один пакетный запрос с использованием конструкции ИМЕЮЩИЕ.

Посмотрим на пример построения такого запроса:


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

Разберем, как работает этот алгоритм:

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

Обновление данных документа

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


Выборка = Запрос.Выполнить().Выбрать();
Пока Выборка.Следующий() Цикл
    
    ОбъектПоступление = Выборка.ПоступлениеСсылка.ПолучитьОбъект();
    Модифицирован = Ложь;
    
    Для Каждого СтрТЧ Из ОбъектПоступление.РасшифровкаПлатежа Цикл
        Если СтрТЧ.Документ <> Выборка.АктСсылка Или СтрТЧ.ПризнакАванса <> Ложь Тогда
            СтрТЧ.Документ = Выборка.АктСсылка;
            СтрТЧ.ПризнакАванса = Ложь;
            Модифицирован = Истина;
        КонецЕсли;
    КонецЦикла;
    
    // Проводим документ только в том случае, если реквизиты действительно изменились
    Если Модифицирован Тогда
        Попытка
            ОбъектПоступление.Записать(РежимЗаписиДокумента.Проведение);
        Исключение
            Сообщить("Ошибка проведения документа " + ОбъектПоступление.Номер + ": " + ОписаниеОшибки());
        КонецПопытки;
    КонецЕсли;
    
КонецЦикла;

Такой подход избавляет базу данных от тысяч лишних запросов, предотвращает взаимные блокировки данных (deadlocks) и решает задачу сопоставления документов в разы быстрее.

← На главную