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

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

При переходе на платформу 1С разработчики с опытом работы в классических СУБД (MS SQL Server, Oracle, PostgreSQL) часто сталкиваются с ограничением: в языке запросов 1С нельзя написать вложенный подзапрос непосредственно в секции ВЫБРАТЬ (скалярный подзапрос вида SELECT A.Поле, (SELECT TOP 1 B.Значение FROM B WHERE B.ID = A.ID ORDER BY B.Дата) FROM A). Разберем причины этого ограничения, выясним, как обойти его в 1С с помощью условий соединения, и рассмотрим альтернативные варианты решения задачи.

Ограничения языка запросов 1С

Проанализируем особенности синтаксиса платформы:

  1. Отсутствие подзапросов в списке полей выборки: Язык запросов 1С требует, чтобы поля в секции ВЫБРАТЬ формировались исключительно из источников данных, выражений, встроенных функций или агрегатов. Вставить запрос в круглых скобках в список полей невозможно.
  2. Отсутствие операторов CROSS APPLY / OUTER APPLY: Платформа не предоставляет прямой синтаксической конструкции для построчной передачи параметров во внешнюю табличную функцию или сопоставленный подзапрос.
  3. Ограничения корреляции: Коррелированные подзапросы (обращающиеся к полям вышестоящей таблицы) запрещены в секции ГДЕ, однако синтаксис секции ПО (в условиях соединений) позволяет связывать поля основной таблицы с вложенным запросом.

Решение 1: Коррелированный подзапрос с конструкцией ПЕРВЫЕ 1 в условии соединения ПО

Рассмотрим прием, позволяющий сымитировать логику TOP 1 с сортировкой непосредственно в одном запросе без предварительного создания временных таблиц. Мы помещаем подзапрос с конструкцией ПЕРВЫЕ 1 и оператором УПОРЯДОЧИТЬ ПО прямо в условие связи ПО оператора ЛЕВОЕ СОЕДИНЕНИЕ.

Посмотрим на пример реализации:


ВЫБРАТЬ
	Основная.Ссылка КАК Ссылка,
	Основная.Период КАК Период,
	ТабЗначений.Значение КАК Значение
ИЗ
	Документ.РеализацияТоваровУслуг КАК Основная
		ЛЕВОЕ СОЕДИНЕНИЕ РегистрСведений.КурсыВалют КАК ТабЗначений
		ПО ТабЗначений.Период В
			(ВЫБРАТЬ ПЕРВЫЕ 1
				ТабОтбор.Период
			ИЗ
				РегистрСведений.КурсыВалют КАК ТабОтбор
			ГДЕ
				ТабОтбор.Валюта = Основная.ДоговорКонтрагента.ВалютаВзаиморасчетов
				И ТабОтбор.Период <= Основная.Период
			УПОРЯДОЧИТЬ ПО
				ТабОтбор.Период УБЫВ)

Разберем по шагам, как работает данный запрос:

  1. Для каждой строки таблицы Основная платформа генерирует условие отбора во вложенной выборке ТабОтбор.
  2. Подзапрос фильтрует данные по ключу связи (Валюта) и граничному условию (Период).
  3. Конструкция УПОРЯДОЧИТЬ ПО совместно с ПЕРВЫЕ 1 оставляет ровно одну строку с нужным экстремумом.
  4. Основная таблица соединяется с ТабЗначений только по полученному ключевому значению, гарантируя отсутствие дублирования строк.

Важное предостережение по производительности: На уровне СУБД такое условие транслируется во вложенный цикл (Nested Loops). Если по соединяемым полям и полям сортировки нет точного покрывающего индекса, СУБД будет выполнять сканирование таблицы для каждой строки левой таблицы. На больших выборках это может вызвать сильные задержки.

Решение 2: Классическая двухэтапная схема через временные таблицы (Стандарт 1С)

Если требуется обработать большой объем данных на высоконагруженной системе, правильнее использовать стандартный шаблон с агрегацией и индексированными временными таблицами. Это позволяет СУБД применить быстрые алгоритмы соединения (Hash Match или Merge Join).

Посмотрим на пример с пакетом запросов:


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

ИНДЕКСИРОВАТЬ ПО
	Валюта,
	ПериодКурса
;

////////////////////////////////////////////////////////////////////////////////
ВЫБРАТЬ
	ВТ_Срезы.Документ КАК Документ,
	КурсыВалют.Курс КАК Курс
ИЗ
	ВТ_Срезы КАК ВТ_Срезы
		ЛЕВОЕ СОЕДИНЕНИЕ РегистрСведений.КурсыВалют КАК КурсыВалют
		ПО ВТ_Срезы.Валюта = КурсыВалют.Валюта
			И ВТ_Срезы.ПериодКурса = КурсыВалют.Период

Преимущества этого подхода:

  1. Мы явно управляем индексацией промежуточных данных с помощью директивы ИНДЕКСИРОВАТЬ ПО.
  2. Оптимизатор СУБД строит предсказуемый и эффективный план выполнения.
  3. Код полностью соответствует стандартам разработки фирмы «1С».

Решение 3: Использование связей наборов данных в СКД

Если задача решается в рамках Системы компоновки данных (СКД), можно обойтись без сложных условий соединения в самом тексте запроса:

  1. Создаем два независимых набора данных: НаборДанных1 (основная таблица) и НаборДанных2 (подчиненная таблица).
  2. Во втором наборе пишем запрос с параметром и ограничением: ВЫБРАТЬ ПЕРВЫЕ 1 Поле Из Таблица ГДЕ Ключ = &ПараметрКлюча УПОРЯДОЧИТЬ ПО Дата УБЫВ.
  3. На вкладке «Связи наборов данных» связываем первый набор со вторым, передавая значение поля текущей строки в качестве параметра &ПараметрКлюча.
  4. СКД самостоятельно оптимизирует пакетную передачу параметров при формировании отчета.
← На главную