В разработке на платформе 1С:Предприятие 8.3 часто возникает задача вывода данных в динамический список, где одна запись основной таблицы (например, Справочник.Запросы) связана с несколькими записями подчиненной таблицы (например, Документ.ТелефонныйЗвонок). Если просто использовать ЛЕВОЕ СОЕДИНЕНИЕ, в списке возникнут дубли: одна и та же заявка повторится столько раз, сколько звонков по ней было совершено. Ситуация осложняется, когда нам нужно показать не просто «любой» звонок, а конкретный, исходя из его статуса (например, сначала необработанные, а если таких нет — любые другие).
В данной статье мы разберем, как правильно построить запрос динамического списка, чтобы избежать дублирования строк и обеспечить корректный выбор данных по заданному приоритету. Мы рассмотрим использование временных таблиц, вложенных запросов и механизмов группировки для достижения этой цели.
Рассмотрим ситуацию: у нас есть объект «Запрос», к которому привязано два телефонных звонка. Один звонок уже завершен (флаг Рассмотрено = ИСТИНА), а второй еще в работе (Рассмотрено = ЛОЖЬ). Мы хотим, чтобы в списке отображался только тот звонок, который не завершен. Если же все звонки завершены, должен отображаться любой из них (но только один!).
Основная ошибка новичков заключается в попытке использовать простую группировку в одном запросе с агрегатными функциями МАКСИМУМ или МИНИМУМ. Однако, если у вас несколько полей, которые нужно вытащить из звонка (дата, ссылка, ответственный), группировка по всем этим полям все равно вернет несколько строк, так как значения в них различаются.
Для начала нам необходимо перевести логическое состояние (рассмотрено или нет) в числовой вид, чтобы система могла «сравнить» записи. Договоримся, что приоритет «2» будет у невыполненных звонков, а «1» — у выполненных. Таким образом, функция МАКСИМУМ(Приоритет) вернет нам именно тот статус, который важнее для отображения.
Проанализируем фрагмент кода для формирования временной таблицы с данными:
ВЫБРАТЬ РАЗРЕШЕННЫЕ
СправочникЗапросы.Ссылка КАК Ссылка,
ТелефонныйЗвонок.Ссылка КАК СсылкаДокумент,
ВЫБОР
КОГДА ПредметыПапкиВзаимодействий.Рассмотрено = ЛОЖЬ ТОГДА 2
ИНАЧЕ 1
КОНЕЦ КАК Приоритет
ПОМЕСТИТЬ ВТ_Данные
ИЗ
Справочник.Запросы КАК СправочникЗапросы
ЛЕВОЕ СОЕДИНЕНИЕ Документ.ТелефонныйЗвонок КАК ТелефонныйЗвонок
ПО СправочникЗапросы.Ссылка = ТелефонныйЗвонок.ВзаимодействиеОснование
ЛЕВОЕ СОЕДИНЕНИЕ РегистрСведений.ПредметыПапкиВзаимодействий КАК ПредметыПапкиВзаимодействий
ПО ТелефонныйЗвонок.Ссылка = ПредметыПапкиВзаимодействий.Взаимодействие
Теперь, когда у нас есть «сырые» данные во временной таблице ВТ_Данные, нам нужно понять для каждого Запроса, какой максимальный приоритет среди его звонков существует. Это позволит нам отсеять «неважные» звонки на следующем этапе.
ВЫБРАТЬ
ВТ_Данные.Ссылка КАК Ссылка,
МАКСИМУМ(ВТ_Данные.Приоритет) КАК МаксПриоритет
ПОМЕСТИТЬ ВТ_Приоритеты
ИЗ
ВТ_Данные КАК ВТ_Данные
СГРУППИРОВАТЬ ПО
ВТ_Данные.Ссылка
Важно помнить: использование временных таблиц в динамических списках разрешено в современных версиях платформы (начиная с 8.3.12). Однако не забудьте указать в свойствах динамического списка «Основную таблицу», иначе функции поиска и сортировки могут работать некорректно.
Часто случается ситуация, когда у одного запроса есть два звонка с одинаковым приоритетом (например, оба не рассмотрены). В этом случае соединение по полю Приоритет снова выдаст две строки. Чтобы этого избежать, нам нужно выбрать ровно один уникальный идентификатор звонка для каждого запроса.
Рассмотрим, как это реализовать, выбрав звонок с максимальной ссылкой (или максимальной датой):
ВЫБРАТЬ
ВТ_Приоритеты.Ссылка КАК Ссылка,
МАКСИМУМ(ВТ_Данные.СсылкаДокумент) КАК ИтоговаяСсылкаДокумента
ПОМЕСТИТЬ ВТ_ИтоговыеСсылки
ИЗ
ВТ_Приоритеты КАК ВТ_Приоритеты
ВНУТРЕННЕЕ СОЕДИНЕНИЕ ВТ_Данные КАК ВТ_Данные
ПО ВТ_Приоритеты.Ссылка = ВТ_Данные.Ссылка
И ВТ_Приоритеты.МаксПриоритет = ВТ_Данные.Приоритет
СГРУППИРОВАТЬ ПО
ВТ_Приоритеты.Ссылка
Здесь мы используем ВНУТРЕННЕЕ СОЕДИНЕНИЕ, чтобы оставить только те звонки, которые соответствуют нашему лучшему приоритету, и с помощью МАКСИМУМ(СсылкаДокумент) гарантируем уникальность строки.
На финальном этапе мы соединяем нашу основную таблицу запросов с полученными результатами. Особое внимание уделим тому, чтобы не потерять те запросы, по которым звонков вообще не было. Для этого используем ЛЕВОЕ СОЕДИНЕНИЕ.
Проанализируем итоговую конструкцию:
ВЫБРАТЬ
СправочникЗапросы.Ссылка КАК Ссылка,
СправочникЗапросы.Наименование,
СправочникЗапросы.ДатаСоздания,
ВТ_ИтоговыеСсылки.ИтоговаяСсылкаДокумента КАК СсылкаДокумент,
ЕСТЬNULL(ВТ_Данные.Приоритет, 0) КАК ТекущийПриоритет
ИЗ
Справочник.Запросы КАК СправочникЗапросы
ЛЕВОЕ СОЕДИНЕНИЕ ВТ_ИтоговыеСсылки КАК ВТ_ИтоговыеСсылки
ПО СправочникЗапросы.Ссылка = ВТ_ИтоговыеСсылки.Ссылка
ЛЕВОЕ СОЕДИНЕНИЕ ВТ_Данные КАК ВТ_Данные
ПО ВТ_ИтоговыеСсылки.ИтоговаяСсылкаДокумента = ВТ_Данные.СсылкаДокумент
ЛЕВОЕ СОЕДИНЕНИЕ всегда используйте функцию ЕСТЬNULL для полей, участвующих в логике. Например, приоритет 0 для объектов без связанных документов позволит корректно выстраивать условия.Наименование во временных таблицах. Группируйте только по Ссылке, а текстовые данные присоединяйте в самом конце. Это значительно ускорит работу SQL-сервера.Справочник.Запросы в качестве основной таблицы. Это позволит платформе автоматически генерировать оптимальные запросы для постраничного вывода (использование конструкций TOP в SQL).Взаимодействие в регистре сведений), обязательно используйте ВЫРАЗИТЬ(Поле КАК Тип). Это исключит лишние соединения со всеми возможными таблицами, входящими в составной тип, и предотвратит замедление списка.Таким образом, применяя методику двойной группировки (сначала по приоритету, затем по уникальному идентификатору записи), мы получаем чистый, быстрый и корректно работающий динамический список без дублей, отвечающий всем требованиям бизнес-логики.