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