Как программно изменить запрос динамического списка через Схему запроса и избежать дублирования записей

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

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

Анализ ошибки: Поле объекта не обнаружено (Тип)

Первая проблема, с которой столкнулся автор, — это ошибка при попытке обращения к свойству Источник. Проанализируем ситуацию: в стеке вызовов фигурирует цикл Пока КонечныйИсточник.Источник.Тип = Тип("ИсточникЗапросаСхемыЗапроса"). Ошибка "Поле объекта не обнаружено (Тип)" возникает из-за того, что структура СхемаЗапроса неоднородна.

Когда мы работаем с источниками запроса (коллекция Источники), элементами этой коллекции являются объекты типа ИсточникЗапросаСхемыЗапроса. У этого объекта действительно есть свойство Источник, но его содержимое может варьироваться. Если источником является реальная таблица базы данных, то свойство Источник содержит объект типа ТаблицаСхемыЗапроса. Если же это вложенный запрос, там будет совершенно другая структура.

Рассмотрим правильный подход к проверке типов источников. Вместо прямого обращения к свойству Тип (которого нет у многих объектов метаданных внутри схемы), следует использовать встроенную функцию ТипЗнч(). Также важно помнить, что в последних версиях платформы 1С свойства объектов схемы стали более строго типизированы. Если вы программно обходите дерево источников, всегда проверяйте, не является ли текущий элемент строкой или Неопределено, особенно если запрос был ранее модифицирован другим расширением.

Проблема дублирования ключевых значений в динамическом списке

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

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


ИЗ
    РегистрБухгалтерии.Хозрасчетный.Обороты(, , Регистратор, Счет В (&СчетаСОграничениями)) КАК ХозрасчетныйОборотыОграничения
        ЛЕВОЕ СОЕДИНЕНИЕ Документ.ОперацияБух КАК ДокументОперацияБух
        ПО ХозрасчетныйОборотыОграничения.Регистратор = ДокументОперацияБух.Ссылка

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

Решение проблемы через использование вложенных запросов и агрегации

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

Посмотрим на пример правильной логики модификации запроса:

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

Рассмотрим фрагмент кода, исправляющий ситуацию с дублями:


&НаСервере
Процедура ИсправитьЗапросСписка(Знач оСписок)
    СхемаЗапроса = Новый СхемаЗапроса();
    СхемаЗапроса.УстановитьТекстЗапроса(оСписок.ТекстЗапроса);
    
    Оператор = СхемаЗапроса.ПакетЗапросов[0].Операторы[0];
    
    // Вместо прямого добавления таблицы, добавим вложенный запрос для уникальности
    ВложенныйЗапрос = Оператор.Источники.Добавить("
    |ВЫБРАТЬ РАЗЛИЧНЫЕ
    |    Регистратор КАК Регистратор
    |ИЗ
    |    РегистрБухгалтерии.Хозрасчетный.Обороты(, , Регистратор, Счет В (&СчетаСОграничениями))", "ОборотыПроверка");
    
    ИсточникЛевый = Оператор.Источники.НайтиПоИмени("ДокументОперацияБух"); 
    
    // Устанавливаем соединение
    НовоеСоединение = ИсточникЛевый.Соединения.Добавить(ВложенныйЗапрос, "ДокументОперацияБух.Ссылка = ОборотыПроверка.Регистратор");
    НовоеСоединение.ТипСоединения = ТипСоединенияСхемыЗапроса.ЛевоеВнешнее;
    
    // Добавляем вычисляемое поле
    Выражение = "ВЫБОР КОГДА ОборотыПроверка.Регистратор ЕСТЬ NULL ТОГДА ЛОЖЬ ИНАЧЕ ИСТИНА КОНЕЦ";
    Оператор.ВыбираемыеПоля.Добавить(Выражение, 0);
    СхемаЗапроса.ПакетЗапросов[0].Колонки[0].Псевдоним = "ЕстьОграничения";
    
    оСписок.ТекстЗапроса = СхемаЗапроса.ПолучитьТекстЗапроса();
КонецПроцедуры

Оптимизация параметров виртуальных таблиц

При работе с СхемаЗапроса важно правильно обращаться к параметрам виртуальных таблиц. Проанализируем ошибку автора: ИсточникПравый.Источник.Параметры[3].Выражение. Обращение по индексу "3" крайне ненадежно. Если платформа обновится или структура регистра изменится (например, добавится новое измерение или изменится состав параметров виртуальной таблицы), ваш индекс станет указывать на другое поле или выйдет за границы массива.

Рекомендуется: находить нужный параметр по его смыслу. В объекте ПараметрыВиртуальнойТаблицыСхемыЗапроса каждый параметр соответствует определенному значению (ПериодНачала, ПериодОкончания, Условие и т.д.). Хотя программный интерфейс СхемаЗапроса предоставляет их в виде коллекции, всегда проверяйте актуальный текст запроса через ПолучитьТекстЗапроса() после изменений.

Также обратите внимание на условие Счет В (&СчетаСОграничениями). Использование параметров в тексте СхемаЗапроса требует их обязательной установки в самом динамическом списке. Выясним, как это сделать правильно:


Параметр = оСписок.Параметры.Элементы.Найти("СчетаСОграничениями");
Если Параметр <> Неопределено Тогда
    Параметр.Значение = ПолучитьНужныеСчета();
    Параметр.Использование = Истина;
КонецЕсли;

Подведение итогов

Проанализировав ситуацию, мы пришли к следующим выводам. При использовании СхемаЗапроса для модификации динамических списков:

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

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

← На главную