Как в классификаторе адресов найти

Федеральная
Налоговая служба

Федеральная информационная адресная система

О ФИАС

Поиск

Обновления

Вопросы и ответы

Статистика

Заявка в ФИАС

Информация о заявке

Вернуться к полнотекстовому поиску

Деление

Муниципальное
Административно-территориальное
Субъект Российской Федерации
Муниципальный район/округ
Поселение
Город
Населенный пункт
Элемент планировочной структуры
Элемент улично-дорожной сети
Номер земельного участка:
Номер здания/сооружения
Номер машино-места:
Номер помещения:
Номер помещения в пределах помещения:
Почтовый индекс
ОКАТО
ОКТМО
Уникальный номер адреса объекта адресации в государственном адресном реестре
Кадастровый номер

Тип сведений

Только действующие объекты

Уровень объекта
Тип объекта

Полный каталог классификатора адресов России


Все адреса в ФИАС: Москва и Россия на одной карте

На данной странице представлен ФИАС – онлайн классификатор адресов России,
находящийся в
открытом доступе. Этот классификатор создан АО ГНИВЦ, и изначально предназначался для
внутриведомственного использования
ФНС, в частности, для привязки адресных объектов к налоговым инспекциям и упрощения отправки
корреспонденции по
контрагентам. Данный классификатор используется в ФНС России с 1 декабря 2005 г. Сейчас область его
применения
значительно расширилась – сегодня информация ФИАС требуется во многих аспектах работы организаций.
Данный онлайн-сервис
представлен у нас в ознакомительных целях. Он позволяет оперативно находить необходимые данные по
любому
территориальному объекту Российской Федерации. Например, можно бесплатно найти в соответствии с ФИАС
объекты по адресу,
почтовому индексу, найти все адреса, привязанные к определенной налоговой инспекции ФНС России, или
к интересующему вас
коду ОКАТО. Данные загружаются и своевременно обновляются из базы, представленной на официальном
сайте www.gnivc.ru.

Структурно справочник адресов ФИАС разделен на семь уровней. Поиск начинается по
региону, затем
продолжается по району, городу, населенному пункту, и заканчивается по номеру дома и квартиры. Задав
в поле поиска
интересующий адрес, по ФИАС в онлайн режиме можно также узнать индекс предприятия связи, номер ИНФС
налогоплательщика,
а также код ОКАТО. Эта информация окажется полезной при сдаче отчетности, постановке на налоговый
учет, при подготовке и
сдаче документов на регистрацию ИП и т.д.

Тем не менее, существуют ограничения доступности адресов некоторых объектов для
обычных пользователей.
Так, в онлайн-реестре у объектов, имеющих любую степень секретности, указаны лишь наименования.
Также в ФИАС
присутствуют старые, неактуальные адресные объекты – переименованные, переподчиненные или влитые в
состав другого
объекта и просто не существующие более. Во многих случаях это позволяет найти актуальные данные
человеку, который даже
не подозревает, что его адресные данные уже сменились.

Еще одной немаловажной функцией этого классификатора является проверка адреса по
ФИАС, которую у нас
также можно выполнить в онлайн режиме. Эта функция позволит уточнить как местонахождение адресанта,
так и правильность
написания адреса. Если адресанта не удалось найти в ФИАС по адресу, но вы уверены, что он
существует, необходимо
обратиться с соответствующим запросом в территориальный орган ФНС России, в ведении которой
находится справочник.

Искать и проверять адрес в формате ФИАС удобно в любой автоматизированной
информационной системе, что
делает этот справочник достаточно универсальным. Кроме того, отображаемый у нас классификатор в
обязательном порядке
используется работодателями при приеме сотрудников на работу, обеспечивая возможность удостовериться
в наличии у
принимаемого человека постоянной прописки по определенному месту жительства. Мы надеемся, что наш
бесплатный интуитивно
понятный онлайн сервис поможет вам в повседневной работе.

Регфорум предлагает вам актуальный КЛАДР 2016 года. С его помощью вы можете быстро узнать почтовый индекс, номер территориальной налоговой инспекции, регистрирующего органа, к которому прикреплен адрес, а также выяснить коды ОКТМО и ОКАТО. Предоставляем вам КЛАДР бесплатно и без дополнительных условий. Просто начните использовать его прямо сейчас!

  • Выбор
  • Поиск

Выберите субъект

  • Москва г.
  • Санкт-Петербург г.
  • Адыгея Респ.
  • Алтай Респ.
  • Алтайский край.
  • Амурская обл.
  • Архангельская обл.
  • Астраханская обл.
  • Байконур г.
  • Башкортостан Респ.
  • Белгородская обл.
  • Брянская обл.
  • Бурятия Респ.
  • Владимирская обл.
  • Волгоградская обл.
  • Вологодская обл.
  • Воронежская обл.
  • Дагестан Респ.
  • Еврейская Аобл.
  • Забайкальский край.
  • Ивановская обл.
  • Ингушетия Респ.
  • Иркутская обл.
  • Кабардино-Балкарская Респ.
  • Калининградская обл.
  • Калмыкия Респ.
  • Калужская обл.
  • Камчатский край.
  • Карачаево-Черкесская Респ.
  • Карелия Респ.
  • Кемеровская обл.
  • Кировская обл.
  • Коми Респ.
  • Костромская обл.
  • Краснодарский край.
  • Красноярский край.
  • Крым Респ.
  • Курганская обл.
  • Курская обл.
  • Ленинградская обл.
  • Липецкая обл.
  • Магаданская обл.
  • Марий Эл Респ.
  • Мордовия Респ.
  • Московская обл.
  • Мурманская обл.
  • Ненецкий АО.
  • Нижегородская обл.
  • Новгородская обл.
  • Новосибирская обл.
  • Омская обл.
  • Оренбургская обл.
  • Орловская обл.
  • Пензенская обл.
  • Пермский край.
  • Приморский край.
  • Псковская обл.
  • Ростовская обл.
  • Рязанская обл.
  • Самарская обл.
  • Саратовская обл.
  • Саха /Якутия/ Респ.
  • Сахалинская обл.
  • Свердловская обл.
  • Севастополь г.
  • Северная Осетия – Алания Респ.
  • Смоленская обл.
  • Ставропольский край.
  • Тамбовская обл.
  • Татарстан Респ.
  • Тверская обл.
  • Томская обл.
  • Тульская обл.
  • Тыва Респ.
  • Тюменская обл.
  • Удмуртская Респ.
  • Ульяновская обл.
  • Хабаровский край.
  • Хакасия Респ.
  • Ханты-Мансийский Автономный округ – Югра АО.
  • Челябинская обл.
  • Чеченская Респ.
  • Чувашская Республика –  Чувашия.
  • Чукотский АО.
  • Ямало-Ненецкий АО.
  • Ярославская обл.

Искать во всех субъектах

Искать только улицыИскать другие объекты

Выберите улицу из списка ниже:

Показать все дома этой улицы

Информация отсутствует.

Почтовый индекс

Показать адрес и телефоныСкрыть адрес и телефоны

Номер территориальной налоговой инспекции

Выберите населенный пункт

Показать все дома этой улицы

Вы выбрали адрес:

Информация отсутствует.

Почтовый индекс

Показать адрес и телефоныСкрыть адрес и телефоны

Номер территориальной налоговой инспекции

Инструкция по использованию КЛАДР

Вы можете использовать наш классификатор КЛАДР для нахождения требуемой информации двумя способами: через «Выбор» и «Поиск». В нем нет адресов, имеющих любую степень секретности. У закрытых административно-территориальных образований в КЛАДР России указывается только их наименование. Файлы классификатора создаются из базы данных Федеральной информационной адресной системы.

Чтобы выбрать адрес и получить желаемый код КЛАДР, кликните по первой строке, где указано «Москва», если вам нужен другой регион. В открывшемся окне найдите субъект РФ в списке или начните набирать его в поисковой строке. Таким же образом выберите город и улицу. И вы получите искомые данные, для которых наш КЛАДР предоставит вам список домов и другую информацию.

Чтобы найти адрес по названию, нажмите на вкладку «Поиск», укажите регион, если это не «Москва». Затем выберите что искать: улицы или другие объекты. Введите название объекта или его часть, нажмите «Искать», и выберите улицу из представленного списка. В зависимости от объекта наш справочник КЛАДР предоставляет вам возможность выбрать дом или просмотреть все адреса с информацией по ним. Если кликнуть по индексу, то отобразятся адрес почтового отделения и его телефоны.

О КЛАДР

Чтобы распределить территории между налоговыми инспекциями и автоматизировать рассылку корреспонденции был создан классификатор адресов КЛАДР для ведомственного использования в ФНС. В нашем сервисе всегда актуальный КЛАДР адресов 2016 года для всех регионов РФ, включая Крым. Используя вкладку «Выбор», вы можете найти адрес в конкретном населенном пункте. Если воспользоваться вкладкой «Поиск», то можно найти объект с определенным наименованием в одном или всех субъектах РФ.

Мы постарались сделать этот инструмент удобным и интуитивно понятным для вас. Нам бы хотелось совершенствовать наш онлайн-сервис КЛАДР, поэтому будем признательны вам за отзыв о его работе.

