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