Как распарсить строку адреса в 1С: отсекаем город от улицы в запросе и коде

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

Работа с неструктурированными строками адресов — одна из классических и наиболее трудоемких задач в практике разработчика 1С. Часто адрес хранится в базе в виде единой строки, например: "Россия, Респ. Башкортостан, Белорецкий р-н., с. Ассы, ул. Мубарякова, д. 8". Когда возникает необходимость сопоставить эти данные, отсечь город от улицы или выполнить точечный поиск, стандартные механизмы запросов начинают давать сбои. Конструкция ПОДОБНО "%...%" работает медленно и неэффективно на больших объемах данных, а встроенный язык СУБД не предоставляет простых функций для работы со строками.

В этой статье мы подробно разберем, как решить проблему разделения адреса на составляющие. Мы рассмотрим как традиционные подходы (выгрузка в таблицу значений, использование временных таблиц), так и современные нативные инструменты платформы 1С, включая регулярные выражения и возможности адресного классификатора.

Метод 1. Нативный разбор строки во встроенном языке (платформа 8.3.6 и выше)

Если задача позволяет обработать адреса во встроенном языке перед выводом или записью, мы можем отказаться от медленных функций БСП и использовать быстрые нативные функции платформы 1С. Начиная с версии 8.3.6, в платформе появились функции СтрРазделить и СтрСоединить, написанные на C++ и работающие значительно быстрее любых аналогов на встроенном языке.

Рассмотрим по шагам, как разложить строку адреса в массив по запятым, отсечь ненужные части и собрать адрес обратно:


АдресСтрока = "Россия, Респ. Башкортостан, Белорецкий р-н., с. Ассы, ул. Мубарякова, д. 8";

// Шаг 1. Разбиваем строку по запятым
МассивЧастей = СтрРазделить(АдресСтрока, ",");

// Шаг 2. Анализируем полученные части
МассивДляНовогоАдреса = Новый Массив;
УлицаИДом = "";

Для Каждого ЧастьАдреса Из МассивЧастей Цикл
    ОчищеннаяЧасть = СокрЛП(ЧастьАдреса);
    
    // Ищем ключевые сокращения улиц и домов
    Если Лев(ОчищеннаяЧасть, 3) = "ул." Или Лев(ОчищеннаяЧасть, 2) = "д." Тогда
        УлицаИДом = УлицаИДом + ?(ЗначениеЗаполнено(УлицаИДом), ", ", "") + ОчищеннаяЧасть;
    Иначе
        МассивДляНовогоАдреса.Добавить(ОчищеннаяЧасть);
    EndIf;
КонецЦикла;

// Шаг 3. Собираем очищенную адресную часть (без улицы и дома) обратно
АдресБезУлицы = СтрСоединить(МассивДляНовогоАдреса, ", ");

Сообщить("Регион/Город: " + АдресБезУлицы);
Сообщить("Улица/Дом: " + УлицаИДом);

Этот метод прост, надежен и отлично подходит для постобработки результатов запроса.

Метод 2. Регулярные выражения (платформа 8.3.23 и выше)

Если в вашем распоряжении современная версия платформы 1С (начиная с 8.3.23), вы можете использовать встроенную поддержку регулярных выражений (RegEx). Это полностью исключает необходимость ручного поиска символов, подсчета запятых или использования нестабильных COM-объектов на Linux-серверах.

Выясним, как с помощью регулярного выражения быстро вычленить из строки только информацию об улице и доме:


АдресСтрока = "Россия, Респ. Башкортостан, Белорецкий р-н., с. Ассы, ул. Мубарякова, д. 8";
ШаблонУлицы = "ул\.\s?[А-Яа-яЁё\s]+,\s?д\.\s?\d+";

// Ищем совпадение шаблона в строке адреса
РезультатПоиска = СтрНайтиПоРегулярномуВыважению(АдресСтрока, ШаблонУлицы);

Если РезультатПоиска.Длина > 0 Тогда
    УлицаИДом = Сред(АдресСтрока, РезультатПоиска.Начало, РезультатПоиска.Длина);
    // Для получения адреса без улицы удаляем найденную подстроку
    АдресБезУлицы = СтрЗаменитьПоРегулярномуВыражению(АдресСтрока, ШаблонУлицы, "");
    
    Сообщить("Найдено: " + УлицаИДом);
    Сообщить("Остаток адреса: " + СокрЛП(АдресБезУлицы));
КонецЕсли;

Регулярные выражения позволяют обрабатывать даже самые нестандартные написания адресов, гибко подстраивая шаблоны под особенности ввода пользователей.

Метод 3. Выгрузка в Таблицу значений и менеджер временных таблиц

Если результат парсинга критически важно использовать в дальнейшем сложном запросе (например, для соединения с другими таблицами БД), наилучшим архитектурным решением будет предварительная обработка данных. Прямой парсинг строк внутри SQL-запроса снижает производительность СУБД.