10 фактов о КЛАДР

  1. КЛАДР РФ содержит в электронном виде всю базу данных адресов России.
  2. В нем вся территория страны распределена по налоговым инспекциям.
  3. Он был введен приказом Федеральной Налоговой Службы и используется в России с 2005 года.
  4. КЛАДР онлайн 2016 года находится в открытом доступе.
  5. В классификаторе встречаются ошибки в кодах ОКАТО, почтовых индексах, к тому же адрес может полностью отсутствовать.
  6. Последний КЛАДР имеет семиуровневую структуру: субъект РФ, район, город, населенный пункт, улица, дом, квартира.
  7. Для ФНС и юристов новая версия КЛАДР не имеет седьмого уровня (квартира/офис), так как налоговыми органами он не используется.
  8. Отвечает за актуализацию КЛАДР ГНИВЦ ФНС РФ, в обязанности которого входит публикация классификатора и загрузка обновлений в базу данных.
  9. Несколько раз в месяц происходит обновление КЛАДР.
  10. Чтобы корректно сдать в налоговую справки 2-НДФЛ и персонифицированного учета, нужно обязательно использовать новый КЛАДР последней версии.

КЛАДР – это Классификатор адресов России. Этот справочник был разработан Федеральной налоговой службой с целью разделить территории и конкретные адреса между налоговыми инспекциями. В действие КЛАДР введен приказом ФНС России от 17.11.2005 № САЭ-3-13/594@.

КЛАДР применяется при регистрации ИП и ООО, постановке на налоговый учет, сдаче отчетности и последующем перемещении налогоплательщиков между инспекциями. При подаче документов на государственную регистрацию адрес должен быть указан так, как он внесен в Классификатор.

Структура справочника имеет семь уровней: регион (субъект РФ); район; город; населенный пункт; улица; дом; квартира.

Обратите внимание: сведения из Классификатора адресов могут содержать ошибки в написании элементов адреса. Возможно неполнота информации из-за несвоевременного отражения переподчинений населенных пунктов и переименований улиц. Кроме того, некоторые адреса, например, секретных объектов и воинских частей попросту в нем отсутствуют.

Если вы не нашли в КЛАДР свой или интересующий вас адрес, но при этом уверены, что этот адрес реально существует (например, это адрес вашей прописки), за разъяснениями надо обращаться в территориальную налоговую инспекцию, т.к. Классификатор находится в введении ФНС.

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

  • почтовый индекс;
  • номер ИФНС, в которой налогоплательщик будет состоять на учете (в некоторых случаях эта налоговая инспекция отличается от той, где происходила государственная регистрация);
  • код ОКАТО (Общероссийский классификатор объектов административно-территориального деления);
  • код ОКТМО (Общероссийский классификатор территорий муниципальных образований).

ОКТМО с 2014 года заменил ОКАТО, но учитывая, что документы с кодом ОКАТО (отчетность, платежные и банковские документы) все еще фигурируют в документообороте, иногда стоит знать и тот, и другой коды.

КЛАДР доступен для бесплатного скачивания на сайте Федеральным государственным предприятием «ГНИВЦ ФНС», но скачивать эту базу адресов не имеет смысла, т.к. она еженедельно обновляется. Гораздо удобнее воспользоваться актуальной формой поиска адресов, такой, как наша.

Время на прочтение
25 мин

Количество просмотров 4.7K

Достаточно часто при работе с вводимыми пользователем адресными данными возникает необходимость сначала подсказать ему, как правильно свой адрес указать, а потом – полученную строку адреса привести в некоторый машинно-читаемый вид.

Таким видом у нас в стране можно считать код по справочникам КЛАДР или ФИАС.

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

Давайте научимся разбирать строку адреса “туда и обратно”, а заодно познакомимся с некоторыми алгоритмическими подходами и их реализацией на SQL.

Получение справочника КЛАДР

База КЛАДР в настоящее время администрируется ФНС и представлена на сайте ГНИВЦ в виде периодически (примерно раз в неделю) обновляемого архива. Для начала мы научимся его скачивать, исправлять некоторые ошибки и преобразовывать в более подходящую для наших задач структуру.

Для этого нам понадобятся wget, p7zip, dbview, recode и psql.

Исходный архив

База выкладывается на сервере ГНИВЦ всегда по одному и тому же адресу в виде 7z-архива Base.7z – версии отличаются друг от друга только заголовком Last-Modified в ответе сервера.

При этом сервер достаточно нервно реагирует на скорость загрузки и не поддерживает HEAD-запросы, увы. Поэтому нам придется скачивать архив ежесуточно полностью, но чтобы снизить нагрузку на сервер установим ограничение скорости, а заголовки ответа сохраним и разберем отдельно:

wget -S https://gnivc.ru/html/gnivcsoft/KLADR/Base.7z --limit-rate=8k 2>.hdr

В сгенерированном .hdr нас будет интересовать эта строка:

...
  Last-Modified: Thu, 05 Aug 2021 09:28:39 GMT
...

Чтобы не пытаться обрабатывать архив и обновлять данные в нашей базе повторно, будем сохранять в ней этот таймстамп для последующих сравнений.

Если же таймстамп файла не совпал с сохраненным, распакуем полученный архив:

p7zip -d Base.7z

Результатом будет 7 DBF-файлов в DOS-кодировке:

Extracting  ALTNAMES.DBF
Extracting  DOMA.DBF
Extracting  FLAT.DBF
Extracting  KLADR.DBF
Extracting  NAMEMAP.DBF
Extracting  SOCRBASE.DBF
Extracting  STREET.DBF

Переберем все эти файлы, формируя единый скрипт выгрузки данных через psql в COPY-формате:

# получаем структуру полей DBF
#   | DOS2WIN
#   | берем только описания полей (skip 2 строки)
#   | оставляем только их имена
dbview -b -t -e -o -r ALTNAMES.DBF 
  | recode CP866..CP1251 
  | tail -n+2 
  | xargs -l 
  | egrep -io "^[a-z0-9_]+"

# ... формируем SQL-заголовок временной таблицы

# получаем данные DBF, разделенные '~'
#   | склеиваем "висящие" строки ([trn] в теле поля данных)
#   | DOS2WIN
#   | убираем все 't', убираем концевые '~', заменяем '~'->'t'
dbview -d~ -b -t ALTNAMES.DBF 
  | sed -e :a -e '/[rt]$/N; s/[rt]n//g; ta' 
  | recode CP866..CP1251 
  | sed -e 's/t//g; s/~r//g; s/~,/,/g; s/~/t/g' >>.sql

По итогу мы получаем большой-большой SQL-файл примерно такого вида:

CREATE TEMPORARY TABLE "STREET.DBF"(
  "NAME"
    varchar,
  "SOCR"
    varchar,
  "CODE"
    varchar,
  "INDEX"
    varchar,
  "GNINMB"
    varchar,
  "UNO"
    varchar,
  "OCATD"
    varchar
);
COPY "STREET.DBF"(
  "NAME",
  "SOCR",
  "CODE",
  "INDEX",
  "GNINMB",
  "UNO",
  "OCATD"
) FROM stdin;
Абадзехская     ул      01000001000000100       385013  0105            79401000000
Абрикосовая     ул      01000001000000200       385013  0105            79401000000
Авиационный     пер     01000001000000300       385006  0105            79401000000
Автодорога 7    ул      01000001000000400       385019  0105            79401000000
...

Импорт данных

Поскольку сами данные в таблицах архива представлены в DOS-кодировке, но это как-то совсем уж немодно, развернем нашу базу в WIN1251 – она тоже однобайтовая, поэтому все поиски данных не станут тяжелее:

CREATE DATABASE kladr
  WITH ENCODING='WIN1251'
    OWNER=postgres
    TEMPLATE=template0
    LC_COLLATE='ru_RU.CP1251'
    LC_CTYPE='ru_RU.CP1251'
    CONNECTION LIMIT=-1;
-- лог проверок обновлений
CREATE TABLE kladr_chk(
  id
    serial
      PRIMARY KEY,
  ts
    timestamp
      DEFAULT now(),
  hostname
    varchar
);

-- лог проведенных обновлений
CREATE TABLE kladr_upd(
  id
    serial
      PRIMARY KEY,
  ts
    timestamp
      DEFAULT now(),
  lm
    varchar,
  hostname
    varchar
);

Защита от параллельной загрузки

Поскольку импорт данных из ГНИВЦ у нас может проходить длительное время и инициироваться с разных источников (мало ли, один из инстансов прикажет долго жить), неплохо бы позаботиться о защите данных, чтобы они не пострадали – дописываем все это в начале .sql:

-- защита от автоотключения по таймауту
SET statement_timeout = 0;
-- включаем WIN-кодировку
SET client_encoding = 'WIN1251';
-- включаем application_name для мониторинга активного процесса
SET application_name = 'kladr : import [`hostname`]';
-- включаем "последовательные" транзакции
BEGIN TRANSACTION ISOLATION LEVEL SERIALIZABLE;
  -- блокируем эксклюзивно upd-таблицу для "замораживания" параллельных операций
  LOCK TABLE kladr_upd IN EXCLUSIVE MODE NOWAIT;
  -- сбрасываем информацию в таблицу протокола проверок
  INSERT INTO kladr_chk(hostname) VALUES('`hostname`');
  -- сравниваем Last-Modified загруженного файла и состояния в базе
  SELECT lm FROM kladr_upd ORDER BY id DESC LIMIT 1;
  -- если lm не совпадает
    -- для каждого DBF
    CREATE TEMPORARY TABLE ...;
    COPY ... FROM stdin;
    ...
    .
    --
    -- обновляем "версию" нашей базы
    INSERT INTO kladr_upd(lm, hostname) VALUES('$lm', '`hostname`');
COMMIT;

В результате такого подхода мы всегда знаем, кто и когда проверял наличие обновлений, и кому это удалось.

Соберем все вместе в единый скрипт:

Много bash-кода

#!/bin/sh

. `dirname "$0"`/../app.conf

