В процессе оптимизации кода часто возникает задача перенести сложную логику обработки данных из цикла в коде 1С в запрос. Рассмотрим специфический случай: у нас есть таблица (например, с координатами), которую нужно обработать "навстречу" — взять первую и последнюю строки, затем вторую и предпоследнюю, и так далее, пока "указатели" не встретятся в середине. На первый взгляд, кажется, что язык запросов, не имеющий циклов, не подходит для этого. Однако, это не так. Разберем, как решить эту задачу декларативно.
Основная сложность заключается в том, что язык запросов 1С оперирует не итерациями и "обходами", а выборками и соединениями данных. Наша цель — не воспроизвести цикл один в один, а получить тот же конечный результат, сформировав набор данных, где каждая строка будет содержать нужную нам пару: элемент с начала и соответствующий ему элемент с конца.
Этот метод является наиболее универсальным и мощным для решения поставленной задачи. Он позволяет получить все нужные пары (первая-последняя, вторая-предпоследняя и т.д.) в результате одного запроса. Разберем его по шагам.
ROW_NUMBER(). Также нам понадобится общее количество строк в таблице, которое мы можем получить с помощью агрегатной функции КОЛИЧЕСТВО(*).Проанализируем логику условия соединения. Пусть у нас есть N строк. Для первой строки (номер 1) нам нужна последняя (номер N). Для второй (номер 2) — предпоследняя (номер N-1). В общем виде, для строки с номером i нам нужна строка с номером N - i + 1. Именно это правило мы и заложим в условие соединения.
Посмотрим на пример. Предположим, у нас есть табличная часть документа КоординатыГородов с колонкой Координата.
Запрос будет выглядеть следующим образом:
// Шаг 1: Помещаем данные во временную таблицу, нумеруем строки и считаем общее количество
ВЫБРАТЬ
ТЧ.Координата,
КОЛИЧЕСТВО(1) ПО (УПОРЯДОЧИТЬ ПО ТЧ.НомерСтроки) КАК НомерСтроки, // ROW_NUMBER()
КОЛИЧЕСТВО(1) ПО () КАК ВсегоСтрок // COUNT(*)
ПОМЕСТИТЬ ПронумерованныеКоординаты
ИЗ
Документ.КакойТоДокумент.КоординатыГородов КАК ТЧ
ГДЕ
ТЧ.Ссылка = &СсылкаНаДокумент;
////////////////////////////////////////////////////////////////////////////////
// Шаг 2: Соединяем временную таблицу с самой собой для получения пар
ВЫБРАТЬ
Т1.Координата КАК КоординатаНачало,
Т2.Координата КАК КоординатаКонец,
Т1.НомерСтроки
ИЗ
ПронумерованныеКоординаты КАК Т1
ВНУТРЕННЕЕ СОЕДИНЕНИЕ ПронумерованныеКоординаты КАК Т2
ПО Т1.НомерСтроки = (Т1.ВсегоСтрок - Т2.НомерСтроки + 1)
ГДЕ
// Условие, чтобы избежать дублирования пар (например, 1-10 и 10-1).
// Мы берем только первую половину строк из левой таблицы.
Т1.НомерСтроки <= Т1.ВсегоСтрок / 2
;
Разберем код подробнее:
ПронумерованныеКоординаты.КОЛИЧЕСТВО(1) ПО (УПОРЯДОЧИТЬ ПО ТЧ.НомерСтроки) — это аналог оконной функции ROW_NUMBER() OVER (ORDER BY НомерСтроки). Она присваивает каждой строке уникальный порядковый номер. Важно, чтобы был некий столбец (в данном случае стандартный реквизит ТЧ НомерСтроки), по которому можно однозначно упорядочить записи.КОЛИЧЕСТВО(1) ПО () — это аналог COUNT(*) OVER (). Он добавляет в каждую строку общее количество записей в выборке. Это избавляет нас от необходимости делать отдельный запрос для подсчета.ПронумерованныеКоординаты (Т1) с ней же (Т2).Т1.НомерСтроки = (Т1.ВсегоСтрок - Т2.НомерСтроки + 1) как раз и связывает i-ю строку с начала с i-й строкой с конца.ГДЕ Т1.НомерСтроки <= Т1.ВсегоСтрок / 2 необходимо, чтобы получить каждую пару только один раз. Без него мы бы получили, например, пару (2, предпоследняя) и (предпоследняя, 2). Если в таблице нечетное количество строк, центральный элемент не попадет в выборку, так как ему нет пары. Если он нужен, условие нужно скорректировать: Т1.НомерСтроки <= (Т1.ВсегоСтрок / 2) + (Т1.ВсегоСтрок % 2).Теперь в результате запроса мы имеем плоскую таблицу, где в каждой строке уже есть нужные нам данные: КоординатаНачало и КоординатаКонец. Дальнейшие вычисления по вашей формуле можно производить прямо в секции ВЫБРАТЬ этого же запроса.
Часто возникает вопрос: что будет быстрее — такой "тяжелый" запрос или привычный цикл в коде 1С? Однозначного ответа нет, все зависит от объема данных.
Главный вывод: всегда проводите замеры производительности на данных, приближенных к реальным. Универсального рецепта нет, и выбор оптимального решения зависит от конкретной ситуации.
Если ваша задача связана не просто с числами, а именно с географическими координатами, и в будущем планируется более сложная логика (построение маршрутов, поиск ближайших объектов, расчет расстояний по кривой Земли), стоит рассмотреть более профессиональные инструменты.
PostGIS — это расширение для СУБД PostgreSQL, которое добавляет поддержку гео-объектов и множество встроенных функций для пространственного анализа. Платформа 1С может работать с PostgreSQL, и для вызова специфичных функций PostGIS можно использовать прямые запросы к СУБД.
Этот подход требует более высокой квалификации и усложняет архитектуру, но для серьезных геоаналитических систем он является наиболее производительным и правильным, так как переносит узкоспециализированные вычисления на тот уровень, где они реализованы максимально эффективно.