Для приготовления виджета Государственного Адресного Реестра сначала нужно его (ГАР) загрузить. При инициализации базы были созданы не только таблицы для загрузки в них ГАР, но также и таблица и функции для виджета. В этой статье остановимся на них подробнее.
Итак, для виджета будем использовать следующую иерархическую таблицу (и индексы), в которую поместим все актуальные данные по всем регионам:
CREATE TABLE IF NOT EXISTS gar ( -- создаём таблицу если её ещё нет -- первичный ключ id uuid NOT NULL DEFAULT gen_random_uuid() PRIMARY KEY, parent uuid, -- родитель name text NOT NULL, -- наименование short text NOT NULL, -- краткий тип type text NOT NULL, -- полный тип post text, -- почтовый индекс region smallint NOT NULL -- код региона ); CREATE INDEX IF NOT EXISTS gar_parent_idx ON gar USING btree (parent); CREATE INDEX IF NOT EXISTS gar_name_idx ON gar USING btree (name); CREATE INDEX IF NOT EXISTS gar_short_idx ON gar USING btree (short); CREATE INDEX IF NOT EXISTS gar_type_idx ON gar USING btree (type); CREATE INDEX IF NOT EXISTS gar_region_idx ON gar USING btree (region);
Далее определим вспомогательную функцию, которую будем использовать для получения полного наименования
-- создаём или меняем функцию от наименования, краткого и полного типа CREATE OR REPLACE FUNCTION gar_text(name text, short text, type text) RETURNS text -- возвращающую полное наименование LANGUAGE sql -- использующую язык sql IMMUTABLE AS $body$ -- зависящую только от своих параметров select case -- в случае -- когда полный тип не определён, возвратить наименование when gar_text.type in ('Не определено') then gar_text.name -- когда полный тип - Чувашия, возвратить наименование и полный тип when gar_text.type in ('Чувашия') then gar_text.name||' '||gar_text.type -- когда в наименовании содержится полный тип, возвратить только наименование when gar_text.name ilike '%'||gar_text.type||'%' then gar_text.name -- иначе возвратить краткий тип точка наименование else gar_text.short||'.'||gar_text.name end; $body$;
Также создадим представление с добавлением этой функции в качестве колонки
CREATE OR REPLACE VIEW gar_view AS -- создаём или меняем представление SELECT gar.*, -- выбирая всё gar_text(name, short, type) AS text -- добавляя полное наименование from gar; -- из ГАРа
Далее определим вспомогательную функцию для подсчёта количества дочерних элементов
-- создаём или меняем функцию от идентификатора CREATE OR REPLACE FUNCTION gar_child(id uuid) -- возвращающую количесвтво его дочерних элементов RETURNS bigint LANGUAGE sql STABLE AS $body$ select count(1) from gar where parent = gar_child.id; $body$;
Также, вспомогательную функцию для выбора, если задан массив идентификаторов
-- создаём или меняем функцию от массива идентификаторов CREATE OR REPLACE FUNCTION gar_select(id uuid[]) RETURNS SETOF gar_view -- возвращающую набор строк из представления LANGUAGE sql STABLE AS $body$ -- выбираем всё, в т.ч. и полное наименование select gar.*, gar_text(name, short, type) AS text from gar -- в указанном агрументом функции порядке inner join (select unnest(gar_select.id) as id, generate_subscripts(gar_select.id, 1) as i) as _ on _.id = gar.id -- для указанных в аргументе функции идентификаторов where gar.id = any(gar_select.id) order by i; $body$;
И ещё вспомогательную функцию для рекурсивного выбора вверх родителей указанного идентификатора, ограничиваясь указанным родительским идентификатором
CREATE OR REPLACE FUNCTION gar_select( -- создаём или меняем функцию от id uuid, -- идентификатора parent uuid DEFAULT NULL -- и необязательного родительского идентификатора ) RETURNS SETOF gar_view -- возвращающую набор строк представления LANGUAGE sql STABLE AS $body$ with recursive _ as ( -- рекурсивно -- сначала выбирая по указанному идентификатору select gar.*, 0 as i from gar where id = gar_select.id union -- а потом выбирая родителей вверх до указанного родителя или до самого верха select gar.*, _.i + 1 as i from gar inner join _ on (_.parent = gar.id) where gar_select.parent is null or _.parent != gar_select.parent ) select id, parent, name, short, type, post, region, gar_text(name, short, type) AS text from _ order by i desc; $body$;
Также вспомогательную функцию для поиска
CREATE OR REPLACE FUNCTION gar_select( -- создаём или меняем функцию parent uuid, -- от родителя name text, -- наименования short text, -- краткого типа type text, -- полного типа post text, -- почтового индекса region text -- кода региона ) RETURNS SETOF gar_view -- возвращающую набор строк представления LANGUAGE sql STABLE AS $body$ -- выбираем всё, где select gar.*, gar_text(gar.name, gar.short, gar.type) AS text from gar where true -- если не задан родитель то без родителя, а если задан - то по нему and ((gar_select.parent is null and parent is null) or parent = gar_select.parent) -- если задано наимнование, то ищем по нему сначала или после пробела или после дефиса или после точки and (gar_select.name is null or name ilike gar_select.name||'%' or name ilike '% '||gar_select.name||'%' or name ilike '%-'||gar_select.name||'%' or name ilike '%.'||gar_select.name||'%') -- если задан кратки тип, то ищем по нему and (gar_select.short is null or short ilike gar_select.short) -- если задан полный тип и это массив - то учитываем все элементы массива, а если не массив, то сначала and (gar_select.type is null or case when gar_select.type ilike '{%}' then type = any(gar_select.type::text[]) else type ilike gar_select.type||'%' end) -- если задан почтовый индекс, то ищем по нему сначала and (gar_select.post is null or post ilike gar_select.post||'%') -- если задан код региона и это массив - то учитываем все элементы массива, а если не массив - то ищем по нему and (gar_select.region is null or case when gar_select.region ilike '{%}' then region = any(gar_select.region::smallint[]) else region = gar_select.region::smallint end) -- сортируем сначала по численному наименованию, а потом по наименованию order by to_number('0'||name, '999999999'), name; $body$;
И ещё вспомогательную функцию для поиска родителей
-- создаём или меняем функцию от родителя, наименования, ... CREATE OR REPLACE FUNCTION gar_select_parent(parent uuid, name text, short text, type text, post text, region text) RETURNS SETOF gar_view LANGUAGE sql STABLE AS $body$ -- выбираем всё select gar.*, gar_text(gar.name, gar.short, gar.type) AS text from gar -- где полный тип из указанного массива where type = any(gar_select_parent.type::text[]) -- и если заданно наименование , то ищем по нему или после пробела или после дефиса или после точки and (gar_select_parent.name is null or name ilike gar_select_parent.name||'%' or name ilike '% '||gar_select_parent.name||'%' or name ilike '%-'||gar_select_parent.name||'%' or name ilike '%.'||gar_select_parent.name||'%') -- сортируя по глубине вложенности order by (select count(id) from gar_select(id, gar_select_parent.parent)), to_number('0'||name, '999999999'), name; $body$;
Ну и теперь, главную функцию для виджета
-- создаём или меняем функцию от json, возвращающую json CREATE OR REPLACE FUNCTION gar_select(INOUT json json) RETURNS json LANGUAGE plpgsql STABLE AS $body$ <<local>> declare id text default nullif(trim(gar_select.json->>'id'), ''); -- уид parent text default nullif(trim(gar_select.json->>'parent'), ''); -- уид родителя name text default nullif(trim(gar_select.json->>'name'), ''); -- наименование short text default nullif(trim(gar_select.json->>'short'), ''); -- кратко type text default nullif(trim(gar_select.json->>'type'), ''); -- тип post text default nullif(trim(gar_select.json->>'port'), ''); -- индекс region text default nullif(trim(gar_select.json->>'region'), ''); -- регион text text default nullif(trim(gar_select.json->>'text'), ''); -- строка поиска offset int default coalesce(nullif(trim(gar_select.json->>'offset'), '')::int, 0); -- офсет limit int default coalesce(nullif(trim(gar_select.json->>'limit'), '')::int, 10); -- лимит "full" boolean default coalesce(nullif(trim(gar_select.json->>'full'), '')::boolean, false); -- все? child boolean default coalesce(nullif(trim(gar_select.json->>'child'), '')::boolean, false); -- дети? begin if local.id is not null then -- если задан id local.id = translate(local.id, '[]','{}'); if local.id ilike '{%}' then -- если id - массив if local.full then -- если все результаты with _ as ( with _ as ( select * from gar_select(local.id::uuid[]) ) select count(1), gar_select.json as query, local.offset, local.limit, ( with _ as ( select * from _ offset local.offset limit local.limit ) select coalesce(json_agg((select json_agg(_) from ( select *, case when local.child then gar_child(_.id) end as child from gar_select(_.id::uuid) ) as _)), '[]'::json) from _ ) as data from _ ) select to_json(_) from _ into strict gar_select.json; else -- иначе - не все результаты with _ as ( with _ as ( select * from gar_select(local.id::uuid[]) ) select count(1), gar_select.json as query, local.offset, local.limit, ( with _ as ( select *, case when local.child then gar_child(_.id) end as child from _ offset local.offset limit local.limit ) select coalesce(json_agg(_), '[]'::json) from _ ) as data from _ ) select to_json(_) from _ into strict gar_select.json; end if; else -- иначе id - не массив if local.full then -- если все результаты with _ as ( with _ as ( select * from gar_select(local.id::uuid) ) select count(1), gar_select.json as query, local.offset, local.limit, ( with _ as ( select * from _ offset local.offset limit local.limit ) select coalesce(json_agg((select json_agg(_) from ( select *, case when local.child then gar_child(_.id) end as child from gar_select(_.id::uuid) ) as _)), '[]'::json) from _ ) as data from _ ) select to_json(_) from _ into strict gar_select.json; else -- иначе - не все результаты with _ as ( with _ as ( select * from gar_select(local.id::uuid) ) select count(1), gar_select.json as query, local.offset, local.limit, ( with _ as ( select *, case when local.child then gar_child(_.id) end as child from _ offset local.offset limit local.limit ) select coalesce(json_agg(_), '[]'::json) from _ ) as data from _ ) select to_json(_) from _ into strict gar_select.json; end if; end if; else -- иначе - не задан id if local.text is not null then -- если искать что-то local.name = local.text; local.short = split_part(local.name, '.', 1); if local.short = local.name or position(' ' in local.short) > 0 or position(',' in local.short) > 0 then local.short = null; else local.name = split_part(local.name, '.', 2); end if; local.name = ltrim(local.name, ' '); end if; if local.text is not null and local.parent is null then -- если искать что-то и родитель не задан with _ as ( with _ as ( select * from gar_select_parent(local.parent::uuid, local.name, local.short, array['Город', 'Поселок', 'Поселение', 'Деревня', 'Населенный пункт', 'Село', 'Рабочий поселок', 'Поселок городского типа']::text, local.post, local.region) ) select count(1), gar_select.json as query, local.offset, local.limit, ( with _ as ( select * from _ offset local.offset limit local.limit ) select coalesce(json_agg((select json_agg(_) from ( select * from gar_select(_.id, local.parent::uuid) ) as _)), '[]'::json) from _ ) as data from _ ) select to_json(_) from _ into strict json; else local.type = translate(local.type, '[]','{}'); if local.full then -- если все результаты with _ as ( with _ as ( select * from gar_select(local.parent::uuid, local.name, local.short, local.type, local.post, local.region) ) select count(1), gar_select.json as query, local.offset, local.limit, ( with _ as ( select * from _ offset local.offset limit local.limit ) select coalesce(json_agg((select json_agg(_) from ( select *, case when local.child then gar_child(_.id) end as child from gar_select(_.id::uuid) ) as _)), '[]'::json) from _ ) as data from _ ) select to_json(_) from _ into strict gar_select.json; else -- иначе - не все результаты with _ as ( with _ as ( select * from gar_select(local.parent::uuid, local.name, local.short, local.type, local.post, local.region) ) select count(1), gar_select.json as query, local.offset, local.limit, ( with _ as ( select *, case when local.child then gar_child(_.id) end as child from _ offset local.offset limit local.limit ) select coalesce(json_agg(_), '[]'::json) from _ ) as data from _ ) select to_json(_) from _ into strict gar_select.json; end if; end if; end if; end;$body$;