echo "`date '+%F %T'` ==== Connecting to DB : $pghost:$pgport:$pgbase:$pguser"
# тестирование подключения к БД
psql -t -c 'SELECT 1' -h $pghost -p $pgport -U $pguser -w $pgbase 1>/dev/null 2>/dev/null
rv="$?"
if [ "$rv" != "0" ]; then
  echo "$pghost:$pgport:$pgbase:$pguser:$pgpass" >>~/.pgpass
  chmod 0600 ~/.pgpass
  psql -t -c 'SELECT 1' -h $pghost -p $pgport -U $pguser -w $pgbase 1>/dev/null 2>/dev/null
  rv="$?"
fi

if [ "$rv" != "0" ]; then
  echo "DB not connected : $pghost:$pgport:$pgbase:$pguser"
  exit 1
fi

# инициализация каталога _dbf
#_dbf=`mktemp -d`
mkdir ./dbf && chmod 777 ./dbf
_dbf=`readlink -f ./dbf`
rm -rf ${_dbf} 2>/dev/null
mkdir ${_dbf} 2>/dev/null
touch ${_dbf}/.sql
dir=`dirname "$0"`
dir=`readlink -f $dir`

## импорт базы КЛАДР'а в _dbf/.sql
# защита от автоотключения по таймауту
echo "SET statement_timeout = 0;" >>${_dbf}/.sql
# включаем WIN-кодировку
echo "SET client_encoding = 'WIN1251';" >>${_dbf}/.sql
# включаем application_name для мониторинга активного процесса
echo "SET application_name = 'kladr : import [`hostname`]';" >>${_dbf}/.sql
# включаем "последовательные" транзакции
echo "BEGIN TRANSACTION ISOLATION LEVEL SERIALIZABLE;" >>${_dbf}/.sql
# блокируем эксклюзивно upd-таблицу для "замораживания" параллельных операций
echo "LOCK TABLE kladr_upd IN EXCLUSIVE MODE NOWAIT;" >>${_dbf}/.sql
# сбрасываем информацию в таблицу протокола проверок
echo "INSERT INTO kladr_chk(hostname) VALUES('`hostname`');" >>${_dbf}/.sql

# инициализация временного каталога импорта
#tmp=`mktemp -d`
mkdir ./tmp && chmod 777 ./tmp
tmp=`readlink -f ./tmp`
cd $tmp

echo "`date '+%F %T'` ==== Downloading : $source"
# загрузка базы КЛАДР'а с ограничением по скорости или без
wget -S $source --limit-rate=8k 2>.hdr
# wget -S $source 2>.hdr

echo "`date '+%F %T'` ==== Comparing 'Last-Modified'"
rc=`cat .hdr | egrep 'HTTP/[0-9].[0-9] [0-9]{3}' | sed -e 's/^[ ]*HTTP/[0-9].[0-9][ ]*([0-9]*).*$/1/i' | egrep -v '301' | head -1`
lm=`cat .hdr | egrep 'Last-Modified' | sed -e 's/^[ ]*Last-Modified:[ ]*//i' | head -1`
echo "  -- HTTP code : $rc"
echo "  -- HTTP 'Last-Modified' : $lm"
pglm=`psql -h $pghost -p $pgport -U $pguser -w -t -c 'SELECT lm FROM kladr_upd ORDER BY id DESC LIMIT 1' $pgbase | sed -e 's/^[ ]*//i'`
echo "  -- PGDB 'Last-Modified' : $pglm"

