gendesign/data/sql/92_cad_bulk_layers.sql
lekss361 c6fb30fdea feat(db): schema migration for bulk cadastre ingest layers (#168)
Add 7 cadastre layer tables + cadastre_jobs saga-state table.

Tables:
- cad_parcels (36368 ЗУ ЕГРН) replaces cad_parcels_geom
- cad_buildings v2 wide schema (replaces existing, backed up as cad_buildings_old_apr26)
- cad_constructions (36383 Сооружения + ЕГРН, flag is_egrn)
- cad_oncs (36384), cad_enk (39663), cad_zouit (4 categoryIds), cad_quarter_stats
- cadastre_jobs (resumable saga state, mirrors geo_jobs pattern)

GIST + BTREE indexes. Migration of cad_parcels_geom data via INSERT...SELECT.
Restore v_complex_full + v_complex_buildings views after table rename.
2026-05-15 12:33:08 +03:00

647 lines
25 KiB
PL/PgSQL

-- =============================================================================
-- 92_cad_bulk_layers.sql
-- Bulk Cadastre Ingest — schema migration for GH issue #168
-- =============================================================================
--
-- Context:
-- Creates 7 new cadastre layer tables + 1 saga-state jobs table for
-- bulk NSPD ingest of all 2408 EKB (66:41:*) quarters.
-- See: research/Bulk_Cadastre_Ingest_Plan_May15.md in Obsidian vault.
--
-- Dependencies:
-- - public.cad_quarters_geom (FK target for cad_quarter_stats)
-- - public.complexes (FK kept in new cad_buildings.complex_id)
-- - v_complex_full, v_complex_buildings (views that JOIN cad_buildings — must survive)
-- - PostGIS extension (geometry types)
--
-- Deploy order:
-- 1. This file (auto-applied by deploy.yml after merge PR #168 part 1)
-- 2. Backend PR #169 (NSPDClient + bulk_harvest_quarter task)
-- 3. Frontend PR #170 (admin scrape/cadastre UI)
--
-- Migration steps performed here:
-- A. CREATE TABLE cad_parcels (new, replaces cad_parcels_geom)
-- B. CREATE TABLE cad_buildings_new (interim name — wider schema)
-- C. CREATE TABLE cad_constructions
-- D. CREATE TABLE cad_oncs
-- E. CREATE TABLE cad_enk
-- F. CREATE TABLE cad_zouit
-- G. CREATE TABLE cad_quarter_stats
-- H. CREATE TABLE cadastre_jobs (saga state, mirrors nspd_geo_jobs pattern)
-- I. All indexes (GIST + BTREE)
-- J. Migrate data: cad_parcels_geom -> cad_parcels
-- K. Backup + replace: cad_buildings -> cad_buildings_old_apr26 -> DROP + rename
-- L. Restore FK + views
-- M. DROP cad_parcels_geom (after data migrated)
--
-- Idempotency: safe to re-run — all DDL uses IF NOT EXISTS / IF EXISTS guards.
-- Data migrations use INSERT ... ON CONFLICT DO NOTHING.
-- If cad_buildings_old_apr26 already exists, backup step is skipped.
--
-- Rollback: restore from cad_buildings_old_apr26 manually if needed.
-- =============================================================================
BEGIN;
-- ---------------------------------------------------------------------------
-- A. cad_parcels (replaces cad_parcels_geom — adds full NSPD props)
-- ---------------------------------------------------------------------------
CREATE TABLE IF NOT EXISTS cad_parcels (
cad_num TEXT NOT NULL,
quarter_cad_number TEXT NOT NULL,
category_id INT NOT NULL DEFAULT 36368,
land_record_area NUMERIC,
specified_area NUMERIC,
declared_area NUMERIC,
land_record_category_type TEXT,
land_record_subtype TEXT,
land_record_type TEXT,
permitted_use_established_by_document TEXT,
cost_value NUMERIC,
cost_index NUMERIC,
cost_application_date DATE,
cost_registration_date DATE,
cost_determination_date DATE,
determination_couse TEXT,
ownership_type TEXT,
right_type TEXT,
readable_address TEXT,
status TEXT,
previously_posted TEXT,
registration_date DATE,
cadastral_districts_code INT,
subcategory INT,
geom GEOMETRY(Polygon, 4326),
raw_props JSONB,
source TEXT NOT NULL,
fetched_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
CONSTRAINT cad_parcels_pkey PRIMARY KEY (cad_num)
);
-- ---------------------------------------------------------------------------
-- B. cad_buildings (wider schema — replaces existing table after data backup)
-- We create under a temporary name first, then swap after backup.
-- ---------------------------------------------------------------------------
CREATE TABLE IF NOT EXISTS cad_buildings_new (
cad_num TEXT NOT NULL,
quarter_cad_number TEXT NOT NULL,
objdoc_id BIGINT,
registers_id INT DEFAULT 36441,
purpose TEXT,
building_name TEXT,
floors INT,
underground_floors INT,
area NUMERIC,
build_record_area NUMERIC,
cost_value NUMERIC,
cost_index NUMERIC,
year_built INT,
year_commisioning INT,
build_record_registration_date DATE,
registration_date DATE,
ownership_type TEXT,
cultural_heritage_object BOOLEAN,
cultural_heritage_val TEXT,
readable_address TEXT,
status TEXT,
common_data_status TEXT,
type TEXT,
build_record_type_value TEXT,
complex_id BIGINT,
geom GEOMETRY(Geometry, 4326),
raw_props JSONB,
source TEXT NOT NULL,
fetched_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
CONSTRAINT cad_buildings_new_pkey PRIMARY KEY (cad_num)
);
-- ---------------------------------------------------------------------------
-- C. cad_constructions (36383 — Сооружения + Сооружения ЕГРН)
-- ---------------------------------------------------------------------------
CREATE TABLE IF NOT EXISTS cad_constructions (
cad_num TEXT NOT NULL,
quarter_cad_number TEXT NOT NULL,
category_name TEXT NOT NULL,
is_egrn BOOLEAN NOT NULL DEFAULT FALSE,
params_purpose TEXT,
params_name TEXT,
params_area NUMERIC,
params_built_up_area NUMERIC,
params_year_built INT,
params_year_commisioning INT,
params_height NUMERIC,
params_volume NUMERIC,
params_extension NUMERIC,
params_depth NUMERIC,
params_occurence_depth NUMERIC,
params_floors INT,
params_underground_floors INT,
object_type_value TEXT,
facility_cad_number TEXT,
cost_value NUMERIC,
cost_index NUMERIC,
ownership_type TEXT,
right_type TEXT,
readable_address TEXT,
address_readable_address TEXT,
status TEXT,
common_data_status TEXT,
registration_date DATE,
registers_id INT,
united_cad_number TEXT,
geom GEOMETRY(Geometry, 4326),
raw_props JSONB,
source TEXT NOT NULL,
fetched_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
CONSTRAINT cad_constructions_pkey PRIMARY KEY (cad_num)
);
-- ---------------------------------------------------------------------------
-- D. cad_oncs (36384 — Объекты незавершённого строительства)
-- ---------------------------------------------------------------------------
CREATE TABLE IF NOT EXISTS cad_oncs (
cad_num TEXT NOT NULL,
quarter_cad_number TEXT NOT NULL,
purpose TEXT,
area NUMERIC,
build_record_area NUMERIC,
cost_value NUMERIC,
status TEXT,
readable_address TEXT,
geom GEOMETRY(Geometry, 4326),
raw_props JSONB,
source TEXT NOT NULL,
fetched_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
CONSTRAINT cad_oncs_pkey PRIMARY KEY (cad_num)
);
-- ---------------------------------------------------------------------------
-- E. cad_enk (39663 — Единые недвижимые комплексы)
-- ---------------------------------------------------------------------------
CREATE TABLE IF NOT EXISTS cad_enk (
cad_num TEXT NOT NULL,
quarter_cad_number TEXT NOT NULL,
name TEXT,
purpose TEXT,
type TEXT,
cost_value NUMERIC,
facility_cad_number TEXT,
readable_address TEXT,
registration_date DATE,
registers_id INT,
geom GEOMETRY(Geometry, 4326),
raw_props JSONB,
fetched_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
CONSTRAINT cad_enk_pkey PRIMARY KEY (cad_num)
);
-- ---------------------------------------------------------------------------
-- F. cad_zouit (4 categoryIds: 36940 / 469039 / 469040 / 469042)
-- Natural key: (reg_numb_border, category_id) — one border can appear in
-- multiple category subtypes.
-- ---------------------------------------------------------------------------
CREATE TABLE IF NOT EXISTS cad_zouit (
id BIGSERIAL,
reg_numb_border TEXT NOT NULL,
category_id INT NOT NULL,
category_name TEXT,
type_zone TEXT NOT NULL,
type_boundary_value TEXT,
subcategory INT,
cadastral_district TEXT,
content_restrict_encumbrances TEXT,
name_by_doc TEXT,
legal_act_name TEXT,
legal_act_date DATE,
legal_act_number TEXT,
legal_act_issuer TEXT,
registration_date DATE,
old_account_number TEXT,
geom GEOMETRY(MultiPolygon, 4326),
raw_props JSONB,
source TEXT NOT NULL,
fetched_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
CONSTRAINT cad_zouit_pkey PRIMARY KEY (id),
CONSTRAINT cad_zouit_reg_category_uniq UNIQUE (reg_numb_border, category_id)
);
-- ---------------------------------------------------------------------------
-- G. cad_quarter_stats (enrichment 1-to-1 with cad_quarters_geom)
-- ---------------------------------------------------------------------------
CREATE TABLE IF NOT EXISTS cad_quarter_stats (
cad_number TEXT NOT NULL,
cnt_land INT,
cnt_oks INT,
cnt_oks_area INT,
cnt_oks_geom INT,
cnt_oks_geom_area INT,
cnt_oks_geom_linear INT,
cnt_oks_linear INT,
cnt_oks_not_geom INT,
cnt_land_geom INT,
cnt_land_not_geom INT,
cnt_land_eou INT,
cnt_land_geom_eou INT,
cnt_enk INT,
cnt_pik INT,
cost_value_total NUMERIC,
cost_value_total_eou NUMERIC,
cost_value_total_geom NUMERIC,
sum_land_area NUMERIC,
sum_land_area_eou NUMERIC,
sum_land_geom_area NUMERIC,
sum_land_geom_area_eou NUMERIC,
date_cr DATE,
real_srid INT,
raw_props JSONB,
fetched_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
CONSTRAINT cad_quarter_stats_pkey PRIMARY KEY (cad_number),
CONSTRAINT cad_quarter_stats_quarter_fkey
FOREIGN KEY (cad_number)
REFERENCES cad_quarters_geom(cad_number)
ON DELETE CASCADE
);
-- ---------------------------------------------------------------------------
-- H. cadastre_jobs (saga state — mirrors nspd_geo_jobs pattern)
-- ---------------------------------------------------------------------------
CREATE TABLE IF NOT EXISTS cadastre_jobs (
job_id BIGSERIAL NOT NULL,
name TEXT,
job_kind TEXT NOT NULL,
scope TEXT NOT NULL,
status TEXT NOT NULL DEFAULT 'queued',
triggered_by TEXT,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
started_at TIMESTAMPTZ,
finished_at TIMESTAMPTZ,
heartbeat_at TIMESTAMPTZ,
targets_total INT NOT NULL DEFAULT 0,
targets_done INT NOT NULL DEFAULT 0,
targets_failed INT NOT NULL DEFAULT 0,
targets_skipped INT NOT NULL DEFAULT 0,
requests_count INT NOT NULL DEFAULT 0,
waf_blocked_count INT NOT NULL DEFAULT 0,
rate_ms INT NOT NULL DEFAULT 333,
error TEXT,
phase_state JSONB,
raw_targets JSONB,
CONSTRAINT cadastre_jobs_pkey PRIMARY KEY (job_id)
);
-- ===========================================================================
-- I. INDEXES
-- ===========================================================================
-- cad_parcels
CREATE INDEX IF NOT EXISTS cad_parcels_geom_gist
ON cad_parcels USING GIST (geom);
CREATE INDEX IF NOT EXISTS cad_parcels_quarter_idx
ON cad_parcels USING BTREE (quarter_cad_number);
-- cad_buildings_new
CREATE INDEX IF NOT EXISTS cad_buildings_new_geom_gist
ON cad_buildings_new USING GIST (geom);
CREATE INDEX IF NOT EXISTS cad_buildings_new_quarter_idx
ON cad_buildings_new USING BTREE (quarter_cad_number);
CREATE INDEX IF NOT EXISTS cad_buildings_new_objdoc_idx
ON cad_buildings_new USING BTREE (objdoc_id)
WHERE objdoc_id IS NOT NULL;
CREATE INDEX IF NOT EXISTS cad_buildings_new_complex_idx
ON cad_buildings_new USING BTREE (complex_id)
WHERE complex_id IS NOT NULL;
CREATE INDEX IF NOT EXISTS cad_buildings_new_purpose_idx
ON cad_buildings_new USING BTREE (purpose);
-- cad_constructions
CREATE INDEX IF NOT EXISTS cad_constructions_geom_gist
ON cad_constructions USING GIST (geom);
CREATE INDEX IF NOT EXISTS cad_constructions_quarter_idx
ON cad_constructions USING BTREE (quarter_cad_number);
CREATE INDEX IF NOT EXISTS cad_constructions_purpose_idx
ON cad_constructions USING BTREE (params_purpose)
WHERE params_purpose IS NOT NULL;
-- cad_oncs
CREATE INDEX IF NOT EXISTS cad_oncs_geom_gist
ON cad_oncs USING GIST (geom);
CREATE INDEX IF NOT EXISTS cad_oncs_quarter_idx
ON cad_oncs USING BTREE (quarter_cad_number);
-- cad_enk
CREATE INDEX IF NOT EXISTS cad_enk_geom_gist
ON cad_enk USING GIST (geom);
CREATE INDEX IF NOT EXISTS cad_enk_quarter_idx
ON cad_enk USING BTREE (quarter_cad_number);
-- cad_zouit
CREATE INDEX IF NOT EXISTS cad_zouit_geom_gist
ON cad_zouit USING GIST (geom);
CREATE INDEX IF NOT EXISTS cad_zouit_reg_numb_idx
ON cad_zouit USING BTREE (reg_numb_border);
CREATE INDEX IF NOT EXISTS cad_zouit_category_idx
ON cad_zouit USING BTREE (category_id);
CREATE INDEX IF NOT EXISTS cad_zouit_subcategory_idx
ON cad_zouit USING BTREE (subcategory)
WHERE subcategory IS NOT NULL;
-- cadastre_jobs
CREATE INDEX IF NOT EXISTS cadastre_jobs_status_idx
ON cadastre_jobs USING BTREE (status, created_at DESC);
CREATE INDEX IF NOT EXISTS cadastre_jobs_running_idx
ON cadastre_jobs USING BTREE (heartbeat_at DESC)
WHERE status = 'running';
-- ===========================================================================
-- J. Migrate existing rows: cad_parcels_geom -> cad_parcels
-- Source has: cad_num, geom, raw_props, fetched_at
-- We set quarter_cad_number from cad_num (first 3 parts: XX:YY:ZZZZZZ)
-- source = 'nspd' (original scraper)
-- ===========================================================================
INSERT INTO cad_parcels (
cad_num,
quarter_cad_number,
geom,
raw_props,
source,
fetched_at,
updated_at
)
SELECT
p.cad_num,
-- Derive quarter from cad_num "66:41:0101001:123" -> "66:41:0101001"
regexp_replace(p.cad_num, '^(\d+:\d+:\d+):.*$', '\1'),
p.geom,
p.raw_props,
'nspd',
p.fetched_at,
p.fetched_at
FROM cad_parcels_geom p
ON CONFLICT (cad_num) DO NOTHING;
-- ===========================================================================
-- K. Backup existing cad_buildings + migrate data into cad_buildings_new
-- then drop old and rename new.
--
-- Old schema columns we map to new:
-- quarter_cad_num -> quarter_cad_number
-- floors TEXT -> floors INT (NULLIF on non-numeric)
-- underground_floors TEXT -> underground_floors INT
-- cultural_heritage TEXT -> cultural_heritage_object BOOLEAN (NOT NULL = FALSE → NULL),
-- cultural_heritage_val TEXT
-- build_record_type -> build_record_type_value
-- obj_type -> type
-- (no source in old table, default 'nspd')
-- ===========================================================================
-- K.1 Backup: rename old table (only if backup doesn't already exist)
DO $$
BEGIN
IF NOT EXISTS (
SELECT 1 FROM information_schema.tables
WHERE table_schema = 'public'
AND table_name = 'cad_buildings_old_apr26'
) THEN
ALTER TABLE cad_buildings RENAME TO cad_buildings_old_apr26;
END IF;
END $$;
-- K.2 Migrate data into cad_buildings_new
-- Old cad_buildings (now _old_apr26) -> new wider schema.
-- complex_id FK preserved as-is.
INSERT INTO cad_buildings_new (
cad_num,
quarter_cad_number,
purpose,
building_name,
floors,
underground_floors,
area,
build_record_area,
cost_value,
year_built,
year_commisioning,
registration_date,
ownership_type,
cultural_heritage_object,
cultural_heritage_val,
readable_address,
status,
common_data_status,
type,
build_record_type_value,
complex_id,
geom,
raw_props,
source,
fetched_at,
updated_at
)
SELECT
o.cad_num,
COALESCE(o.quarter_cad_num,
regexp_replace(o.cad_num, '^(\d+:\d+:\d+):.*$', '\1')),
o.purpose,
o.building_name,
-- floors was TEXT in old schema; cast safely
CASE
WHEN o.floors ~ '^\d+$' THEN o.floors::INT
ELSE NULL
END,
-- underground_floors was TEXT
CASE
WHEN o.underground_floors ~ '^\d+$' THEN o.underground_floors::INT
ELSE NULL
END,
o.area,
o.build_record_area,
o.cost_value,
o.year_built,
o.year_commisioning,
o.registration_date,
o.ownership_type,
-- cultural_heritage TEXT: treat non-NULL / non-empty string as TRUE
CASE
WHEN o.cultural_heritage IS NOT NULL
AND o.cultural_heritage <> '' THEN TRUE
ELSE NULL
END,
o.cultural_heritage,
o.readable_address,
o.status,
o.common_data_status,
o.obj_type,
o.build_record_type,
o.complex_id,
o.geom,
o.raw_props,
'nspd',
o.fetched_at,
o.fetched_at
FROM cad_buildings_old_apr26 o
ON CONFLICT (cad_num) DO NOTHING;
-- K.3 Rename cad_buildings_new -> cad_buildings
-- (only if the rename hasn't happened already)
DO $$
BEGIN
IF NOT EXISTS (
SELECT 1 FROM information_schema.tables
WHERE table_schema = 'public' AND table_name = 'cad_buildings'
) THEN
ALTER TABLE cad_buildings_new RENAME TO cad_buildings;
-- Rename indexes to drop the _new suffix
ALTER INDEX IF EXISTS cad_buildings_new_pkey RENAME TO cad_buildings_pkey;
ALTER INDEX IF EXISTS cad_buildings_new_geom_gist RENAME TO cad_buildings_geom_gist;
ALTER INDEX IF EXISTS cad_buildings_new_quarter_idx RENAME TO cad_buildings_quarter_idx;
ALTER INDEX IF EXISTS cad_buildings_new_objdoc_idx RENAME TO cad_buildings_objdoc_idx;
ALTER INDEX IF EXISTS cad_buildings_new_complex_idx RENAME TO cad_buildings_complex_idx;
ALTER INDEX IF EXISTS cad_buildings_new_purpose_idx RENAME TO cad_buildings_purpose_idx;
-- Rename constraint
ALTER TABLE cad_buildings
RENAME CONSTRAINT cad_buildings_new_pkey TO cad_buildings_pkey;
END IF;
END $$;
-- ---------------------------------------------------------------------------
-- L.1 Re-add FK: cad_buildings.complex_id -> complexes.id
-- (was dropped implicitly when old table was renamed)
-- Guard: only add if FK doesn't already exist.
-- ---------------------------------------------------------------------------
DO $$
BEGIN
IF NOT EXISTS (
SELECT 1 FROM information_schema.table_constraints
WHERE table_schema = 'public'
AND table_name = 'cad_buildings'
AND constraint_name = 'cad_buildings_complex_id_fkey'
AND constraint_type = 'FOREIGN KEY'
) THEN
ALTER TABLE cad_buildings
ADD CONSTRAINT cad_buildings_complex_id_fkey
FOREIGN KEY (complex_id)
REFERENCES complexes(id)
ON DELETE SET NULL;
END IF;
END $$;
-- ---------------------------------------------------------------------------
-- L.2 Restore views that JOIN cad_buildings.
-- Both views only reference: cad_num, complex_id, canonical_name.
-- These columns exist in new cad_buildings — views will still be valid
-- after the rename. However, we drop + recreate explicitly to ensure
-- pg_depend is attached to the renamed table (not the old OID).
-- ---------------------------------------------------------------------------
DROP VIEW IF EXISTS v_complex_buildings;
CREATE OR REPLACE VIEW v_complex_buildings AS
SELECT
c.id AS complex_id,
c.canonical_name,
array_agg(DISTINCT b.cad_num ORDER BY b.cad_num) AS cad_building_nums,
COUNT(b.cad_num) AS buildings_count
FROM complexes c
LEFT JOIN cad_buildings b ON b.complex_id = c.id
GROUP BY c.id, c.canonical_name;
DROP VIEW IF EXISTS v_complex_full;
CREATE OR REPLACE VIEW v_complex_full AS
SELECT
c.id AS complex_id,
c.canonical_name,
c.developer_name,
c.region_id,
c.district_name,
c.obj_class,
c.address,
c.latitude,
c.longitude,
(c.geom IS NOT NULL) AS has_geom,
c.flat_count,
c.square_living,
c.site_status,
c.cad_quarter,
(
SELECT array_agg(b.cad_num ORDER BY b.cad_num)
FROM cad_buildings b
WHERE b.complex_id = c.id
) AS cad_building_nums,
(
SELECT COUNT(*)
FROM cad_buildings b
WHERE b.complex_id = c.id
) AS cad_buildings_n,
(
SELECT array_agg(DISTINCT cs.source ORDER BY cs.source)
FROM complex_sources cs
WHERE cs.complex_id = c.id
) AS sources,
(
SELECT COUNT(*)
FROM complex_sources cs
WHERE cs.complex_id = c.id
) AS source_links_n,
(
SELECT COUNT(DISTINCT cs.source)
FROM complex_sources cs
WHERE cs.complex_id = c.id
) AS source_kinds_n,
(
SELECT array_agg(cs.source_external_id ORDER BY cs.source_external_id)
FROM complex_sources cs
WHERE cs.complex_id = c.id
AND cs.source = 'domrf_kn'
) AS domrf_obj_ids,
(
SELECT cs.source_id
FROM complex_sources cs
WHERE cs.complex_id = c.id
AND cs.source = 'objective'
LIMIT 1
) AS objective_project_name,
(
SELECT COUNT(*)
FROM domrf_kn_flats f
WHERE f.obj_id IN (
SELECT cs.source_external_id
FROM complex_sources cs
WHERE cs.complex_id = c.id
AND cs.source = 'domrf_kn'
AND cs.source_external_id IS NOT NULL
)
) AS kn_flats_n,
(
SELECT COUNT(*)
FROM objective_lots ol
WHERE ol.complex_id = c.id
) AS objective_lots_n,
c.primary_source,
c.created_at,
c.updated_at
FROM complexes c;
-- ===========================================================================
-- M. DROP cad_parcels_geom (original table — data migrated to cad_parcels)
-- Only dropped if cad_parcels exists and has at least as many rows.
-- Guard: skip drop if the new table doesn't exist yet (paranoia).
-- ===========================================================================
DO $$
BEGIN
IF EXISTS (
SELECT 1 FROM information_schema.tables
WHERE table_schema = 'public' AND table_name = 'cad_parcels'
) AND EXISTS (
SELECT 1 FROM information_schema.tables
WHERE table_schema = 'public' AND table_name = 'cad_parcels_geom'
) THEN
DROP TABLE cad_parcels_geom;
END IF;
END $$;
COMMIT;