При разработке сложных отчетов или механизмов ценообразования в 1С часто возникает ситуация, когда данные по одной и той же номенклатуре поступают из разных источников (например, из разных документов, регистров или условий). При этом для каждой позиции может быть задан «приоритет», и нашей задаче является выбор только одной строки с наиболее высоким приоритетным значением. Рассмотрим, как правильно реализовать такой алгоритм в языке запросов 1С, проанализируем типичные ошибки и разберем современные методы оптимизации.
Представим ситуацию: у нас есть две таблицы. В первой таблице для Ном1 указан процент 20 и приоритет 1. Во второй таблице для того же Ном1 указан процент 25 и приоритет 2. Нам необходимо получить итоговую таблицу, где для каждой номенклатуры останется только одна строка с максимальным приоритетом. В нашем примере результатом для Ном1 должна стать строка с процентом 25.
Типичная ошибка начинающих разработчиков — попытка сделать простое объединение с группировкой по всем полям. Если мы просто сгруппируем по Номенклатура и Процент, взяв МАКСИМУМ(Приоритет), мы все равно получим две строки, так как значения процентов различаются. Система не сможет автоматически «понять», какой именно процент соответствует максимальному приоритету, пока мы не укажем это явно через механизмы соединений.
Этот метод является классическим и наиболее универсальным для всех версий платформы 8.x. Разберем его по шагам:
Рассмотрим пример кода, реализующий этот подход:
// Шаг 1: Объединяем данные из разных источников
ВЫБРАТЬ
Источник1.Номенклатура КАК Номенклатура,
Источник1.Процент КАК Процент,
Источник1.Приоритет КАК Приоритет
ПОМЕСТИТЬ ВТ_ВсеДанные
ИЗ
&Таблица1 КАК Источник1
ОБЪЕДИНИТЬ ВСЕ
ВЫБРАТЬ
Источник2.Номенклатура,
Источник2.Процент,
Источник2.Приоритет
ИЗ
&Таблица2 КАК Источник2
;
// Шаг 2 и 3: Находим максимумы и соединяем их с исходными данными
ВЫБРАТЬ
Т_Макс.Номенклатура,
Б.Процент,
Т_Макс.МаксПриоритет КАК Приоритет
ИЗ
(ВЫБРАТЬ
Номенклатура,
МАКСИМУМ(Приоритет) КАК МаксПриоритет
ИЗ
ВТ_ВсеДанные
СГРУППИРОВАТЬ ПО
Номенклатура) КАК Т_Макс
ВНУТРЕННЕЕ СОЕДИНЕНИЕ ВТ_ВсеДанные КАК Б
ПО Т_Макс.Номенклатура = Б.Номенклатура
И Т_Макс.МаксПриоритет = Б.Приоритет
Обратим внимание на использование ВНУТРЕННЕЕ СОЕДИНЕНИЕ. Оно позволяет нам отсечь все строки из ВТ_ВсеДанные, чей приоритет не совпадает с максимальным для данной номенклатуры. Это самый надежный способ «притянуть» нужный процент к его приоритету.
Если источников строго два и мы заранее знаем их иерархию, можно использовать логику «если есть данные во второй таблице с большим приоритетом, берем их, иначе — из первой». Рассмотрим, как это выглядит в запросе:
ВЫБРАТЬ
ВЫБОР
КОГДА ВТ2.Номенклатура ЕСТЬ NULL ТОГДА ВТ1.Номенклатура
ИНАЧЕ ВТ2.Номенклатура
КОНЕЦ КАК Номенклатура,
ВЫБОР
КОГДА ВТ2.Номенклатура ЕСТЬ NULL ТОГДА ВТ1.Процент
ИНАЧЕ ВТ2.Процент
КОНЕЦ КАК Процент
ИЗ
ВТ1 КАК ВТ1
ЛЕВОЕ СОЕДИНЕНИЕ ВТ2 КАК ВТ2
ПО ВТ1.Номенклатура = ВТ2.Номенклатура
И ВТ2.Приоритет > ВТ1.Приоритет
Этот способ менее гибкий. Если в ВТ1 данных нет, а в ВТ2 они есть, нам придется использовать ПОЛНОЕ СОЕДИНЕНИЕ, что сильно усложнит конструкцию ВЫБОР КОГДА. Поэтому для трех и более источников всегда рекомендуем использовать первый метод с объединением в общую таблицу.
Если ваша база данных работает на платформе 1С версии 8.3.21 и выше, мы можем использовать мощный инструмент — оконные функции. Это позволяет решить задачу без повторных соединений с базовой таблицей, что значительно повышает производительность на больших объемах данных.
Проанализируем пример с использованием функции ROW_NUMBER() (в 1С это функция ПОРЯДОК НОМЕРА()):
ВЫБРАТЬ
ВТ_Данные.Номенклатура,
ВТ_Данные.Процент,
ВТ_Данные.Приоритет,
ПОРЯДОК НОМЕРА() OVER (РАЗДЕЛИТЬ ПО ВТ_Данные.Номенклатура УПОРЯДОЧИТЬ ПО ВТ_Данные.Приоритет УБЫВ) КАК НомерСтроки
ПОМЕСТИТЬ ВТ_ПронумерованныеДанные
ИЗ
ВТ_ВсеДанные КАК ВТ_Данные
;
ВЫБРАТЬ
Номенклатура,
Процент,
Приоритет
ИЗ
ВТ_ПронумерованныеДанные
ГДЕ
НомерСтроки = 1
В этом случае система нумерует строки внутри каждой группы номенклатуры, начиная с той, где приоритет самый высокий (благодаря УПОРЯДОЧИТЬ ПО Приоритет УБЫВ). Нам остается лишь выбрать записи под номером 1. Это самый эффективный способ на сегодняшний день.
Разберем несколько критически важных моментов, которые могут повлиять на корректность результата:
МАКСИМУМ(Процент).ВТ_ВсеДанные, добавьте ИНДЕКСИРОВАТЬ ПО Номенклатура, Приоритет. Это ускорит операцию соединения в разы.ОБЪЕДИНИТЬ пытается найти и удалить одинаковые строки во всех источниках. Это ресурсоемкая операция. Поскольку мы все равно потом будем искать максимум через группировку, используйте ОБЪЕДИНИТЬ ВСЕ — это работает быстрее, так как просто «склеивает» таблицы.ЕСТЬNULL(Поле, 0), особенно в арифметических операциях или при сравнении приоритетов.Подводя итог, можно сказать, что выбор метода зависит от версии платформы и количества источников. Для старых систем идеальным остается подход с объединением во временную таблицу и соединением с выборкой МАКСИМУМ(Приоритет). Для новых систем — использование оконных функций. Проанализируем ваши данные и выберем оптимальный вариант вместе!