Проанализируем эффективный алгоритм реализации этой схемы:

  1. Выполняем первичный быстрый запрос для получения ссылок и сырых строк адресов.
  2. Выгружаем результат запроса в объект ТаблицаЗначений.
  3. В цикле обрабатываем строки программно методами СтрРазделить или регулярными выражениями, разделяя адрес на нужные колонки.
  4. Передаем заполненную и структурированную ТаблицуЗначений обратно в новый запрос через менеджер временных таблиц для финального сопоставления.

Рассмотрим пример кода, реализующий данный подход:


// Шаг 1. Получаем сырые данные
ЗапросПервичный = Новый Запрос;
ЗапросПервичный.Текст = 
    "ВЫБРАТЬ
    |	Контрагенты.Ссылка КАК Контрагент,
    |	Контрагенты.АдресСтрокой КАК АдресСтрокой
    |ИЗ
    |	Справочник.Контрагенты КАК Контрагенты";

ТаблицаАдресов = ЗапросПервичный.Выполнить().Выгрузить();

// Добавляем новые типизированные колонки для разделенного адреса
ТаблицаАдресов.Колонки.Добавить("ГородскойОкруг", Новый ОписаниеТипов("Строка",,,, Новый КвалификаторыСтроки(150)));
ТаблицаАдресов.Колонки.Добавить("УлицаИДом", Новый ОписаниеТипов("Строка",,,, Новый КвалификаторыСтроки(150)));

// Шаг 2. Парсим строки адреса во встроенном языке
Для Каждого СтрокаАдреса Из ТаблицаАдресов Цикл
    МассивЧастей = СтрРазделить(СтрокаАдреса.АдресСтрокой, ",");
    
    // Простая логика: считаем, что первые 3 части - это город/район, остальное - улица
    Если МассивЧастей.Количество() >= 3 Тогда
        СтрокаАдреса.ГородскойОкруг = СокрЛП(МассивЧастей[0]) + ", " + СокрЛП(МассивЧастей[1]) + ", " + СокрЛП(МассивЧастей[2]);
        
        // Собираем оставшуюся часть адреса
        МассивУлицы = Новый Массив;
        Для Индекс = 3 По МассивЧастей.В Граница() Цикл
            МассивУлицы.Добавить(СокрЛП(МассивЧастей[Индекс]));
        КонецЦикла;
        СтрокаАдреса.УлицаИДом = СтрСоединить(МассивУлицы, ", ");
    Иначе
        СтрокаАдреса.ГородскойОкруг = СтрокаАдреса.АдресСтрокой;
    КонецЕсли;
КонецЦикла;

// Шаг 3. Передаем обработанную таблицу в новый запрос через МВТ
МенеджерВТ = Новый МенеджерВременныхТаблиц;
ЗапросФинальный = Новый Запрос;
ЗапросФинальный.МенеджерВременныхТаблиц = МенеджерВТ;

// Помещаем таблицу значений во временную таблицу в запросе
ЗапросФинальный.УстановитьПараметр("ТаблицаАдресов", ТаблицаАдресов);

ЗапросФинальный.Текст = 
    "ВЫБРАТЬ *
    |ПОМЕСТИТЬ ВТ_Адреса
    |ИЗ
    |	&ТаблицаАдресов КАК ТаблицаАдресов
    |;
    |
    |////////////////////////////////////////////////////////////////////////////////
    |ВЫБРАТЬ
    |	ВТ_Адреса.Контрагент,
    |	ВТ_Адреса.ГородскойОкруг,
    |	ВТ_Адреса.УлицаИДом
    |ИЗ
    |	ВТ_Адреса КАК ВТ_Адреса";

Результат = ЗапросФинальный.Выполнить().Выбрать();
Пока Результат.Следующий() Цикл
    // Выполняем дальнейшую бизнес-логику
КонецЦикла;

Метод 4. Парсинг строки прямо внутри SQL-запроса (Метод "Таблицы чисел")

Существует продвинутый и нестандартный прием, позволяющий обойти ограничения встроенного языка запросов 1С и разбить строку по запятым прямо на уровне СУБД. Этот метод крайне полезен, когда выгрузка миллионов записей в оперативную память сервера приложений невозможна из-за жестких требований к производительности.

Суть метода заключается в генерации последовательности чисел (индексов символов) и посимвольном анализе строки через функцию ПОДСТРОКА. Сначала мы создаем виртуальную таблицу чисел от 1 до 100, затем соединяем ее с нашей таблицей адресов и находим позиции запятых. На основе позиций запятых с помощью функций ВЫБОР мы вычленяем нужные подстроки.

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


ВЫБРАТЬ 1 КАК Число ПОМЕСТИТЬ Цифры
ОБЪЕДИНИТЬ ВСЕ ВЫБРАТЬ 2 ОБЪЕДИНИТЬ ВСЕ ВЫБРАТЬ 3 ОБЪЕДИНИТЬ ВСЕ ВЫБРАТЬ 4 
ОБЪЕДИНИТЬ ВСЕ ВЫБРАТЬ 5 ОБЪЕДИНИТЬ ВСЕ ВЫБРАТЬ 6 ОБЪЕДИНИТЬ ВСЕ ВЫБРАТЬ 7 
ОБЪЕДИНИТЬ ВСЕ ВЫБРАТЬ 8 ОБЪЕДИНИТЬ ВСЕ ВЫБРАТЬ 9 ОБЪЕДИНИТЬ ВСЕ ВЫБРАТЬ 0
;

