В практике разработки на платформе 1С:Предприятие 8.3 часто возникает специфическая задача: необходимо соединить две таблицы по общему ключевому полю так, чтобы записи не «размножались» (как при обычном соединении), а сопоставлялись последовательно. Например, если в первой таблице две записи с ключом «А», и во второй таблице две записи с ключом «А», мы хотим получить две строки в результате, а не четыре. В классическом SQL эта задача решается с помощью оконных функций, но в языке запросов 1С нам придется проявить изобретательность.
В этой статье мы подробно разберем, как реализовать механизм последовательного сопоставления строк (так называемый метод FIFO в запросе) с использованием временных таблиц и нумерации записей.
Представим ситуацию, описанную автором темы. У нас есть две таблицы. В первой для ключа «а» есть два значения — Q и W. Во второй таблице для того же ключа «а» есть значение S. Обычное ЛЕВОЕ СОЕДИНЕНИЕ по полю «Ключ» выдаст нам две строки:
Однако задача стоит иначе: значение S должно «приклеиться» только к первой записи, а вторая запись должна остаться пустой (или наоборот, если данных больше во второй таблице). Результат должен выглядеть так:
Проанализируем ситуацию: стандартный механизм соединений 1С работает по принципу декартова произведения в рамках совпадающих ключей. Чтобы избежать этого, нам нужно добавить в условие соединения еще одно поле, которое будет уникальным для каждой строки внутри группы. Таким полем станет порядковый номер записи в группе.
Для начала рассмотрим наиболее современный подход, доступный в актуальных версиях платформы 8.3. Мы воспользуемся функцией АВТОНОМЕРЗАПИСИ() во временных таблицах. Это позволит нам получить уникальный индекс для каждой строки, который мы позже преобразуем в локальный номер внутри группы.
Создадим две временные таблицы ВТ_Исходная1 и ВТ_Исходная2, добавив в них техническое поле с номером:
ВЫБРАТЬ
ИсходнаяТаблица1.Ключ КАК Ключ,
ИсходнаяТаблица1.Значение КАК Значение,
АВТОНОМЕРЗАПИСИ() КАК СквознойНомер
ПОМЕСТИТЬ ВТ_Таблица1_С_Номером
ИЗ
ИсходнаяТаблица1 КАК ИсходнаяТаблица1
;
////////////////////////////////////////////////////////////////////////////////
ВЫБРАТЬ
ИсходнаяТаблица2.Ключ КАК Ключ,
ИсходнаяТаблица2.Значение КАК Значение,
АВТОНОМЕРЗАПИСИ() КАК СквознойНомер
ПОМЕСТИТЬ ВТ_Таблица2_С_Номером
ИЗ
ИсходнаяТаблица2 КАК ИсходнаяТаблица2
Важный нюанс: Функция АВТОНОМЕРЗАПИСИ() создает уникальный номер для всей таблицы в целом. Для нашей задачи этого недостаточно, так как нам нужно сопоставлять первую запись «а» с первой записью «а», а первую запись «б» — с первой записью «б».
Рассмотрим основной прием: чтобы получить номер строки внутри группы «по ключу», мы должны соединить таблицу саму с собой. Условие соединения будет следующим: мы считаем, сколько записей с таким же ключом имеют СквознойНомер меньше или равный текущему. Это и будет нашим локальным номером.
Проанализируем запрос для первой таблицы:
ВЫБРАТЬ
Т1.Ключ КАК Ключ,
Т1.Значение КАК Значение,
КОЛИЧЕСТВО(РАЗЛИЧНЫЕ Т2.СквознойНомер) КАК НомерВГруппе
ПОМЕСТИТЬ ВТ_Таблица1_Финал
ИЗ
ВТ_Таблица1_С_Номером КАК Т1
ВНУТРЕННЕЕ СОЕДИНЕНИЕ ВТ_Таблица1_С_Номером КАК Т2
ПО Т1.Ключ = Т2.Ключ
И Т1.СквознойНомер >= Т2.СквознойНомер
СГРУППИРОВАТЬ ПО
Т1.Ключ,
Т1.Значение,
Т1.СквознойНомер
Выясним, что происходит в этом блоке: для каждой строки из Т1 мы подбираем все строки из Т2 с тем же ключом, чей номер не больше текущего. Группировка и функция КОЛИЧЕСТВО превращают этот набор в порядковое число: 1, 2, 3 и так далее для каждого нового ключа. Аналогичную операцию необходимо проделать и для второй таблицы.
Теперь, когда у нас есть две таблицы, где каждая строка имеет Ключ и НомерВГруппе, мы можем соединить их. Мы используем ПОЛНОЕ СОЕДИНЕНИЕ, чтобы не потерять данные, если в одной из таблиц строк по конкретному ключу окажется больше.
ВЫБРАТЬ
ЕСТЬNULL(Т1.Ключ, Т2.Ключ) КАК Ключ,
Т1.Значение КАК Значение1,
Т2.Значение КАК Значение2
ИЗ
ВТ_Таблица1_Финал КАК Т1
ПОЛНОЕ СОЕДИНЕНИЕ ВТ_Таблица2_Финал КАК Т2
ПО Т1.Ключ = Т2.Ключ
И Т1.НомерВГруппе = Т2.НомерВГруппе
УПОРЯДОЧИТЬ ПО
Ключ,
Т1.НомерВГруппе
Обратите внимание на использование функции ЕСТЬNULL для поля Ключ. При полном соединении, если запись есть только во второй таблице, поле Т1.Ключ будет содержать NULL, поэтому мы подстраховываемся, чтобы результат был корректным.
Рассмотрим важный аспект: метод самосоединения для нумерации имеет квадратичную сложность O(n²). Если по одному ключу у вас будет 10 000 записей, запрос может работать крайне долго, так как количество связей внутри одного соединения будет колоссальным.
Рекомендации по оптимизации:
ИНДЕКСИРОВАТЬ ПО для поля Ключ.ТаблицуЗначений, добавление колонки с номером в цикле и загрузка обратно в менеджер временных таблиц будет работать быстрее, чем сложный SQL-запрос.СквознойНомер), уникально. Если использовать неуникальное поле, функция КОЛИЧЕСТВО может выдать одинаковые номера для разных строк, и данные снова «схлопнутся» или размножатся.Если ваша задача реализуется в рамках Системы компоновки данных (СКД), можно рассмотреть вариант с использованием нескольких наборов данных. Однако стоит помнить, что связи между наборами данных в СКД по умолчанию работают как ЛЕВОЕ СОЕДИНЕНИЕ. Без предварительной нумерации строк в самих запросах-источниках, СКД точно так же выдаст дубли при связи «один ко многим».
Поэтому решение с нумерацией внутри запроса, предложенное выше, является наиболее универсальным и надежным для формирования сложных отчетов, где требуется четкое соответствие строк из разных источников данных.
Мы проанализировали ситуацию, разобрали принцип работы механизмов соединения и научились имитировать сложные оконные функции стандартными средствами языка запросов 1С. Этот метод позволяет решать задачи сопоставления данных любой сложности, сохраняя структуру и целостность результирующей таблицы.