При разработке на платформе 1С:Предприятие 8.3 программисты часто сталкиваются с необходимостью динамического изменения текста запроса в списках документов или справочников. Одним из наиболее продвинутых способов реализации такой задачи является использование объекта СхемаЗапроса. Однако этот метод таит в себе неочевидные ловушки, одна из которых — ошибка «Обнаружено дублирование ключевых значений в колонках: Ссылка». Рассмотрим подробно, почему возникает эта ситуация, проанализируем механизмы работы динамического списка и найдем надежные способы решения проблемы.
Прежде всего, разберем архитектуру динамического списка. Когда у объекта ДинамическийСписок установлено свойство ОсновнаяТаблица, платформа берет на себя управление выборкой данных. Она ожидает, что каждая строка в результирующей таблице будет иметь уникальный идентификатор — Ссылка. Если в результате программного изменения запроса (через прямое редактирование текста или через СхемаЗапроса) к основной таблице присоединяются другие данные, может произойти «размножение» строк.
Проанализируем типичный пример. Допустим, мы хотим вывести в списке документов ОперацияБух признак того, что по данному документу есть обороты на определенных счетах. Для этого мы выполняем ЛЕВОЕ СОЕДИНЕНИЕ с виртуальной таблицей РегистрБухгалтерии.Хозрасчетный.Обороты по полю Регистратор.
В чем здесь кроется подвох? Виртуальная таблица оборотов может содержать несколько записей для одного и того же регистратора, если проводки документа затрагивают разные счета или имеют разные значения субконто. В итоге одна строка документа ОперацияБух соединяется с несколькими строками из таблицы Обороты. Платформа получает две и более строки с одинаковым значением в колонке Ссылка, что и приводит к фатальной ошибке отображения данных.
Рассмотрим фрагмент кода, который часто становится источником проблем при неправильном подходе:
&НаСервере
Процедура ИзменитьЗапросСписка(Знач оСписок)
Перем СхемаЗапроса, ОператорЗапроса, Источники, ИсточникПравый;
СхемаЗапроса = Новый СхемаЗапроса;
СхемаЗапроса.УстановитьТекстЗапроса(оСписок.ТекстЗапроса);
ОператорЗапроса = СхемаЗапроса.ПакетЗапросов[0].Операторы[0];
Источники = ОператорЗапроса.Источники;
// Добавление соединения с таблицей оборотов
ИсточникПравый = Источники.Добавить("РегистрБухгалтерии.Хозрасчетный.Обороты", "ХозрасчетныйОбороты");
ИсточникПравый.Источник.Параметры[3].Выражение = Новый ВыражениеСхемыЗапроса("Счет В (&Счета)");
ИсточникЛевый = Источники.НайтиПоИмени("Документ.ОперацияБух");
ИсточникПравый.Соединения.Добавить(ИсточникЛевый, "ХозрасчетныйОбороты.Регистратор = ДокументОперацияБух.Ссылка");
ИсточникПравый.Соединения[0].ТипСоединения = ТипСоединенияСхемыЗапроса.ЛевоеВнешнее;
// Добавление вычисляемого поля
ОператорЗапроса.ВыбираемыеПоля.Добавить("НЕ ХозрасчетныйОбороты.Счет ЕСТЬ NULL", 0);
СхемаЗапроса.ПакетЗапросов[0].Колонки[0].Псевдоним = "ЕстьОбороты";
оСписок.ТекстЗапроса = СхемаЗапроса.ПолучитьТекстЗапроса();
КонецПроцедуры
Посмотрим на результат выполнения этого кода. Если документ сделал три проводки по указанным счетам, запрос вернет три идентичные строки для этого документа. Динамический список не сможет отобразить такой результат, так как он не знает, какую именно строку считать основной для объекта.
Чтобы избежать дублирования, нам необходимо гарантировать, что из правой таблицы (в нашем случае — оборотов) для каждой ссылки левой таблицы придет ровно одна запись. Для этого следует использовать вложенный запрос с группировкой по полю связи.
Рассмотрим, как это реализовать. Вместо соединения напрямую с виртуальной таблицей, мы должны создать вложенный запрос, в котором выберем Регистратор и применим к остальным полям агрегатные функции или просто сгруппируем их. Однако СхемаЗапроса работает с источниками данных напрямую. Поэтому правильнее будет подготовить текст вложенного запроса заранее или использовать объект ВложенныйЗапросСхемыЗапроса.
Пошагово это выглядит так:
Регистратор из таблицы оборотов.СГРУППИРОВАТЬ ПО Регистратор.ЛЕВОЕ СОЕДИНЕНИЕ основной таблицы списка с этим вложенным запросом.Если наша задача — просто проверить наличие записей в другой таблице (например, наличие файлов или наличие определенных проводок), то соединение вообще не является оптимальным способом. Лучше использовать подзапрос в секции ВЫБРАТЬ.
Проанализируем пример выражения для колонки, которое не вызовет дублей:
ВЫБОР
КОГДА Ссылка В (ВЫБРАТЬ Т.Регистратор ИЗ РегистрБухгалтерии.Хозрасчетный.Обороты КАК Т ГДЕ Т.Счет В (&Счета))
ТОГДА ИСТИНА
ИНАЧЕ ЛОЖЬ
КОНЕЦ КАК ЕстьОбороты
При использовании СхемаЗапроса такое выражение добавляется в коллекцию ВыбираемыеПоля. Этот подход гарантирует, что количество строк в основном запросе останется неизменным, так как подзапрос возвращает скалярное значение для каждой строки основной таблицы.
В некоторых типовых конфигурациях ошибка возникает при попытке соединиться с регистром сведений НаличиеФайлов. Существует миф, что в этом регистре может быть несколько записей на один объект, что и вызывает дубли. На самом деле, в архитектуре БСП (Библиотеки Стандартных Подсистем) этот регистр спроектирован так, что измерение Объект является уникальным.
Если вы столкнулись с дублями при соединении с этим регистром, проверьте следующие моменты:
Объект, а не по каким-либо дополнительным полям, которые могут содержать разные значения (например, идентификаторы узлов обмена).КОЛИЧЕСТВО(*) > 1. Если такие записи найдены, значит, база данных повреждена или нарушена логика записи в регистр.Чтобы избежать ошибок с ключевыми полями, придерживайтесь следующих правил:
СхемаЗапроса платформа иногда пытается автоматически добавить поля и соединения, основываясь на метаданных. Всегда проверяйте итоговый текст запроса в отладчике.Список.ТекстЗапроса) в консоль запросов и выполните его. Вы сразу увидите строки-дубли и сможете понять, из-за какой присоединенной таблицы они появились.Ссылка или Регистратор).Таким образом, проблема дублирования ключевых значений — это всегда следствие некорректного проектирования связей в запросе. Использование СхемаЗапроса требует от разработчика понимания того, как будет выглядеть финальный SQL-текст, который платформа отправит на сервер СУБД. Следуя принципу «одна строка источника — одна строка результата», вы навсегда избавитесь от ошибок в динамических списках.