// Генерируем таблицу чисел от 1 до 100 через кросс-соединение
ВЫБРАТЬ
	(Т1.Число * 10 + Т2.Число) + 1 КАК НомерСимвола
ПОМЕСТИТЬ ТаблицаЧисел
ИЗ
	Цифры КАК Т1
		ПОЛНОЕ СОЕДИНЕНИЕ Цифры КАК Т2 ПО ИСТИНЕ
;

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

// Вычленяем части адреса на основе найденных позиций
ВЫБРАТЬ
	ПозицииЗапятых.Ссылка,
	ПОДСТРОКА(ПозицииЗапятых.Адрес, 1, МИНИМУМ(ПозицииЗапятых.ПозицияЗапятой) - 1) КАК СтранаИРегион
ИЗ
	ПозицииЗапятых КАК ПозицииЗапятых
СГРУППИРОВАТЬ ПО
	ПозицииЗапятых.Ссылка,
	ПозицииЗапятых.Адрес

Важное примечание: Этот метод сложен в отладке и поддержке. Используйте его только в критических с точки зрения оптимизации сценариях.

Метод 5. Использование встроенных инструментов БСП (ГАР / ФИАС)

Если ваша конфигурация разработана с использованием Библиотеки стандартных подсистем (БСП), вам нет необходимости писать собственные алгоритмы разбора строк. В БСП встроены мощные механизмы взаимодействия с Государственным адресным реестром (ГАР/ФИАС). Дабы не "изобретать велосипед", задействуем функционал модуля адресного классификатора.

Для разбора произвольной строки адреса в структурированное представление используйте метод:


// Локальный разбор строки по загруженному адресному классификатору ГАР/ФИАС
РезультатРаспознавания = АдресныйКлассификаторСлужебный.РаспознатьАдрес(СтрокаАдреса);

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


// Запрос к веб-сервису 1С для парсинга адреса любой сложности
РезультатВеб = АдресныйКлассификаторСлужебный.РаспознатьАдресВебСервис(СтрокаАдреса);

В результате работы этих функций система вернет готовую структуру, содержащую отдельно регион, район, город, населенный пункт, улицу, дом и квартиру. Этот метод является наиболее надежным и современным стандартом в экосистеме 1С.

Метод 6. Использование внешних сервисов стандартизации (DaData)

Если база данных полностью самописная и интеграция БСП невозможна или нецелесообразна, отличной альтернативой выступает использование внешних специализированных API, таких как DaData. Сервис позволяет бесплатно или по подписке отправлять неструктурированные строки адресов и получать назад полностью валидированный, разобранный по полям JSON.

Реализуем процедуру разбора адреса через HTTP-соединение:


Функция РазобратьАдресЧерезДадату(СыройАдрес, ТокенAPI, СекретныйКлюч) Экспорт
    
    HTTPСоединение = Новый HTTPСоединение("clean.dadata.ru", 443,,,,, Новый ЗащищенноеСоединениеOpenSSL);
    
    HTTPЗапрос = Новый HTTPЗапрос("/api/v1/clean/address");
    HTTPЗапрос.Заголовки.Вставить("Content-Type", "application/json");
    HTTPЗапрос.Заголовки.Вставить("Authorization", "Token " + ТокенAPI);
    HTTPЗапрос.Заголовки.Вставить("X-Secret", СекретныйКлюч);
    
    // Передаем адрес массивом в теле запроса
    ТелоЗапроса = "[""" + СыройАдрес + """]";
    HTTPЗапрос.УстановитьТелоШаблона(ТелоЗапроса);
    
    Попытка
        HTTPОтвет = HTTPСоединение.ОтправитьДляОбработки(HTTPЗапрос);
        Если HTTPОтвет.КодСостояния = 200 Тогда
            ЧтениеJSON = Новый ЧтениеJSON;
            ЧтениеJSON.УстановитьСтроку(HTTPОтвет.ПолучитьТелоКакСтроку());
            РезультатАнализа = ПрочитатьJSON(ЧтениеJSON);
            ЧтениеJSON.Закрыть();
            
            // Возвращаем структуру с разделенными полями адреса
            Если РезультатАнализа.Количество() > 0 Тогда
                Возврат РезультатАнализа[0];
            КонецЕсли;
        КонецЕсли;
    Исключение
        ЗаписьЖурналаРегистрации("ИнтеграцияDaData", УровеньЖурналаРегистрации.Ошибка,,, ОписаниеОшибки());
    КонецПопытки;
    
    Возврат Неопределено;
КонецФункции

Полученный от DaData объект будет содержать такие ключи, как city (город), street (улица), house (дом) и даже географические координаты, что полностью закрывает потребность в ручном парсинге.

← На главную