if [ "$rc" = "200" ] && [ "$lm" != "" ] && [ "$lm" != "$pglm" ]; then
  # распаковка базы
  echo "`date '+%F %T'` ==== Unpacking 7z"
  p7zip -d Base.7z 1>/dev/null 2>/dev/null
  cp $tmp/* ${_dbf}
  cd $dir

  echo "`date '+%F %T'` ==== Processing DBF"
  # обработка всех .DBF
  for dbf in `find ${_dbf} -maxdepth 1 -iname '*.DBF'`; do
    dbfn=`basename $dbf | tr '[:lower:]' '[:upper:]'`
    # преобразование заголовков
    echo "  -- DBF : $dbfn"
    echo "    -- header"
    # получаем структуру полей DBF | DOS2WIN | берем только описания полей (skip 2 строки) | оставляем только их имена
    fld=`dbview -b -t -e -o -r $dbf | recode CP866..CP1251 | tail -n+2 | xargs -l | egrep -io "^[a-z0-9_]+"`
    echo "CREATE TEMPORARY TABLE "$dbfn"(" >>${_dbf}/.sql
    fl="0"
    for i in ${fld}; do
      [ "$fl" = "1" ] && echo ',' >>${_dbf}/.sql
      echo -n "  "$i"n    varchar" >>${_dbf}/.sql
      fl="1"
    done
    echo ");" >>${_dbf}/.sql
    # преобразование данных
    echo "    -- data"
    echo "COPY "$dbfn"(" >>${_dbf}/.sql
    fl="0"
    for i in ${fld}; do
      [ "$fl" = "1" ] && echo ',' >>${_dbf}/.sql
      echo -n "  "$i"" >>${_dbf}/.sql
      fl="1"
    done
    echo ") FROM stdin;" >>${_dbf}/.sql
    # получаем данные DBF, разделенные '~' | склеиваем "висящие" строки ([trn] в теле поля данных) | DOS2WIN | убираем все 't' | убираем концевые ';' | заменяем ';'->'t'
    dbview -d~ -b -t $dbf | sed -e :a -e '/[rt]$/N; s/[rt]n//g; ta' | recode CP866..CP1251 | sed -e 's/t//g; s/~r//g; s/~,/,/g; s/~/t/g' >>${_dbf}/.sql
    echo "\." >>${_dbf}/.sql
  done

  # интеграция процедуры обновления базы - последовательное подключение всех sql-файлов импорта
  ls ${dir}/import/*.sql | xargs -l readlink -f | xargs -l -I{} cat {} >>${_dbf}/.sql
  # вставка метки обновления
  echo "INSERT INTO kladr_upd(lm, hostname) VALUES('$lm', '`hostname`');" >>${_dbf}/.sql
fi
echo "COMMIT;" >>${_dbf}/.sql

cd $dir
rm -rf $tmp

echo "`date '+%F %T'` ==== Processing SQL"
psql -h $pghost -p $pgport -U $pguser -w -f ${_dbf}/.sql $pgbase
rv="$?"

if [ "$rv" = "0" ]; then
  rm -rf ${_dbf}/ 2>/dev/null
fi
echo "`date '+%F %T'` ==== Exit : $rv"

exit "$rv"

Тут можно использовать для импорта и временные каталоги, создаваемые через mktemp, но хватит ли у вас места на tmp-разделе?..

Реквизиты доступа к базе и КЛАДР-источник в нашем случае будут храниться в app.conf:

pghost="kladr.tensor.ru"
pgport="5432"
pguser="postgres"
pgpass="postgres"
pgbase="kladr"
source="https://gnivc.ru/html/gnivcsoft/KLADR/Base.7z"

Поисковая база

А зачем нам вообще нужна какая-то другая структура? Чем нас не устраивают таблицы в оригинальном КЛАДР-архиве?

  • хранение адресных объектов (улиц и населенных пунктов) в разных структурах

  • хранение статуса/версии объекта (00, 51, …) вместе с его кодом

  • невозможность наложить эффективные для поиска индексы

Напомню, что оригинальный код КЛАДР, согласно документации имеет вид СС РРР ГГГ ППП УУУУ ААгде:

  • СС – код субъекта РФ (региона)

  • РРР – код района

  • ГГГ – код города

  • ППП – код населенного пункта

  • УУУУ – код улицы (отсутствует у населенных пунктов)

  • АА – признак актуальности

То есть если вынести признак актуальности в отдельное поле, то у кодов многих объектов (например, городов) в конце окажутся нули, которые стоит безболезненно отсечь. И тогда коды будут иметь строго ограниченный набор длин в соответствии с “уровнем” объекта:

  • 2 – регионы и города федерального подчинения (77 – г. Москва, 76 – Ярославская обл.)

  • 5 – районы (76 001 – Ярославская обл., Ярославский р-н)

  • 8 – города (76 000 001 – Ярославская обл., г. Ярославль)

  • 11 – населенные пункты (76 001 000 008 – Ярославская обл., Ярославский р-н, д.Алешково)

  • 15 – улицы (76 001 000 008 0001 – Ярославская обл., Ярославский р-н, д.Алешково, ул.Малиновая)

При этом, как видим, коды “вышестоящих” объектов становятся префиксами кодов объектов вложенных.

Что же получилось по структуре?

-- основная таблица хранения адресных объектов
CREATE TABLE kladr(
  code
    varchar,
  status
    varchar,
  name
    varchar,
  abbr
    varchar,
  idx
    varchar,
  ifns
    varchar,
  ocato
    varchar,
  lvl
    smallint,
  norm
    varchar,
  PRIMARY KEY(code, status)
);
-- индекс по почтовому индексу
CREATE INDEX "kladr-idx"
  ON kladr(idx);
-- префиксный индекс по названию объекта
CREATE INDEX "kladr-nm"
  ON kladr(length(code), code varchar_pattern_ops, lower(name));

-- дома, привязанные к адресным объектам
CREATE TABLE kladr_house(
  code
    varchar,
  codeExt
    varchar,
  name
    varchar,
  idx
    varchar,
  ifns
    varchar,
  ocato
    varchar,
  PRIMARY KEY(code, codeExt)
);

-- замены и объединения объектов
CREATE TABLE kladr_repl(
  oldCode
    varchar,
  newCode
    varchar,
  PRIMARY KEY(oldCode, newCode)
);
CREATE INDEX "kladr-repl-new"
  ON kladr_repl(newCode);
CREATE INDEX "kladr-repl-old"
  ON kladr_repl(oldCode);

-- аббревиатуры административно-территориальных единиц ("ул", "пер", "г")
CREATE TABLE kladr_abbr(
  code
    varchar,
  lvl
    smallint,
  name
    varchar,
  PRIMARY KEY(code, lvl)
);
-- индекс по возможному "уровню" объекта
CREATE INDEX "kladr-abbr-lvl"
  ON kladr_abbr(lvl);

Это основные таблицы, данные в которые импортируются непосредственно из соответствующих DBF по модели наложения “диффов”, описанной в статье “DBA: грамотно организовываем синхронизации и импорты”:

kladr-abbr

--// КЛАДР : сокращения

-- создаем временную таблицу с импортируемыми данными КЛАДР'а
CREATE TEMPORARY TABLE _kladr_abbr(
  LIKE kladr_abbr INCLUDING INDEXES
);

-- заполняем преобразованными из DBF данными
INSERT INTO _kladr_abbr(code, lvl, name)
  SELECT
    "SCNAME",
    "KOD_T_ST"::smallint,
    "SOCRNAME"
  FROM
    "SOCRBASE.DBF";

-- удаляем отсутствующие
DELETE FROM
  kladr_abbr T
USING
  kladr_abbr X LEFT JOIN
  _kladr_abbr Y
    USING(code, lvl)
WHERE
  (T.code, T.lvl) = (X.code, X.lvl) AND
  Y IS NULL;

-- обновляем оставшиеся
UPDATE
  kladr_abbr kl
SET
  name = kli.name
FROM
  _kladr_abbr kli
WHERE
  (kl.code, kl.lvl) = (kli.code, kli.lvl) AND
  (
    kl.name
  ) IS DISTINCT FROM
  (
    kli.name
  );

-- очищаем совпадающие
DELETE FROM
  _kladr_abbr kli
USING
  kladr_abbr kl
WHERE
  (kli.code, kli.lvl) = (kl.code, kl.lvl);

-- вставляем оставшиеся
INSERT INTO kladr_abbr
  SELECT
    *
  FROM
    _kladr_abbr;

kladr

--// КЛАДР : от регионов до улиц

-- создаем временную таблицу с импортируемыми данными КЛАДР'а
CREATE TEMPORARY TABLE _kladr(
  LIKE kladr INCLUDING INDEXES
);

-- заполняем преобразованными из DBF данными
INSERT INTO _kladr(code, status, name, abbr, idx, ifns, ocato, lvl)
  SELECT DISTINCT ON(code, status)
    *
  FROM
    (
      SELECT
        regexp_replace(rpad(substr("CODE", 1, length("CODE") - 2), 15, '0'), '^(.{0,}?)((((0{3})?0{3})?0{3})?0{4})?$', E'\1', 'ig') code,
        substr("CODE", length("CODE") - 1, 2) status,
        "NAME",
        "SOCR",
        nullif("INDEX", ''),
        nullif("GNINMB", ''),
        nullif("OCATD", ''),
        "STATUS"
      FROM
        (
          SELECT
            "CODE",
            "NAME",
            "SOCR",
            "INDEX",
            "GNINMB",
            "OCATD",
            "STATUS"::smallint
          FROM
            "KLADR.DBF"
        UNION ALL
          SELECT
            "CODE",
            "NAME",
            "SOCR",
            "INDEX",
            "GNINMB",
            "OCATD",
            NULL::smallint "STATUS"
          FROM
            "STREET.DBF"
        ) T
    ) T;

-- удаляем отсутствующие
DELETE FROM
  kladr T
USING
  kladr X LEFT JOIN
  _kladr Y
    USING(code, status)
WHERE
  (T.code, T.status) = (X.code, X.status) AND
  Y IS NULL;

-- обновляем оставшиеся
UPDATE
  kladr kl
SET
  (
    name,
    abbr,
    idx,
    ifns,
    ocato,
    lvl
  ) =
  (
    kli.name,
    kli.abbr,
    kli.idx,
    kli.ifns,
    kli.ocato,
    kli.lvl
  )
FROM
  _kladr kli
WHERE
  (kl.code, kl.status) = (kli.code, kli.status) AND
  (
    kl.name,
    kl.abbr,
    kl.idx,
    kl.ifns,
    kl.ocato,
    kl.lvl
  ) IS DISTINCT FROM
  (
    kli.name,
    kli.abbr,
    kli.idx,
    kli.ifns,
    kli.ocato,
    kli.lvl
  );

-- очищаем совпадающие
DELETE FROM
  _kladr kli
USING
  kladr kl
WHERE
  (kli.code, kli.status) = (kl.code, kl.status);

-- вставляем оставшиеся
INSERT INTO kladr
  SELECT
    *
  FROM
    _kladr;

-- обновляем поисковый кэш
DELETE FROM
  kladr_kw
WHERE
  (code, status) IN (
    SELECT
      (ro).code,
      (ro).status
    FROM
      kladr$log
    WHERE
      ro IS DISTINCT FROM NULL
  );

INSERT INTO
  kladr_kw(code, status, keyword)
SELECT DISTINCT
  code,
  status,
  kw
FROM
  (
    SELECT
      (rn).code,
      (rn).status,
      regexp_split_to_table(lower((rn).name), E'[^\-a-zа-яё0-9]+', 'i') kw
    FROM
      kladr$log
    WHERE
      rn IS DISTINCT FROM NULL
  ) T
WHERE
  kw <> '';

DELETE FROM kladr$log;

Здесь регулярное выражение используется для отсечения “хвостовых” нулей по маске до необходимой нам длины. То есть нельзя просто так взять 76 000 010 000 и убрать все 4 последних ноля, поскольку 010 тут является значимым кодом города.

kladr-house

--// КЛАДР : дома

-- создаем временную таблицу с импортируемыми данными КЛАДР'а
CREATE TEMPORARY TABLE _kladr_house(
  LIKE kladr_house INCLUDING INDEXES
);

-- заполняем преобразованными из DBF данными
INSERT INTO _kladr_house(code, codeExt, name, idx, ifns, ocato)
  SELECT
    regexp_replace(substr("CODE", 1, 15), '^(.{0,}?)((((0{3})?0{3})?0{3})?0{4})?$', E'\1', 'ig'),
    substr("CODE", 16, 4),
    "NAME",
    nullif("INDEX", ''),
    nullif("GNINMB", ''),
    nullif("OCATD", '')
  FROM
    "DOMA.DBF";

-- удаляем отсутствующие
DELETE FROM
  kladr_house T
USING
  kladr_house X LEFT JOIN
  _kladr_house Y
    USING(code, codeExt)
WHERE
  (T.code, T.codeExt) = (X.code, X.codeExt) AND
  Y IS NULL;

-- обновляем оставшиеся
UPDATE
  kladr_house kl
SET
  (
    name,
    idx,
    ifns,
    ocato
  ) =
  (
    kli.name,
    kli.idx,
    kli.ifns,
    kli.ocato
  )
FROM
  _kladr_house kli
WHERE
  (kl.code, kl.codeExt) = (kli.code, kli.codeExt) AND
  (
    kl.name,
    kl.idx,
    kl.ifns,
    kl.ocato
  ) IS DISTINCT FROM
  (
    kli.name,
    kli.idx,
    kli.ifns,
    kli.ocato
  );

-- очищаем совпадающие
DELETE FROM
  _kladr_house kli
USING
  kladr_house kl
WHERE
  (kli.code, kli.codeExt) = (kl.code, kl.codeExt);

-- вставляем оставшиеся
INSERT INTO kladr_house
  SELECT
    *
  FROM
    _kladr_house;

-- обновляем поисковый кэш
DELETE FROM
  kladr_hs
WHERE
  (code) IN (
      SELECT
        (ro).code
      FROM
        kladr_house$log
      WHERE
        ro IS DISTINCT FROM NULL
    UNION ALL
      SELECT
        (rn).code
      FROM
        kladr_house$log
      WHERE
        rn IS DISTINCT FROM NULL
  );

-- заполняем преобразованными данными
CREATE TEMPORARY TABLE _kladr_hs0 AS
  SELECT DISTINCT ON(code, house)
    code,
    idx,
    ifns,
    ocato,
    unnest(houses) house
  FROM
    (
      SELECT
        *,
        CASE
          WHEN _range IS NULL AND name ~ E'_' THEN ARRAY[regexp_replace(name, '_', '-')]
          WHEN _range IS NULL THEN ARRAY[name]
          WHEN _range IS NOT NULL THEN ARRAY(
            SELECT
              i::text
            FROM
              generate_series(_range[1]::integer + CASE WHEN _range[4] IS NOT NULL THEN (_range[1]::integer + _range[4]::integer) % 2 ELSE 0 END, _range[2]::integer, _range[3]::integer) i
          )
          ELSE NULL
        END houses
      FROM
        (
          SELECT
            code,
            idx,
            ifns,
            ocato,
            name,
            CASE
              WHEN name ~ E'^Н\(\d+-\d+\)$' THEN regexp_split_to_array(substr(name, 3, length(name) - 3), '-') || '2'::text || '1'::text
              WHEN name ~ E'^Ч\(\d+-\d+\)$' THEN regexp_split_to_array(substr(name, 3, length(name) - 3), '-') || '2'::text || '0'::text
              WHEN name = 'Н' THEN '{1,999,2}'::text[]
              WHEN name = 'Ч' THEN '{2,998,2}'::text[]
              WHEN name ~ E'^\d+-\d+$' THEN regexp_split_to_array(name, '-') || '1'::text
              ELSE NULL
            END _range
          FROM
            (
              SELECT
                code,
                idx,
                ifns,
                ocato,
                unnest(regexp_split_to_array(upper(name), ',')) "name"
              FROM
                kladr_house
              WHERE
                (code) IN (
                    SELECT
                      (ro).code
                    FROM
                      kladr_house$log
                    WHERE
                      ro IS DISTINCT FROM NULL
                  UNION ALL
                    SELECT
                      (rn).code
                    FROM
                      kladr_house$log
                    WHERE
                      rn IS DISTINCT FROM NULL
                )
            ) T
        ) T
    ) T
ORDER BY
  code, house, (_range IS NULL) DESC;

CREATE INDEX ON _kladr_hs0(code, house, idx DESC NULLS LAST);

CREATE TEMPORARY TABLE _kladr_hs1 AS
  SELECT DISTINCT ON (code, house)
    code,
    idx,
    ifns,
    ocato,
    house
  FROM
    _kladr_hs0
  ORDER BY
    code, house, idx DESC NULLS LAST;

CREATE INDEX ON _kladr_hs1(code, house);

CREATE TEMPORARY TABLE _kladr_hs2 AS
  SELECT
    code,
    coalesce(
      idx, 
      coalesce(
        (
          SELECT
            idx
          FROM
            _kladr_hs1
          WHERE
            (code, house) = (T.code, regexp_replace(T.house, E'^(\d+)(\D)?.*$', E'\1', 'ig'))
          LIMIT 1
        ), 
        coalesce(
          (
            SELECT
              idx
            FROM
              kladr
            WHERE
              code IN (
                substr(T.code, 1, 15),
                substr(T.code, 1, 11),
                substr(T.code, 1,  8),
                substr(T.code, 1,  5),
                substr(T.code, 1,  2)
              ) AND
--              status = '00' AND
              idx IS NOT NULL
            ORDER BY
              length(code) DESC
            LIMIT 1
          ),
          ''
        )
      )
    ) idx,
    ifns,
    ocato,
    house
  FROM
    _kladr_hs1 T;

CREATE INDEX ON _kladr_hs2(code, idx, ifns, ocato, house);

INSERT INTO kladr_hs(code, idx, ifns, ocato, houses)
  SELECT
    code,
    idx,
    ifns,
    ocato,
    array_agg(house ORDER BY house) houses
  FROM
    _kladr_hs2
  GROUP BY
    1, 2, 3, 4;

DELETE FROM kladr_house$log;

Здесь регулярными выражениями мы приводим форматы исходной базы в списки конкретных номеров домов:

  • 5-11 -> {5, 6, 7, 8, 9, 10, 11}

  • Н(5-11) -> {5, 7, 9, 11}

  • Ч(6-10) -> {6, 8, 10}

  • Н -> {1, 3, ..., 999}

  • Ч -> {2, 4, ..., 998}

kladr-repl

--// КЛАДР : замены

-- создаем временную таблицу с импортируемыми данными КЛАДР'а
CREATE TEMPORARY TABLE _kladr_repl(
  LIKE kladr_repl INCLUDING INDEXES
);

-- заполняем преобразованными из DBF данными
INSERT INTO _kladr_repl(oldCode, newCode)
  SELECT DISTINCT
    co,
    cn
  FROM
    (
      SELECT
        regexp_replace(rpad(co, 15, '0'), '^(.{0,}?)((((0{3})?0{3})?0{3})?0{4})?$', E'\1', 'ig') co,
        so,
        regexp_replace(rpad(cn, 15, '0'), '^(.{0,}?)((((0{3})?0{3})?0{3})?0{4})?$', E'\1', 'ig') cn,
        sn
      FROM
        (
          SELECT
            *,
            substr("OLDCODE", 1, length("OLDCODE") - 2) co,
            substr("OLDCODE", length("OLDCODE") - 1, 2) so,
            substr("NEWCODE", 1, length("NEWCODE") - 2) cn,
            substr("NEWCODE", length("NEWCODE") - 1, 2) sn
          FROM
            "ALTNAMES.DBF"
        ) T
    ) T;

-- удаляем отсутствующие
DELETE FROM
  kladr_repl T
USING
  kladr_repl X LEFT JOIN
  _kladr_repl Y
    USING(oldCode, newCode)
WHERE
  (T.oldCode, T.newCode) = (X.oldCode, X.newCode) AND
  Y IS NULL;

-- очищаем совпадающие
DELETE FROM
  _kladr_repl kli
USING
  kladr_repl kl
WHERE
  (kli.oldCode, kli.newCode) = (kl.oldCode, kl.newCode);

-- вставляем оставшиеся
INSERT INTO kladr_repl
  SELECT
    *
  FROM
    _kladr_repl;

Тут мы использовали еще две “производные” таблицы, которые выступают в качестве поисковых индексов – kladr_kw и kladr_hs:

-- ключевые слова адресных объектов
CREATE TABLE kladr_kw(
  code
    varchar,
  status
    varchar,
  keyword
    varchar
);
CREATE INDEX "kladr-kw-cd"
  ON kladr_kw(code, status);
CREATE INDEX "kladr-kw-kwcd"
  ON kladr_kw(keyword varchar_pattern_ops, code varchar_pattern_ops);
CREATE INDEX "kladr-kw-cdkw"
  ON kladr_kw(code varchar_pattern_ops, keyword varchar_pattern_ops);

-- группы номеров домов с одинаковыми признаками
CREATE TABLE kladr_hs(
  code
    varchar,
  idx
    varchar,
  ifns
    varchar,
  ocato
    varchar,
  houses
    varchar[]
);
CREATE INDEX "kladr-hs-code"
  ON kladr_hs(code);
CREATE INDEX "kladr-hs-idx"
  ON kladr_hs(idx);

-- специальные "уровневые" индексы
DO $$
DECLARE
  ln integer[] = '{2,5,8,11,15}'::integer[];
BEGIN
  FOR i IN 1..array_length(ln, 1) LOOP
    EXECUTE '
CREATE INDEX "kladr-' || lpad(ln[i]::text, 2, '0') || '"
  ON kladr(code varchar_pattern_ops, status)
WHERE
  length(code) = ' || ln[i] || ';
CREATE INDEX "kladr-kw-' || lpad(ln[i]::text, 2, '0') || '"
  ON kladr_kw(keyword varchar_pattern_ops)
WHERE
  length(code) = ' || ln[i] || ';
CREATE INDEX "kladr-kw-kwcd-' || lpad(ln[i]::text, 2, '0') || '"
  ON kladr_kw(keyword varchar_pattern_ops, code varchar_pattern_ops)
WHERE
  length(code) = ' || ln[i] || ';
    ';
  END LOOP;
END$$ LANGUAGE plpgsql;

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

normalize.sql

CREATE OR REPLACE FUNCTION normalize(obj kladr) RETURNS text AS $$
  SELECT
    CASE
      -- регионы
      WHEN length(code) = 2 THEN
        regexp_replace(
          regexp_replace(
            (
              CASE
                WHEN abbr = 'Респ' AND name !~* '(ая)$' THEN
                  abbrfull || ' ' || name
                WHEN abbr = 'край' THEN
                  name || ' ' || abbr
                WHEN abbr = 'обл' THEN
                  name || ' ' || abbr || '.'
                WHEN abbr = 'г' THEN
                  abbr || '.' || name
                WHEN abbr IN ('АО', 'Аобл') THEN
                  regexp_replace(name, ' Автономный округ', '', 'ig') || ' АО'
                ELSE
                  name || ' ' || abbrfull
              END
            ),
            E'/([а-я]+)/',
            E'- \1',
            'ig'
          ),
          E'^(.{0,}?) - ([а-я]+)(.{0,}?)$',
          E'\1\3 (\2)',
          'ig'
        )
      -- районы
      WHEN length(code) = 5 THEN
        CASE
          -- (14xxx) улус
          WHEN abbr = 'у' THEN
            initcap(name) || ' ' || lower(abbrfull)
          -- (24048)
          WHEN abbr = 'АО' THEN
            initcap(name) || ' ' || abbr
          ELSE
            initcap(name) || ' ' || abbr || (CASE WHEN abbr ~ '(-|/)' THEN '' ELSE '.' END)
        END
      -- города и населенные пункты
      WHEN length(code) IN (8, 11) THEN
        CASE
          WHEN abbr IN ('тер', 'г', 'с/п', 'дп') THEN
            regexp_replace(
              regexp_replace(
                CASE
                  WHEN name ~* E'^ДНП ' THEN
                    regexp_replace(name, E'^ДНП ', 'днп ', 'ig')
                  WHEN name ~* E'^ДНТ ' THEN
                    regexp_replace(name, E'^ДНТ ', 'днт ', 'ig')
                  WHEN name ~* E'^ДСК ' THEN
                    regexp_replace(name, E'^ДСК ', 'дск ', 'ig')
                  WHEN name ~* E'^ДСПК ' THEN
                    regexp_replace(name, E'^ДСПК ', 'дспк ', 'ig')
                  WHEN name ~* E'^ДПК ' THEN
                    regexp_replace(name, E'^ДПК ', 'дпк ', 'ig')
                  WHEN name ~* E'^НСТ ' THEN
                    regexp_replace(name, E'^НСТ ', 'нст ', 'ig')
                  WHEN name ~* E'^СДТ ' THEN
                    regexp_replace(name, E'^СДТ ', 'сдт ', 'ig')
                  WHEN name ~* E'^СК ' THEN
                    regexp_replace(name, E'^СК ', 'ск ', 'ig')
                  WHEN name ~* E'^С/К ' THEN
                    regexp_replace(name, E'^С/К ', 'ск ', 'ig')
                  WHEN name ~* E'^СКТ ' THEN
                    regexp_replace(name, E'^СКТ ', 'скт ', 'ig')
                  WHEN name ~* E'^СТ ' THEN
                    regexp_replace(name, E'^СТ ', 'ст ', 'ig')
                  WHEN name ~* E'^С/Т ' THEN
                    regexp_replace(name, E'^С/Т ', 'ст ', 'ig')
                  WHEN name ~* E'^СНТ ' THEN
                    regexp_replace(name, E'^СНТ ', 'снт ', 'ig')
                  WHEN name ~* E'^ПКС ' THEN
                    regexp_replace(name, E'^ПКС ', 'пкс ', 'ig')
                  WHEN name ~* E'^ПКСТ ' THEN
                    regexp_replace(name, E'^ПКСТ ', 'пкст ', 'ig')
                  WHEN name ~* E'^ПОС ' THEN
                    regexp_replace(name, E'^ПОС ', 'пос ', 'ig')
                  WHEN name ~* E'^массив ' THEN
                    regexp_replace(name, E'^массив ', 'массив ', 'ig')
                  WHEN name ~* E' СНТ$' THEN
                    regexp_replace(name, E'^(.*) СНТ$', E'снт \1', 'ig')
                  WHEN name ~* E' СТ$' THEN
                    regexp_replace(name, E'^(.*) СТ$', E'ст \1', 'ig')
                  WHEN name ~* E' ДНТ$' THEN
                    regexp_replace(name, E'^(.*) ДНТ$', E'днт \1', 'ig')
                  WHEN name ~* E' ДПК$' THEN
                    regexp_replace(name, E'^(.*) ДПК$', E'дпк \1', 'ig')
                  WHEN name ~* E'^уч-к СДТ ' THEN
                    regexp_replace(name, E'^уч-к СДТ ', 'сдт ', 'ig')
                  WHEN name ~* E'^уч-к ' THEN
                    regexp_replace(name, E'^уч-к ', '', 'ig')
                  WHEN name ~* E'^СПК ' THEN
                    regexp_replace(name, E'^СПК ', 'спк ', 'ig')
                  WHEN name ~* E'^сот ' THEN
                    regexp_replace(name, E'^сот ', 'сот ', 'ig')
                  WHEN name ~* E'^урочище ' THEN
                    regexp_replace(name, E'^урочище ', 'урочище ', 'ig')
                  WHEN name ~* E'^КС ' THEN
                    regexp_replace(name, E'^КС ', 'кс ', 'ig')
                  WHEN name ~* E'^МО пгт.' THEN
                    regexp_replace(name, E'^МО пгт.', 'пгт ', 'ig')
                  WHEN name ~* E'^МО город ' THEN
                    regexp_replace(name, E'^МО город ', 'г.', 'ig')
                  WHEN name ~* E'^МО городское поселение ' THEN
                    regexp_replace(name, E'^МО городское поселение ', 'г/п ', 'ig')
                  WHEN name ~* E'^МО поселок ' THEN
                    regexp_replace(name, E'^МО поселок ', 'пос.', 'ig')
                  WHEN name ~* E'^МО СП ' THEN
                    regexp_replace(name, E'^МО СП (.*)', E'с/п \1', 'ig')
                  WHEN name ~* E'^МО ГП поселок ' THEN
                    regexp_replace(name, E'^МО ГП поселок (.*)', E'пгт \1', 'ig')
                  WHEN name ~* E'^МО сельское поселение ' THEN
                    regexp_replace(name, E'^МО сельское поселение (.*)', E'с/п \1', 'ig')
                  WHEN name ~* E'^МО селское поселение ' THEN
                    regexp_replace(name, E'^МО селское поселение (.*)', E'с/п \1', 'ig')
                  WHEN name ~* E'^МО (.*) сельское поселение$' THEN
                    regexp_replace(name, E'^МО (.*) сельское поселение$', E'с/п \1', 'ig')
                  WHEN name ~* ' сельское поселение$' THEN
                    regexp_replace(name, '^(.*) сельское поселение$', E'с/п \1', 'ig')
                  WHEN name ~* ' селское поселение$' THEN
                    regexp_replace(name, '^(.*) селское поселение$', E'с/п \1', 'ig')
                  WHEN name ~* ' городское поселение$' THEN
                    regexp_replace(name, '^(.*) городское поселение$', E'г/п \1', 'ig')
                  WHEN name ~* ' городской округ$' THEN
                    regexp_replace(name, '^(.*) городской округ$', E'\1 г/о', 'ig')
                  WHEN name ~* '^Городское поселение ' THEN
                    regexp_replace(name, '^Городское поселение ', E'г/п ', 'ig')
                  ELSE
                    CASE
                      WHEN abbr IN ('с/п', 'дп') THEN abbr || ' ' || name
                      ELSE abbr || '.' || name
                    END
                END,
                E'^(г/п|с/п) (.*(?:ое))$',
                E'\2 \1',
                'ig'
              ),
              E'(\S)\(',
              E'\1 (',
              'ig'
            )
          WHEN abbr IN ('высел') THEN
            CASE
              WHEN name ~ E'(.*((?:ие)))$' THEN
                name || ' выселки'
              ELSE
                'выселок ' || name
            END
          WHEN abbr IN ('городок') THEN
            CASE
              WHEN name ~* E'(^городок|городок$)' THEN
                name
              WHEN name ~* E'военный' THEN
                regexp_replace(name, E'(военный)(\s?)', E'\1 городок\2', 'ig')
              ELSE
                abbr || ' ' || name
            END
          WHEN abbr IN ('с/а', 'с/мо') THEN
            regexp_replace(abbr || ' ' || name, E'^(' || abbr || ') (.*((?:ая)|(?:ое)|(?:ий)|(?:ый)))$', E'\2 \1', 'ig')
          WHEN abbr IN ('п/о', 'пгт', 'массив') THEN abbr || ' ' || name
          WHEN abbr = 'с/с' THEN (
            CASE
              WHEN name ~* E'^Рабочий поселок' THEN regexp_replace(name, E'^Рабочий поселок', 'рабочий поселок', 'ig')
              WHEN name ~* E'^р\.п\.' THEN regexp_replace(name, E'^р\.п\.', 'рабочий поселок', 'ig')
              ELSE name || ' ' || abbr
            END
          )
          WHEN abbr = 'ж/д_будка' THEN
            'ж/д будка ' || regexp_replace(name, E'(будка|будка железной дороги|железнодорожная будка)\s*', E'', 'ig')
          WHEN abbr = 'ж/д_казарм' THEN
            'ж/д казарма ' || regexp_replace(name, E'(казарма)\s*', E'', 'ig')
          WHEN abbr = 'ж/д_оп' THEN
            'ж/д о/п ' || name
          WHEN abbr = 'ж/д_платф' THEN
            'ж/д платформа ' || regexp_replace(name, E'(платформа|платформа железной дороги|железнодорожная платформа)\s*', E'', 'ig')
          WHEN abbr = 'ж/д_пост' THEN
            CASE
              WHEN name ~* E'^Блокпост' THEN
                name
              WHEN name ~* E'^(железнодорожная )?будка' THEN
                regexp_replace(name, E'^(железнодорожная )?будка', E'ж/д будка', 'ig')
              WHEN name ~* E'^разъезд ' THEN
                regexp_replace(name, E'^разъезд', E'ж/д разъезд', 'ig')
              ELSE
                'ж/д пост ' || name
            END
          WHEN abbr = 'ж/д_рзд' THEN
            'ж/д разъезд ' || name
          WHEN abbr = 'ж/д_ст' THEN
            'ж/д станция ' || name
          WHEN abbr = 'казарма' THEN
            CASE
              WHEN name ~* E'^Блокпост' THEN
                name
              WHEN name ~* E'^железнодорожная\s+' THEN
                regexp_replace(name, E'^железнодорожная\s+', E'ж/д казарма ', 'ig')
              ELSE
                'казарма ' || regexp_replace(name, E'^казарма\s*', E'', 'ig')
            END
          WHEN abbr = 'мкр' THEN
            CASE
              WHEN name ~* E'^(.*-й|\d+)$' THEN
                name || ' ' || lower(abbrfull)
              WHEN name ~* E'^(\d+[а-я])$' THEN
                lower(abbrfull) || ' ' || name
              ELSE
                abbr || '.' || name
            END
          WHEN abbr IN ('нп','с','д','тер','п','жилзона','жилрайон','кв-л','х','ст-ца','рп','рзд','м','ст','у','снт','сл') THEN
            CASE
              WHEN name ~* E'^остров\s+' THEN
                regexp_replace(name, E'^остров', E'остров', 'ig')
              WHEN name ~* E'^казарма\s+' THEN
                regexp_replace(name, E'^казарма', E'казарма', 'ig')
              WHEN name ~* E'^кордон\s+' THEN
                regexp_replace(name, E'^кордон', E'кордон', 'ig')
              WHEN name ~* E'^жилой район ' THEN
                regexp_replace(name, E'^жилой район ', 'жилой район ', 'ig')
              WHEN name ~* E' жилой район$' THEN
                regexp_replace(name, E'^(.*) жилой район$', E'жилой район \1', 'ig')
              WHEN name ~* E'^жилая зона ' THEN
                regexp_replace(name, E'^жилая зона ', 'жилая зона ', 'ig')
              WHEN name ~* E' жилая зона$' THEN
                regexp_replace(name, E'^(.*) жилая зона$', E'жилая зона \1', 'ig')
              WHEN name ~* E'^д/о ' THEN
                regexp_replace(name, E'^д/о ', 'д/о ', 'ig')
              WHEN name ~* E'^п/о ' THEN
                regexp_replace(name, E'^п/о ', 'п/о ', 'ig')
              WHEN name ~* E'^п/л ' THEN
                regexp_replace(name, E'^п/л ', 'п/л ', 'ig')
              WHEN name ~* E'^т/б ' THEN
                regexp_replace(name, E'^т/б ', 'т/б ', 'ig')
              WHEN name ~* E'^СНТ ' THEN
                regexp_replace(name, E'^СНТ ', 'снт ', 'ig')
              WHEN name ~* E' СНТ$' THEN
                regexp_replace(name, E'^(.*) СНТ$', E'снт \1', 'ig')
              WHEN name ~* E'^СДТ ' THEN
                regexp_replace(name, E'^СДТ ', 'сдт ', 'ig')
              WHEN name ~* E' СДТ$' THEN
                regexp_replace(name, E'^(.*) СДТ$', E'сдт \1', 'ig')
              WHEN name ~* E'^СТ ' THEN
                regexp_replace(name, E'^СТ ', 'ст ', 'ig')
              WHEN name ~* E' СТ$' THEN
                regexp_replace(name, E'^(.*) СТ$', E'ст \1', 'ig')
              WHEN name ~* E'^ДНТ ' THEN
                regexp_replace(name, E'^ДНТ ', 'днт ', 'ig')
              WHEN name ~* E' ДНТ$' THEN
                regexp_replace(name, E'^(.*) ДНТ$', E'днт \1', 'ig')
              WHEN name ~* E'^ДНП ' THEN
                regexp_replace(name, E'^ДНП ', 'днп ', 'ig')
              WHEN name ~* E' ДНП$' THEN
                regexp_replace(name, E'^(.*) ДНП$', E'днп \1', 'ig')
              WHEN name ~* E'^будка( (ж/д|железной дороги))?' THEN
                regexp_replace(name, E'^будка( (ж/д|железной дороги))?', E'ж/д будка', 'ig')
              WHEN name ~* E'^железнодорожная будка' THEN
                regexp_replace(name, E'^железнодорожная будка', E'ж/д будка', 'ig')
              WHEN name ~* E'^платф.+' THEN
                regexp_replace(name, E'^(платф)\s+', E'платформа ', 'ig')
              WHEN name ~* E'^поселок.+' THEN
                regexp_replace(name, E'^(поселок)\s+', E'п.', 'ig')
              WHEN name ~* E'^(рзд|разъезд).+' THEN
                regexp_replace(name, E'^(рзд|разъезд)\s+', E'разъезд ', 'ig')
              WHEN name ~* E'^участок.+' THEN
                regexp_replace(name, E'^(участок)\s+', E'участок ', 'ig')
              WHEN name ~* E'^(урочище|ур-ще) ' THEN
                regexp_replace(name, E'^(урочище|ур-ще) ', 'урочище ', 'ig')
--              WHEN name ~* E'^ж/д' THEN
--                name
              ELSE
                CASE
                  WHEN abbr IN ('с', 'д', 'тер', 'п') THEN
                    abbr || '.' || name
                  WHEN abbr IN ('жилзона','жилрайон','кв-л','х','ст-ца','рп','рзд','м','ст','у') THEN
                    lower(abbrfull) || ' ' || name
                  WHEN abbr IN ('снт') THEN
                    abbr || ' ' || name
                  WHEN abbr IN ('сл') THEN
                    regexp_replace(lower(abbrfull) || ' ' || name, E'^(' || lower(abbrfull) || ') (.*((?:ая)))$', E'\2 \1', 'ig')
                  WHEN abbr IN ('нп') THEN
                    'н/п ' || name
                  ELSE
                    name
                END
            END
          ELSE
            CASE
              WHEN length(code) = 8 THEN
                name || ' ' || abbr
              ELSE
                abbr || ' ' || name
            END
        END
      -- улицы
      WHEN length(code) = 15 THEN
        CASE
          WHEN abbr IN ('высел') THEN
            CASE
              WHEN name ~ E'(.*((?:ие)))$' THEN
                name || ' выселки'
              ELSE
                'выселок ' || name
            END
          WHEN abbr IN ('гск') THEN
            CASE
              WHEN name ~* E'^ГСК ' THEN
                regexp_replace(name, E'^ГСК ', 'гск ', 'ig')
              WHEN name ~* E'^Г СК ' THEN
                regexp_replace(name, E'^Г СК ', 'гск ', 'ig')
              ELSE
                lower(abbr) || ' ' || name
            END
          WHEN abbr IN ('ж/д_будка') THEN
            CASE
              WHEN name ~* E'^((железнодорожная )?будка|ждб|ж-д будка)' THEN
                regexp_replace(name, E'^((железнодорожная )?будка|ждб|ж-д будка)', E'ж/д будка', 'ig')
              WHEN name ~* E'^железнодорожная\s+' THEN
                regexp_replace(name, E'^железнодорожная', E'ж/д будка', 'ig')
              WHEN name ~* E'^железнодорожная$' THEN
                regexp_replace(name, E'^железнодорожная$', E'Железнодорожная будка', 'ig')
              ELSE
                'ж/д будка ' || name
            END
          WHEN abbr IN ('ж/д_казарм') THEN
            CASE
              WHEN name ~* E'^казарма\s+' THEN
                regexp_replace(name, E'^казарма', E'ж/д казарма', 'ig')
              ELSE
                'ж/д казарма ' || name
            END
          WHEN abbr IN ('ж/д_оп') THEN
            'ж/д о/п ' || name
          WHEN abbr = 'ж/д_платф' THEN
            'ж/д платформа ' || regexp_replace(name, E'(платформа|платформа железной дороги|железнодорожная платформа)\s*', E'', 'ig')
          WHEN abbr IN ('аал','аул') THEN
            lower(abbrfull) || ' ' || name
          WHEN abbr = 'ж/д_пост' THEN
            CASE
              WHEN name ~* E'^Блокпост' THEN
                name
              WHEN name ~* E'^(железнодорожная )?будка' THEN
                regexp_replace(name, E'^(железнодорожная )?будка', E'ж/д будка', 'ig')
              WHEN name ~* E'^разъезд ' THEN
                regexp_replace(name, E'^разъезд', E'ж/д разъезд', 'ig')
              ELSE
                'ж/д пост ' || name
            END
          WHEN abbr = 'ж/д_рзд' THEN
            'ж/д разъезд ' || name
          WHEN abbr = 'ж/д_ст' THEN
            'ж/д станция ' || name
          WHEN abbr = 'жт' THEN
            CASE
              WHEN name ~* '^животноводческая' THEN
                name
              WHEN name ~* E'^точка\s' THEN
                lower(abbrfull) || ' ' || regexp_replace(name, E'^точка ', E'', 'ig')
               WHEN name ~* E'^ферма ' THEN
                 regexp_replace(name, E'^ферма ', 'ферма ', 'ig')
               WHEN name ~* E' ферма$' THEN
                 regexp_replace(name, E'^(.*) ферма$', E'ферма \1', 'ig')
              ELSE
                regexp_replace(lower(abbrfull) || ' ' || name, E'^(' || lower(abbrfull) || ') (.*((?:ая)|(?:-я)))$', E'\2 \1', 'ig')
            END
          WHEN abbr = 'зона' THEN
            regexp_replace(
              CASE
                 WHEN name ~* E'^зона ' THEN
                   regexp_replace(name, E'^зона ', 'зона ', 'ig')
                 WHEN name ~* E' зона$' THEN
                   name
                 WHEN name ~* E'^промзона$' THEN
                   name
                 WHEN name ~* E' промзона$' THEN
                   name
                ELSE
                  regexp_replace(lower(abbrfull) || ' ' || name, E'^(' || lower(abbrfull) || ') (.*((?:ая)|(?:-я)))$', E'\2 \1', 'ig')
              END,
              E' промышленная зона$',
              E' промзона',
              'ig'
            )
          WHEN abbr = 'казарма' THEN
            CASE
              WHEN name ~* E'^Блокпост' THEN
                name
              WHEN name ~* E'^железнодорожная\s+' THEN
                regexp_replace(name, E'^железнодорожная\s+', E'ж/д казарма ', 'ig')
              ELSE
                'казарма ' || regexp_replace(name, E'^казарма\s*', E'', 'ig')
            END
          WHEN abbr = 'км' THEN
            CASE
              WHEN name ~* E'(\d|-й)$' THEN
                name || ' ' || abbr
              WHEN name ~* E'^([0-9\.]+)( )?' THEN
                regexp_replace(name, E'^([0-9\.]+)( )?(км( |$))?', E'\1 км ', 'ig')
              ELSE
                name
            END
          WHEN abbr = 'мкр' THEN
            CASE
              WHEN name ~* E'^(.*-й|\d+)$' THEN
                name || ' ' || lower(abbrfull)
              WHEN name ~* E'^(\d+[а-я])$' THEN
                lower(abbrfull) || ' ' || name
              ELSE
                abbr || '.' || name
            END
          WHEN abbr = 'остров' THEN
            CASE
              WHEN name ~* E'(.*-й$|\d+)$' THEN
                name || ' ' || lower(abbrfull)
              ELSE
                lower(abbrfull) || ' ' || name
            END
          WHEN abbr IN ('лпх','п/о','п/р','п/ст','промзона') THEN
            regexp_replace(lower(abbr) || ' ' || name, E'^(' || lower(abbr) || ') (.*((?:ая)|(?:ое)|(?:ой)|(?:ий)|(?:ый)|(?:-й)|(?:-я)))$', E'\2 \1', 'ig')
          WHEN abbr IN ('сад','б-р') THEN
            CASE
              WHEN name ~* E'^(.*-й|.*-я|\d+)$' THEN
                name || ' ' || lower(abbrfull)
              ELSE
                regexp_replace(lower(abbr) || ' ' || name, E'^(' || lower(abbr) || ') (.*((?:ой)|(?:ий)|(?:ый)|(?:-й)))$', E'\2 \1', 'ig')
            END
          WHEN abbr IN ('пер','пл','туп') THEN
            CASE
              WHEN name ~* E'^(.*-й|.*-я|\d+)$' THEN
                name || ' ' || lower(abbrfull)
              ELSE
                regexp_replace(lower(abbr) || '.' || name, E'^(' || lower(abbr) || E'\.)(.*((?:ая)|(?:ое)|(?:ой)|(?:ий)|(?:ый)|(?:-й)|(?:-я)))$', E'\2 \1', 'ig')
            END
          WHEN abbr IN ('х','ш') THEN
              CASE
                WHEN name ~* E'^(.*-й|.*-я|\d+)$' THEN
                  name || ' ' || lower(abbrfull)
                ELSE
                  regexp_replace(lower(abbr) || '.' || regexp_replace(name, E' ' || lower(abbrfull) || '$', E'', 'ig'), E'^(' || lower(abbr) || E'\.)(.*((?:ая)|(?:ое)|(?:ой)|(?:ий)|(?:ый)|(?:-й)|(?:-я)))$', E'\2 ' || lower(abbrfull), 'ig')
              END
          WHEN abbr IN ('пр-кт') THEN
            CASE
              WHEN name ~* E'^(.*-й|.*-я|\d+)$' THEN
                name || ' ' || lower(abbrfull)
              ELSE
                regexp_replace(lower('пр-т') || ' ' || name, E'^(' || lower('пр-т') || E')\s(.*((?:ой)|(?:ий)|(?:ый)|(?:-й)|(?:-я)))$', E'\2 \1', 'ig')
            END
          WHEN abbr IN ('проезд') THEN
            CASE
              WHEN name ~* E'^(.*-й|.*-я|\d+)$' THEN
                name || ' ' || lower(abbrfull)
              ELSE
                regexp_replace(lower('пр-д') || ' ' || name, E'^(' || lower('пр-д') || E')\s(.*((?:ой)|(?:ий)|(?:ый)|(?:-й)|(?:-я)))$', E'\2 \1', 'ig')
            END
          WHEN abbr IN ('стр') THEN
            CASE
              WHEN name ~* E'^\d+-е$' THEN
                name || ' ' || lower(abbrfull)
              WHEN name ~* E'^\d+(-[а-я])?$' THEN
                lower(abbrfull) || ' ' || name
              ELSE
                name
            END
          WHEN abbr IN ('аллея','берег','бугор','вал','въезд','городок','дор','заезд','канал','кв-л','кольцо','коса','линия','мост','парк','переезд','платф','пл-ка','полустанок','просек','просека','проселок','проток','проулок','рзд','ряды','сквер','спуск','тракт','уч-к','ферма') THEN
            CASE
              WHEN abbr = 'дор' AND name ~* '^(а/д|автодорога)' THEN
                regexp_replace(name, E'^(а/д|автодорога)\s', E'а/д ', 'ig')
              WHEN name ~* E'^(.*-й|.*-я|\d+)$' THEN
                name || ' ' || lower(abbrfull)
              ELSE
                regexp_replace(lower(abbrfull) || ' ' || name, E'^(' || lower(abbrfull) || ') (.*((?:ая)|(?:ое)|(?:ой)|(?:ий)|(?:ый)|(?:ые)|(?:-й)|(?:-я)))$', E'\2 \1', 'ig')
            END
          WHEN abbr IN ('ул') THEN
            replace(abbr, '_', ' ') || (CASE WHEN abbr ~ '(-|/)' THEN '' ELSE '.' END) || '' || name
          WHEN abbr IN ('нп','с','д','тер','п','жилзона','жилрайон','х','ст-ца','рп','рзд','м','ст','у','снт','сл') THEN
            CASE
              WHEN name ~* E'^остров\s+' THEN
                regexp_replace(name, E'^остров', E'остров', 'ig')
              WHEN name ~* E'^казарма\s+' THEN
                regexp_replace(name, E'^казарма', E'казарма', 'ig')
              WHEN name ~* E'^кордон\s+' THEN
                regexp_replace(name, E'^кордон', E'кордон', 'ig')
              WHEN name ~* E'^жилой район ' THEN
                regexp_replace(name, E'^жилой район ', 'жилой район ', 'ig')
              WHEN name ~* E' жилой район$' THEN
                regexp_replace(name, E'^(.*) жилой район$', E'жилой район \1', 'ig')
              WHEN name ~* E'^жилая зона ' THEN
                regexp_replace(name, E'^жилая зона ', 'жилая зона ', 'ig')
              WHEN name ~* E' жилая зона$' THEN
                regexp_replace(name, E'^(.*) жилая зона$', E'жилая зона \1', 'ig')
              WHEN name ~* E'^д/о ' THEN
                regexp_replace(name, E'^д/о ', 'д/о ', 'ig')
              WHEN name ~* E'^п/о ' THEN
                regexp_replace(name, E'^п/о ', 'п/о ', 'ig')
              WHEN name ~* E'^п/л ' THEN
                regexp_replace(name, E'^п/л ', 'п/л ', 'ig')
              WHEN name ~* E'^т/б ' THEN
                regexp_replace(name, E'^т/б ', 'т/б ', 'ig')
              WHEN name ~* E'^СНТ ' THEN
                regexp_replace(name, E'^СНТ ', 'снт ', 'ig')
              WHEN name ~* E' СНТ$' THEN
                regexp_replace(name, E'^(.*) СНТ$', E'снт \1', 'ig')
              WHEN name ~* E'^СДТ ' THEN
                regexp_replace(name, E'^СДТ ', 'сдт ', 'ig')
              WHEN name ~* E' СДТ$' THEN
                regexp_replace(name, E'^(.*) СДТ$', E'сдт \1', 'ig')
              WHEN name ~* E'^СТ ' THEN
                regexp_replace(name, E'^СТ ', 'ст ', 'ig')
              WHEN name ~* E' СТ$' THEN
                regexp_replace(name, E'^(.*) СТ$', E'ст \1', 'ig')
              WHEN name ~* E'^ДНТ ' THEN
                regexp_replace(name, E'^ДНТ ', 'днт ', 'ig')
              WHEN name ~* E' ДНТ$' THEN
                regexp_replace(name, E'^(.*) ДНТ$', E'днт \1', 'ig')
              WHEN name ~* E'^ДНП ' THEN
                regexp_replace(name, E'^ДНП ', 'днп ', 'ig')
              WHEN name ~* E' ДНП$' THEN
                regexp_replace(name, E'^(.*) ДНП$', E'днп \1', 'ig')
              WHEN name ~* E'^будка( (ж/д|железной дороги))?' THEN
                regexp_replace(name, E'^будка( (ж/д|железной дороги))?', E'ж/д будка', 'ig')
              WHEN name ~* E'^железнодорожная будка' THEN
                regexp_replace(name, E'^железнодорожная будка', E'ж/д будка', 'ig')
              WHEN name ~* E'^платф.+' THEN
                regexp_replace(name, E'^(платф)\s+', E'платформа ', 'ig')
              WHEN name ~* E'^поселок.+' THEN
                regexp_replace(name, E'^(поселок)\s+', E'п.', 'ig')
              WHEN name ~* E'^(рзд|разъезд).+' THEN
                regexp_replace(name, E'^(рзд|разъезд)\s+', E'разъезд ', 'ig')
              WHEN name ~* E'^участок.+' THEN
                regexp_replace(name, E'^(участок)\s+', E'участок ', 'ig')
              WHEN name ~* E'^(урочище|ур-ще) ' THEN
                regexp_replace(name, E'^(урочище|ур-ще) ', 'урочище ', 'ig')
--              WHEN name ~* E'^ж/д' THEN
--                name
              ELSE
                CASE
                  WHEN abbr IN ('с', 'д', 'тер', 'п') THEN
                    abbr || '.' || name
                  WHEN abbr IN ('жилзона','жилрайон','кв-л','х','ст-ца','рп','рзд','м','ст','у') THEN
                    lower(abbrfull) || ' ' || name
                  WHEN abbr IN ('снт') THEN
                    abbr || ' ' || name
                  WHEN abbr IN ('сл') THEN
                    regexp_replace(lower(abbrfull) || ' ' || name, E'^(' || lower(abbrfull) || ') (.*((?:ая)))$', E'\2 \1', 'ig')
                  WHEN abbr IN ('нп') THEN
                    'н/п ' || name
                  ELSE
                    name
                END
            END
          ELSE
            CASE
              WHEN name ~* '((?:ая)|(?:ое)|(?:ой)|(?:ий)|(?:ый)|(?:-й)|(?:-я))$' THEN
                name || ' ' || replace(abbr, '_', ' ') || (CASE WHEN abbr ~ '(-|/)' THEN '' ELSE '.' END)
              ELSE
                replace(abbr, '_', ' ') || (CASE WHEN abbr ~ '(-|/)' THEN '' ELSE '.' END) || name
            END
        END
    END
  FROM
    (
      SELECT
        kl.*,
        ab.name abbrfull
      FROM
        (
          SELECT ($1).*
        ) kl LEFT JOIN
        kladr_abbr ab ON
          ab.code = kl.abbr AND (
            (ab.lvl / 100, length(kl.code)) = (5, 15) OR
            (ab.lvl / 100, length(kl.code)) = (4, 11) OR
            (ab.lvl / 100, length(kl.code)) = (3,  8) OR
            (ab.lvl / 100, length(kl.code)) = (2,  5) OR
            (ab.lvl / 100, length(kl.code)) = (1,  2)
          )
    ) T
$$ LANGUAGE sql;

CREATE OR REPLACE FUNCTION kladr$norm() RETURNS trigger AS $$
BEGIN
  -- проверяем необходимость логгирования при обновлении записи
  NEW.norm = normalize(NEW);
  RETURN NEW;
END;
$$ LANGUAGE plpgsql;

CREATE TRIGGER norm
  BEFORE INSERT OR UPDATE OF code, name, abbr
  ON kladr
    FOR EACH ROW
      EXECUTE PROCEDURE kladr$norm();

Добавить комментарий