В процессе разработки на платформе 1С:Предприятие программисты часто сталкиваются с ситуацией, когда при соединении двух таблиц в запросе возникает "размножение" строк. Это происходит, если одному ключу связи в первой таблице соответствует несколько записей во второй. Классический пример: нам нужно получить остаток товара и только одну (например, самую свежую или самую крупную) цену из регистра цен. Если просто использовать ЛЕВОЕ СОЕДИНЕНИЕ, в результат попадут все найденные цены, и количество строк исходной таблицы увеличится.
В этой статье мы подробно разберем, как ограничить выборку из второй таблицы таким образом, чтобы сопоставлялась только одна запись, отвечающая заданным критериям.
Проанализируем наиболее простой и распространенный метод, который подходит для большинства версий платформы. Если критерием "первой" записи является экстремальное значение (максимальная дата, минимальная цена, наибольшее числовое поле), мы можем заранее подготовить вторую таблицу с помощью группировки.
Рассмотрим ситуацию: у нас есть Таблица1 со списком номенклатуры и Таблица2 с ценами. Для каждой позиции номенклатуры нам нужно вытащить только максимальную цену.
Выполним следующие шаги:
МАКСИМУМ к полю, которое нам нужно получить.Посмотрим на пример кода запроса:
ВЫБРАТЬ
Таб1.Поле1 КАК Поле1,
ВложенныйЗапрос.ПолеТаб2 КАК МаксимальноеЗначение
ИЗ
Таб1 КАК Таб1
ЛЕВОЕ СОЕДИНЕНИЕ (ВЫБРАТЬ
Таб2.Поле1 КАК Поле1,
МАКСИМУМ(Таб2.ПолеТаб2) КАК ПолеТаб2
ИЗ
Таб2 КАК Таб2
СГРУППИРОВАТЬ ПО
Таб2.Поле1) КАК ВложенныйЗапрос
ПО Таб1.Поле1 = ВложенныйЗапрос.Поле1
Этот метод эффективен, когда нам нужно получить только одно числовое или датированное значение. Однако у него есть ограничение: если из второй таблицы нужно вытащить несколько полей (например, и цену, и ответственного, который ее установил), простая группировка не позволит это сделать без потери связи между данными.
Современные версии платформы 1С 8.3.20+ предоставляют мощный инструмент — оконные функции. Они позволяют пронумеровать строки внутри каждой группы данных прямо в запросе. Это идеальное решение задачи "первой записи".
Разберем алгоритм решения через ПОРЯДКОВЫЙ_НОМЕР():
ПЕРЕГОРОДКА ПО (PARTITION BY) для группировки по ключу связи.УПОРЯДОЧИТЬ ПО для определения того, какая запись будет считаться первой (например, по убыванию даты).Проанализируем структуру такого запроса:
ВЫБРАТЬ
Т1.Ссылка,
Т2.Цена,
Т2.Сотрудник
ИЗ
Справочник.Номенклатура КАК Т1
ЛЕВОЕ СОЕДИНЕНИЕ (
ВЫБРАТЬ
Цены.Номенклатура,
Цены.Цена,
Цены.Сотрудник,
ПОРЯДКОВЫЙ_НОМЕР() OVER (PARTITION BY Цены.Номенклатура УПОРЯДОЧИТЬ ПО Цены.Период УБЫВ) КАК НомерСтроки
ИЗ
РегистрСведений.ЦеныНоменклатуры КАК Цены
) КАК Т2
ПО Т1.Ссылка = Т2.Номенклатура И Т2.НомерСтроки = 1
Этот подход гораздо элегантнее и быстрее, так как СУБД выполняет оптимизацию на уровне собственного движка, не создавая лишних соединений для поиска дополнительных реквизитов.
Если версия платформы не позволяет использовать оконные функции, а нам нужно получить из второй таблицы целую строку (несколько полей), применим метод "ключ + значение". Выясним причину популярности этого метода: он гарантирует целостность данных при отсутствии современных функций.
Разберем по шагам:
Таблицу1 с Таблицей2, используя сразу два условия связи: по ключевому полю и по значению критерия из нашей ВТ.Пример реализации:
// Помещаем во временную таблицу только ключи и максимальные даты
ВЫБРАТЬ
Цены.Номенклатура КАК Номенклатура,
МАКСИМУМ(Цены.Период) КАК МаксПериод
ПОМЕСТИТЬ ВТ_МаксДаты
ИЗ
РегистрСведений.ЦеныНоменклатуры КАК Цены
СГРУППИРОВАТЬ ПО
Цены.Номенклатура;
////////////////////////////////////////////////////////////////////////////////
// Соединяем основную таблицу с ценами по двум полям
ВЫБРАТЬ
Номенклатура.Ссылка,
Цены.Цена,
Цены.Валюта
ИЗ
Справочник.Номенклатура КАК Номенклатура
ЛЕВОЕ СОЕДИНЕНИЕ ВТ_МаксДаты КАК МаксДаты
ПО Номенклатура.Ссылка = МаксДаты.Номенклатура
ЛЕВОЕ СОЕДИНЕНИЕ РегистрСведений.ЦеныНоменклатуры КАК Цены
ПО Номенклатура.Ссылка = Цены.Номенклатура
И МаксДаты.МаксПериод = Цены.Период
Важный нюанс: Если в одну и ту же секунду (период) были установлены две разные цены, этот запрос все равно вернет две строки. Чтобы этого избежать, в группировку и в условия связи нужно добавлять дополнительное уникальное поле, например, УникальныйИдентификатор или НомерСтроки.
Если ваша "вторая таблица" является периодическим Регистром Сведений, платформа 1С уже подготовила оптимальное решение. Виртуальная таблица СрезПоследних делает именно то, что нам нужно: возвращает ровно одну запись для каждого уникального набора измерений на указанный момент времени.
Рассмотрим пример:
ВЫБРАТЬ
Номенклатура.Ссылка,
ЦеныСрез.Цена
ИЗ
Справочник.Номенклатура КАК Номенклатура
ЛЕВОЕ СОЕДИНЕНИЕ РегистрСведений.ЦеныНоменклатуры.СрезПоследних(&Период, ) КАК ЦеныСрез
ПО Номенклатура.Ссылка = ЦеныСрез.Номенклатура
Это самый быстрый и рекомендуемый способ при работе с регистрами сведений, так как расчет среза выполняется эффективно на уровне СУБД.
Выбирая метод сопоставления первой записи, следует помнить о производительности:
JOIN.СОЕДИНЕНИЕ, если данных много. Лучше вынести подготовку "первой записи" в отдельную временную таблицу.ВЫБРАТЬ ПЕРВЫЕ 1 внутри подзапроса, который соединяется с основной таблицей, может не дать ожидаемого результата для каждой строки, так как ПЕРВЫЕ 1 отсечет данные для всей выборки подзапроса сразу, а не для каждой группы связи.Проанализировав ситуацию, мы видим, что выбор метода зависит от версии вашей платформы и структуры данных. Для простых задач агрегации достаточно МАКСИМУМ, для сложных выборок всей строки лучше использовать временные таблицы с двойным условием связи, а для современных систем — оконные функции.