При разработке отчетов и обработок в платформе 1С:Предприятие разработчики часто сталкиваются с ошибкой времени выполнения:
«В запросе с предложениями РАЗЛИЧНЫЕ или ОБЪЕДИНИТЬ нельзя использовать вложенные таблицы»
Выясним причину возникновения этой ошибки, разберем способы её правильного устранения и рассмотрим, как переписать связанный код без использования ресурсоемких запросов внутри циклов.
Проанализируем исходный фрагмент запроса, который привел к сбою:
ВЫБРАТЬ РАЗЛИЧНЫЕ
ПоступлениеНаСчет.Ссылка,
ПоступлениеНаСчет.РасшифровкаПлатежа.(
ПризнакАванса
)
ИЗ
Документ.ПоступлениеНаСчет КАК ПоступлениеНаСчет
ГДЕ
ПоступлениеНаСчет.Проведен
И ПоступлениеНаСчет.Дата МЕЖДУ &Дата1 И &Дата2
И ПоступлениеНаСчет.РасшифровкаПлатежа.ПризнакАванса = ИСТИНА
Разберем логику работы платформы и СУБД:
ПоступлениеНаСчет.РасшифровкаПлатежа.(...) формирует так называемую вложенную таблицу. В результате платформа 1С возвращает выборку, где в одной из колонок находится подчиненный объект выборки (строки табличной части).РАЗЛИЧНЫЕ (эквивалент DISTINCT в SQL) заставляет СУБД выполнить операцию исключения дубликатов по всем полям результирующей выборки.ОБЪЕДИНИТЬ (или ОБЪЕДИНИТЬ ВСЕ). Платформа 1С накладывает на такое сочетание строгий синтаксический запрет.Кроме того, обратим внимание на фильтр в секции ГДЕ. Условие ПоступлениеНаСчет.РасшифровкаПлатежа.ПризнакАванса = ИСТИНА проверяет только факт наличия хотя бы одной строки с авансом в документе, но во вложенную таблицу РасшифровкаПлатежа.(...) попадут все строки документа, включая те, где признак аванса равен ЛОЖЬ.
Если структура с вложенной таблицей принципиально необходима для последующей иерархической обработки через метод выборки Выбрать(ОбходРезультатаЗапроса.ПоГруппировкамСИерархией), самым быстрым решением является отказ от ключевого слова РАЗЛИЧНЫЕ.
ВЫБРАТЬ
ПоступлениеНаСчет.Ссылка,
ПоступлениеНаСчет.РасшифровкаПлатежа.(
ПризнакАванса
)
ИЗ
Документ.ПоступлениеНаСчет КАК ПоступлениеНаСчет
ГДЕ
ПоступлениеНаСчет.Проведен
И ПоступлениеНаСчет.Дата МЕЖДУ &Дата1 И &Дата2
И ПоступлениеНаСчет.РасшифровкаПлатежа.ПризнакАванса = ИСТИНА
Однако в большинстве прикладных задач вложенные таблицы не требуются, так как они усложняют чтение выборки и снижают производительность системы.
В реляционной модели данных 1С каждая табличная часть хранится как отдельная таблица базы данных. Вместо обращения к объекту документа и раскрытия вложенных таблиц правильнее сразу выбрать в качестве источника саму табличную часть: Документ.ПоступлениеНаСчет.РасшифровкаПлатежа.
Посмотрим на исправленный и оптимизированный вариант запроса:
ВЫБРАТЬ РАЗЛИЧНЫЕ
ПоступлениеНаСчет.Ссылка КАК Ссылка,
ПоступлениеНаСчет.ПризнакАванса КАК ПризнакАванса
ИЗ
Документ.ПоступлениеНаСчет.РасшифровкаПлатежа КАК ПоступлениеНаСчет
ГДЕ
ПоступлениеНаСчет.Ссылка.Проведен
И ПоступлениеНаСчет.Ссылка.Дата МЕЖДУ &Дата1 И &Дата2
И ПоступлениеНаСчет.ПризнакАванса
Разберем ключевые отличия и преимущества этого подхода:
Выгрузить() в объект ТаблицаЗначений или стандартный обход Следующий().РАЗЛИЧНЫЕ теперь сравнивает простые примитивные типы (ссылку и булево значение), поэтому СУБД отрабатывает запрос без ошибок.ПризнакАванса действительно равен ИСТИНА.Проведен или Дата) осуществляется через предопределенное поле Ссылка.Рассмотрим вторую часть проблемы из обсуждения. Часто после получения списка документов разработчики запускают цикл со вложенными запросами для поиска связанных документов (например, сопоставления платежа и акта отгрузки). Выполнение запросов в цикле считается грубой ошибкой разработки в 1С, так как это многократно увеличивает количество обращений к СУБД, вызывает таймауты и блокировки таблиц.
Если перед нами стоит задача найти только те документы поступления, к которым подходит строго один акт (а если актов больше одного — пропустить документ), перенесем всю логику сопоставления в один пакетный запрос с использованием конструкции ИМЕЮЩИЕ.
Посмотрим на пример построения такого запроса:
ВЫБРАТЬ
Поступление.Ссылка КАК ПоступлениеСсылка,
МАКСИМУМ(Акт.Ссылка) КАК АктСсылка
ИЗ
Документ.ПоступлениеНаСчет КАК Поступление
ВНУТРЕННЕЕ СОЕДИНЕНИЕ Документ.АктВыполненныхРабот КАК Акт
ПО Поступление.Контрагент = Акт.Контрагент
И Поступление.СуммаДокумента = Акт.СуммаДокумента
И Акт.Проведен
И Акт.Дата МЕЖДУ &Дата1 И &Дата2
// Исключаем акты, которые уже привязаны к другим поступлениям
ЛЕВОЕ СОЕДИНЕНИЕ Документ.ПоступлениеНаСчет.РасшифровкаПлатежа КАК ДругиеПлатежи
ПО Акт.Ссылка = ДругиеПлатежи.Документ
И ДругиеПлатежи.Ссылка.Проведен
ГДЕ
Поступление.Проведен
И Поступление.Дата МЕЖДУ &Дата1 И &Дата2
И ДругиеПлатежи.Ссылка ЕСТЬ NULL
СГРУППИРОВАТЬ ПО
Поступление.Ссылка
ИМЕЮЩИЕ
КОЛИЧЕСТВО(РАЗЛИЧНЫЕ Акт.Ссылка) = 1
Разберем, как работает этот алгоритм:
ЛЕВОЕ СОЕДИНЕНИЕ с табличной частью других платежей и условие ДругиеПлатежи.Ссылка ЕСТЬ NULL (анти-соединение) на уровне запроса отсекают акты, которые уже были закрыты другими документами поступления.СГРУППИРОВАТЬ ПО Поступление.Ссылка объединяет результаты для каждого документа.ИМЕЮЩИЕ КОЛИЧЕСТВО(РАЗЛИЧНЫЕ Акт.Ссылка) = 1 фильтрует выборку: документы, к которым не нашлось подходящих актов (0) или подошло несколько неоднозначных актов (2 и более), автоматически отбрасываются.МАКСИМУМ(Акт.Ссылка) возвращает ссылку на единственный найденный документ.После выполнения оптимизированного запроса остается только обойти результат и применить изменения к документам, минимизируя время блокировок:
Выборка = Запрос.Выполнить().Выбрать();
Пока Выборка.Следующий() Цикл
ОбъектПоступление = Выборка.ПоступлениеСсылка.ПолучитьОбъект();
Модифицирован = Ложь;
Для Каждого СтрТЧ Из ОбъектПоступление.РасшифровкаПлатежа Цикл
Если СтрТЧ.Документ <> Выборка.АктСсылка Или СтрТЧ.ПризнакАванса <> Ложь Тогда
СтрТЧ.Документ = Выборка.АктСсылка;
СтрТЧ.ПризнакАванса = Ложь;
Модифицирован = Истина;
КонецЕсли;
КонецЦикла;
// Проводим документ только в том случае, если реквизиты действительно изменились
Если Модифицирован Тогда
Попытка
ОбъектПоступление.Записать(РежимЗаписиДокумента.Проведение);
Исключение
Сообщить("Ошибка проведения документа " + ОбъектПоступление.Номер + ": " + ОписаниеОшибки());
КонецПопытки;
КонецЕсли;
КонецЦикла;
Такой подход избавляет базу данных от тысяч лишних запросов, предотвращает взаимные блокировки данных (deadlocks) и решает задачу сопоставления документов в разы быстрее.