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

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

При разработке управляемых форм в 1С часто возникает задача: необходимо в списке документов (например, "Заказы клиентов" или "Реализации") вывести информацию, которая содержится не в шапке документа, а в его табличной части. Классический пример — поле "Проект", "Склад" или "Номенклатура", которые указываются построчно.

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

Суть проблемы: почему появляются дубли?

Давайте проанализируем, как работает запрос динамического списка. Когда мы пишем простой запрос с соединением, платформа выполняет стандартную операцию SQL LEFT JOIN. Если у одного документа в табличной части 5 строк, то в результирующей выборке этот документ повторится 5 раз.

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

Решение 1: Использование агрегатных функций и вложенных запросов

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

Рассмотрим пример запроса, который выбирает "максимальный" проект (это позволяет избавиться от дублей, так как для одного документа будет возвращено только одно значение).

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


ВЫБРАТЬ
	Док.Ссылка,
	Док.Номер,
	Док.Дата,
	Док.Контрагент,
	ВложенныйЗапрос.Проект КАК Проект
ИЗ
	Документ.ЗаказКлиента КАК Док
		ЛЕВОЕ СОЕДИНЕНИЕ (
			ВЫБРАТЬ
				ТЧ.Ссылка КАК Ссылка,
				МАКСИМУМ(ТЧ.Проект) КАК Проект
			ИЗ
				Документ.ЗаказКлиента.Товары КАК ТЧ
			СГРУППИРОВАТЬ ПО
				ТЧ.Ссылка
		) КАК ВложенныйЗапрос
		ПО Док.Ссылка = ВложенныйЗапрос.Ссылка

Разберем по шагам, что мы сделали:

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

Важное примечание: Динамические списки в стандартном режиме не поддерживают использование временных таблиц (конструкция ПОМЕСТИТЬ). Поэтому всю логику нужно реализовывать именно через вложенные подзапросы.

Решение 2: Получение значения из последней строки

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

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


ВЫБРАТЬ
	Док.Ссылка,
	Док.Дата,
	ТЧ_Данные.Проект
ИЗ
	Документ.ЗаказКлиента КАК Док
		// Присоединяем данные конкретной строки
		ЛЕВОЕ СОЕДИНЕНИЕ Документ.ЗаказКлиента.Товары КАК ТЧ_Данные
		ПО Док.Ссылка = ТЧ_Данные.Ссылка
		И ТЧ_Данные.НомерСтроки В (
			// Подзапрос для поиска номера последней строки
			ВЫБРАТЬ
				МАКСИМУМ(ТЧ.НомерСтроки)
			ИЗ
				Документ.ЗаказКлиента.Товары КАК ТЧ
			ГДЕ
				ТЧ.Ссылка = Док.Ссылка
		)

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

Решение 3: Программный вывод (Событие ПриПолученииДанныхНаСервере)

Бывают ситуации, когда запросом решить задачу невозможно. Например, заказчик хочет видеть в одной ячейке список всех проектов через запятую: "Проект А, Проект Б, Проект В". Язык запросов 1С не имеет функции объединения строк (аналога GROUP_CONCAT в SQL).

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

Правильный алгоритм действий:

  1. В процедуре получаем массив ссылок всех отображаемых в данный момент строк.
  2. Делаем один запрос к базе данных с отбором по этому массиву ссылок.
  3. Выгружаем результат запроса в таблицу значений или соответствие.
  4. Обходим оформляемые строки и заполняем данные из полученной коллекции.

Рассмотрим пример кода:


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

Архитектурный подход (Денормализация)

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

Суть метода заключается в добавлении реквизита "ОсновнойПроект" или "ПроектыСтрокой" непосредственно в шапку документа (или в отдельный регистр сведений). Этот реквизит заполняется программно в момент записи документа (событие ПередЗаписью). Тогда в динамическом списке вы просто выводите готовое поле шапки. Это работает мгновенно и не требует сложных вычислений при открытии списка.

Частые ошибки при настройке

При настройке динамического списка обратите внимание на следующие нюансы, которые могут привести к тому, что колонка останется пустой:

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

← На главную