Федеральная
Налоговая служба
Федеральная информационная адресная система
О ФИАС
Поиск
Обновления
Вопросы и ответы
Статистика
Заявка в ФИАС
Информация о заявке
Вернуться к полнотекстовому поиску
Деление |
|
Субъект Российской Федерации | |
Муниципальный район/округ | |
Поселение | |
Город | |
Населенный пункт | |
Элемент планировочной структуры | |
Элемент улично-дорожной сети | |
Номер земельного участка: | |
Номер здания/сооружения | |
Номер машино-места: | |
Номер помещения: | |
Номер помещения в пределах помещения: |
Почтовый индекс | |
ОКАТО | |
ОКТМО | |
Уникальный номер адреса объекта адресации в государственном адресном реестре | |
Кадастровый номер |
Тип сведений |
Только действующие объекты |
Уровень объекта | |
Тип объекта |
Полный каталог классификатора адресов России
Все адреса в ФИАС: Москва и Россия на одной карте
На данной странице представлен ФИАС – онлайн классификатор адресов России,
находящийся в
открытом доступе. Этот классификатор создан АО ГНИВЦ, и изначально предназначался для
внутриведомственного использования
ФНС, в частности, для привязки адресных объектов к налоговым инспекциям и упрощения отправки
корреспонденции по
контрагентам. Данный классификатор используется в ФНС России с 1 декабря 2005 г. Сейчас область его
применения
значительно расширилась – сегодня информация ФИАС требуется во многих аспектах работы организаций.
Данный онлайн-сервис
представлен у нас в ознакомительных целях. Он позволяет оперативно находить необходимые данные по
любому
территориальному объекту Российской Федерации. Например, можно бесплатно найти в соответствии с ФИАС
объекты по адресу,
почтовому индексу, найти все адреса, привязанные к определенной налоговой инспекции ФНС России, или
к интересующему вас
коду ОКАТО. Данные загружаются и своевременно обновляются из базы, представленной на официальном
сайте www.gnivc.ru.
Структурно справочник адресов ФИАС разделен на семь уровней. Поиск начинается по
региону, затем
продолжается по району, городу, населенному пункту, и заканчивается по номеру дома и квартиры. Задав
в поле поиска
интересующий адрес, по ФИАС в онлайн режиме можно также узнать индекс предприятия связи, номер ИНФС
налогоплательщика,
а также код ОКАТО. Эта информация окажется полезной при сдаче отчетности, постановке на налоговый
учет, при подготовке и
сдаче документов на регистрацию ИП и т.д.
Тем не менее, существуют ограничения доступности адресов некоторых объектов для
обычных пользователей.
Так, в онлайн-реестре у объектов, имеющих любую степень секретности, указаны лишь наименования.
Также в ФИАС
присутствуют старые, неактуальные адресные объекты – переименованные, переподчиненные или влитые в
состав другого
объекта и просто не существующие более. Во многих случаях это позволяет найти актуальные данные
человеку, который даже
не подозревает, что его адресные данные уже сменились.
Еще одной немаловажной функцией этого классификатора является проверка адреса по
ФИАС, которую у нас
также можно выполнить в онлайн режиме. Эта функция позволит уточнить как местонахождение адресанта,
так и правильность
написания адреса. Если адресанта не удалось найти в ФИАС по адресу, но вы уверены, что он
существует, необходимо
обратиться с соответствующим запросом в территориальный орган ФНС России, в ведении которой
находится справочник.
Искать и проверять адрес в формате ФИАС удобно в любой автоматизированной
информационной системе, что
делает этот справочник достаточно универсальным. Кроме того, отображаемый у нас классификатор в
обязательном порядке
используется работодателями при приеме сотрудников на работу, обеспечивая возможность удостовериться
в наличии у
принимаемого человека постоянной прописки по определенному месту жительства. Мы надеемся, что наш
бесплатный интуитивно
понятный онлайн сервис поможет вам в повседневной работе.
Регфорум предлагает вам актуальный КЛАДР 2016 года. С его помощью вы можете быстро узнать почтовый индекс, номер территориальной налоговой инспекции, регистрирующего органа, к которому прикреплен адрес, а также выяснить коды ОКТМО и ОКАТО. Предоставляем вам КЛАДР бесплатно и без дополнительных условий. Просто начните использовать его прямо сейчас!
- Выбор
- Поиск
Выберите субъект
- Москва г.
- Санкт-Петербург г.
- Адыгея Респ.
- Алтай Респ.
- Алтайский край.
- Амурская обл.
- Архангельская обл.
- Астраханская обл.
- Байконур г.
- Башкортостан Респ.
- Белгородская обл.
- Брянская обл.
- Бурятия Респ.
- Владимирская обл.
- Волгоградская обл.
- Вологодская обл.
- Воронежская обл.
- Дагестан Респ.
- Еврейская Аобл.
- Забайкальский край.
- Ивановская обл.
- Ингушетия Респ.
- Иркутская обл.
- Кабардино-Балкарская Респ.
- Калининградская обл.
- Калмыкия Респ.
- Калужская обл.
- Камчатский край.
- Карачаево-Черкесская Респ.
- Карелия Респ.
- Кемеровская обл.
- Кировская обл.
- Коми Респ.
- Костромская обл.
- Краснодарский край.
- Красноярский край.
- Крым Респ.
- Курганская обл.
- Курская обл.
- Ленинградская обл.
- Липецкая обл.
- Магаданская обл.
- Марий Эл Респ.
- Мордовия Респ.
- Московская обл.
- Мурманская обл.
- Ненецкий АО.
- Нижегородская обл.
- Новгородская обл.
- Новосибирская обл.
- Омская обл.
- Оренбургская обл.
- Орловская обл.
- Пензенская обл.
- Пермский край.
- Приморский край.
- Псковская обл.
- Ростовская обл.
- Рязанская обл.
- Самарская обл.
- Саратовская обл.
- Саха /Якутия/ Респ.
- Сахалинская обл.
- Свердловская обл.
- Севастополь г.
- Северная Осетия – Алания Респ.
- Смоленская обл.
- Ставропольский край.
- Тамбовская обл.
- Татарстан Респ.
- Тверская обл.
- Томская обл.
- Тульская обл.
- Тыва Респ.
- Тюменская обл.
- Удмуртская Респ.
- Ульяновская обл.
- Хабаровский край.
- Хакасия Респ.
- Ханты-Мансийский Автономный округ – Югра АО.
- Челябинская обл.
- Чеченская Респ.
- Чувашская Республика – Чувашия.
- Чукотский АО.
- Ямало-Ненецкий АО.
- Ярославская обл.
Искать во всех субъектах
Искать только улицыИскать другие объекты
Выберите улицу из списка ниже:
Показать все дома этой улицы
Информация отсутствует.
Почтовый индекс
Показать адрес и телефоныСкрыть адрес и телефоны
Номер территориальной налоговой инспекции
Выберите населенный пункт
Показать все дома этой улицы
Вы выбрали адрес:
Информация отсутствует.
Почтовый индекс
Показать адрес и телефоныСкрыть адрес и телефоны
Номер территориальной налоговой инспекции
Инструкция по использованию КЛАДР
Вы можете использовать наш классификатор КЛАДР для нахождения требуемой информации двумя способами: через «Выбор» и «Поиск». В нем нет адресов, имеющих любую степень секретности. У закрытых административно-территориальных образований в КЛАДР России указывается только их наименование. Файлы классификатора создаются из базы данных Федеральной информационной адресной системы.
Чтобы выбрать адрес и получить желаемый код КЛАДР, кликните по первой строке, где указано «Москва», если вам нужен другой регион. В открывшемся окне найдите субъект РФ в списке или начните набирать его в поисковой строке. Таким же образом выберите город и улицу. И вы получите искомые данные, для которых наш КЛАДР предоставит вам список домов и другую информацию.
Чтобы найти адрес по названию, нажмите на вкладку «Поиск», укажите регион, если это не «Москва». Затем выберите что искать: улицы или другие объекты. Введите название объекта или его часть, нажмите «Искать», и выберите улицу из представленного списка. В зависимости от объекта наш справочник КЛАДР предоставляет вам возможность выбрать дом или просмотреть все адреса с информацией по ним. Если кликнуть по индексу, то отобразятся адрес почтового отделения и его телефоны.
О КЛАДР
Чтобы распределить территории между налоговыми инспекциями и автоматизировать рассылку корреспонденции был создан классификатор адресов КЛАДР для ведомственного использования в ФНС. В нашем сервисе всегда актуальный КЛАДР адресов 2016 года для всех регионов РФ, включая Крым. Используя вкладку «Выбор», вы можете найти адрес в конкретном населенном пункте. Если воспользоваться вкладкой «Поиск», то можно найти объект с определенным наименованием в одном или всех субъектах РФ.
Мы постарались сделать этот инструмент удобным и интуитивно понятным для вас. Нам бы хотелось совершенствовать наш онлайн-сервис КЛАДР, поэтому будем признательны вам за отзыв о его работе.
10 фактов о КЛАДР
- КЛАДР РФ содержит в электронном виде всю базу данных адресов России.
- В нем вся территория страны распределена по налоговым инспекциям.
- Он был введен приказом Федеральной Налоговой Службы и используется в России с 2005 года.
- КЛАДР онлайн 2016 года находится в открытом доступе.
- В классификаторе встречаются ошибки в кодах ОКАТО, почтовых индексах, к тому же адрес может полностью отсутствовать.
- Последний КЛАДР имеет семиуровневую структуру: субъект РФ, район, город, населенный пункт, улица, дом, квартира.
- Для ФНС и юристов новая версия КЛАДР не имеет седьмого уровня (квартира/офис), так как налоговыми органами он не используется.
- Отвечает за актуализацию КЛАДР ГНИВЦ ФНС РФ, в обязанности которого входит публикация классификатора и загрузка обновлений в базу данных.
- Несколько раз в месяц происходит обновление КЛАДР.
- Чтобы корректно сдать в налоговую справки 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();