Как добавить колонку с ценой в динамический список формы выбора номенклатуры без дублирования записей?

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

При доработке формы выбора справочника Номенклатура часто возникает задача вывести актуальную цену товара отдельной колонкой. Разработчики редактируют запрос динамического списка, подключая виртуальную таблицу среза последних регистра сведений ЦеныНоменклатуры, однако сталкиваются с ошибкой: «Обнаружено дублирование ключевых значений в колонках: Ссылка. Отображение данных в списках невозможно», либо с исчезновением позиций, у которых цена не задана. Разберем по шагам, почему возникает эта ошибка, как правильно составить запрос и как оптимизировать быстродействие формы.

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

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

  1. Неправильный порядок или направление соединения: если основной таблицей запроса сделать регистр или применить некорректное соединение, на одну номенклатуру может выпасть несколько записей из регистра.
  2. Наличие дополнительных измерений в регистре цен: регистр сведений ЦеныНоменклатуры содержит не только измерение Номенклатура, но также ВидЦены, Характеристика (и в ряде конфигураций — Упаковка). Если срез последних не ограничен конкретным видом цен и пустой характеристикой, запрос вернет столько строк на одну позицию номенклатуры, сколько цен разного вида или разных характеристик для нее зарегистрировано.

Исправление структуры соединения и отборов в запросе

Рассмотрим типичные ошибки в тексте запроса и разберем, как их исправить:

  1. Базовой таблицей должен оставаться справочник: соединение необходимо строить от таблицы Справочник.Номенклатура к срезу последних, используя ЛЕВОЕ СОЕДИНЕНИЕ. Это гарантирует, что товары без цен останутся в списке.
  2. Перенос отборов из секции ГДЕ в параметры виртуальной таблицы: если наложить условие по виду цены в блоке ГДЕ, левое соединение превратится во внутреннее — все позиции без установленной цены будут отсечены из выборки. Условие по виду цены обязательно нужно помещать в параметры самой виртуальной таблицы СрезПоследних.
  3. Отказ от обращений через точку к наименованию: сравнение вида цены по строке ВидЦены.Наименование = "..." снижает производительность и нарушает работу при переименовании элементов. Передавать отбор следует через параметр со ссылкой на элемент справочника ВидыЦен.
  4. Устранение лишних измерений: если характеристики в данной форме выбора не отображаются, необходимо явно ограничить срез пустой ссылкой на характеристики, чтобы избежать задвоения строк.

Пошаговое построение корректного запроса для динамического списка

Проанализируем пример правильного текста запроса для динамического списка формы выбора:


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

Разберем подробнее ключевые моменты представленного запроса:

  1. В параметрах виртуальной таблицы указано условие ВидЦены = &ВидЦены. Это позволяет СУБД задействовать кластерный индекс регистра, начинающийся с поля ВидЦены.
  2. Условие Характеристика = ЗНАЧЕНИЕ(...) отсекает дополнительные строки регистра по характеристикам, что исключает дублирование записей с одинаковой ссылкой номенклатуры.
  3. Связь соединяет напрямую поля ссылочного типа: ЦеныНоменклатурыСрезПоследних.Номенклатура = СпрНоменклатура.Ссылка без лишних обращений через точку.

Параметры динамического списка необходимо заполнить в модуле формы (например, в процедуре ПриСозданииНаСервере):


&НаСервере
Процедура ПриСозданииНаСервере(Отказ, СтандартнаяОбработка)
	
	// Устанавливаем текущую дату для среза цен
	Список.Параметры.УстановитьЗначениеПараметра("Период", ТекущаяДатаСеанса());
	
	// Получаем ссылку на нужный вид цены (из настроек, константы или реквизита документа)
	Список.Параметры.УстановитьЗначениеПараметра("ВидЦены", ПредопределенноеЗначение("Справочник.ВидыЦен.Розничная"));
	
КонецПроцедуры

Оптимизация быстродействия: альтернатива через ПриПолученииДанныхНаСервере

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

Разберем по шагам реализацию через обработчик ПриПолученииДанныхНаСервере:

  1. Удалим соединение с регистром цен из запроса динамического списка, оставив запрос простым и быстрым.
  2. Добавим в таблицу формы новую колонку Цена (без привязки к реквизиту данных динамического списка).
  3. В обработчике таблицы формы СписокПриПолученииДанныхНаСервере получим цены исключительно для тех строк, которые физически отображаются на экране пользователя в текущий момент:

&НаСервере
Процедура СписокПриПолученииДанныхНаСервере(ИмяЭлемента, Строки)
	
	МассивНоменклатуры = Новый Массив;
	Для Каждого КлючСтроки Из Строки.ПолучитьКлючи() Цикл
		МассивНоменклатуры.Добавить(КлючСтроки);
	КонецЦикла;
	
	Если МассивНоменклатуры.Количество() = 0 Тогда
		Возврат;
	КонецЕсли;
	
	Запрос = Новый Запрос;
	Запрос.Текст = 
		"ВЫБРАТЬ
		|	Цены.Номенклатура КАК Номенклатура,
		|	Цены.Цена КАК Цена
		|ИЗ
		|	РегистрСведений.ЦеныНоменклатуры.СрезПоследних(
		|		&Период,
		|		Номенклатура В (&МассивНоменклатуры)
		|			И ВидЦены = &ВидЦены
		|			И Характеристика = ЗНАЧЕНИЕ(Справочник.ХарактеристикиНоменклатуры.ПустаяСсылка)) КАК Цены";
		
	Запрос.УстановитьПараметр("Период", ТекущаяДатаСеанса());
	Запрос.УстановитьПараметр("ВидЦены", ВидЦеныДляПодбора);
	Запрос.УстановитьПараметр("МассивНоменклатуры", МассивНоменклатуры);
	
	Выборка = Запрос.Выполнить().Выбрать();
	Пока Выборка.Следующий() Цикл
		СтрокаОформления = Строки[Выборка.Номенклатура];
		Если СтрокаОформления <> Неопределено Тогда
			СтрокаОформления.Ячейки.Цена.УстановитьТекст(Формат(Выборка.Цена, "ЧДЦ=2"));
		КонецЕсли;
	КонецЦикла;
	
КонецПроцедуры

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

← На главную