393 lines
14 KiB
PL/PgSQL
393 lines
14 KiB
PL/PgSQL
-- ============================================================================
|
|
-- Architektur-Planungs-App Datenbank Schema
|
|
-- PostgreSQL 15+ mit PostGIS 3.4+
|
|
-- Schweizer Koordinatensystem: LV95 (EPSG:2056)
|
|
-- ============================================================================
|
|
|
|
-- PostGIS Extension aktivieren
|
|
CREATE EXTENSION IF NOT EXISTS postgis;
|
|
|
|
-- UUID Extension für uuid_generate_v4()
|
|
CREATE EXTENSION IF NOT EXISTS "uuid-ossp";
|
|
|
|
-- ============================================================================
|
|
-- ENUMS
|
|
-- ============================================================================
|
|
|
|
CREATE TYPE status_prozess AS ENUM (
|
|
'Eingang',
|
|
'Analyse',
|
|
'Studie',
|
|
'Planung',
|
|
'Baurechtsverfahren',
|
|
'Umsetzung',
|
|
'Archiv'
|
|
);
|
|
|
|
CREATE TYPE dokument_typ AS ENUM (
|
|
'Datei',
|
|
'Url'
|
|
);
|
|
|
|
CREATE TYPE tag_typ AS ENUM (
|
|
'Kataster Objekte',
|
|
'Kataster Werkeleitungen',
|
|
'Kataster Belastete Standorte',
|
|
'Kataster Bäume',
|
|
'Zonenplan',
|
|
'Planungs- und Baugesetz (PGB)',
|
|
'Bau- und Zonenordnung (BZO)',
|
|
'Parkplatzverordnung',
|
|
'Eigentümerauskunft',
|
|
'Grundbuchauszug'
|
|
);
|
|
|
|
CREATE TYPE geo_tag_typ AS ENUM (
|
|
'Referenzpunkt Kat. 1',
|
|
'Referenzpunkt Kat. 2',
|
|
'Referenzpunkt Kat. 3',
|
|
'Geometeraufnahme'
|
|
);
|
|
|
|
CREATE TYPE ja_nein AS ENUM (
|
|
'',
|
|
'Ja',
|
|
'Nein'
|
|
);
|
|
|
|
-- ============================================================================
|
|
-- HAUPTTABELLEN
|
|
-- ============================================================================
|
|
|
|
-- Land
|
|
CREATE TABLE land (
|
|
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
|
|
label VARCHAR(255) NOT NULL,
|
|
created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
|
|
updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP
|
|
);
|
|
|
|
-- Kanton
|
|
CREATE TABLE kanton (
|
|
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
|
|
label VARCHAR(255) NOT NULL,
|
|
baureglement_aktuell_id UUID,
|
|
baureglement_revision_id UUID,
|
|
bauverordnung_aktuell_id UUID,
|
|
bauverordnung_revision_id UUID,
|
|
created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
|
|
updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP
|
|
);
|
|
|
|
-- Gemeinde
|
|
CREATE TABLE gemeinde (
|
|
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
|
|
label VARCHAR(255) NOT NULL,
|
|
plz VARCHAR(10),
|
|
bzo_aktuell_id UUID,
|
|
bzo_revision_id UUID,
|
|
created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
|
|
updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP
|
|
);
|
|
|
|
-- Dokument
|
|
CREATE TABLE dokument (
|
|
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
|
|
label VARCHAR(255) NOT NULL,
|
|
versionsbezeichnung VARCHAR(100),
|
|
typ dokument_typ NOT NULL,
|
|
format VARCHAR(50),
|
|
dokument_referenz TEXT NOT NULL,
|
|
tags tag_typ[],
|
|
created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
|
|
updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP
|
|
);
|
|
|
|
-- Projekt
|
|
CREATE TABLE projekt (
|
|
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
|
|
label VARCHAR(255) NOT NULL,
|
|
status_prozess status_prozess[],
|
|
created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
|
|
updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP
|
|
);
|
|
|
|
-- Parzelle
|
|
CREATE TABLE parzelle (
|
|
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
|
|
label VARCHAR(255) NOT NULL,
|
|
parzellen_nummern VARCHAR(50)[],
|
|
eigentuemerschaaft VARCHAR(255),
|
|
strasse_nr VARCHAR(255),
|
|
|
|
-- Geografischer Kontext
|
|
land_id UUID REFERENCES land(id),
|
|
kanton_id UUID REFERENCES kanton(id),
|
|
gemeinde_id UUID REFERENCES gemeinde(id),
|
|
|
|
-- Geometrie (PostGIS)
|
|
geo_umfang GEOMETRY(POLYGON, 2056),
|
|
|
|
-- Bauliche Parameter
|
|
bauzone VARCHAR(50),
|
|
az DECIMAL(5,2),
|
|
bz DECIMAL(5,2),
|
|
vollgeschoss_zahl INTEGER,
|
|
anrechenbar_dachgeschoss DECIMAL(3,2),
|
|
anrechenbar_untergeschoss DECIMAL(3,2),
|
|
gebaeudehoehe_max DECIMAL(6,2),
|
|
|
|
-- Regelungen
|
|
regeln_grenzabstand TEXT,
|
|
regeln_mehrlaengenzuschlag TEXT,
|
|
regeln_mehrhoehenzuschlag TEXT,
|
|
|
|
-- Schutzzonen
|
|
hochwasserschutzzone VARCHAR(100),
|
|
laermschutzzone VARCHAR(100),
|
|
grundwasserschutzzone VARCHAR(100),
|
|
|
|
-- Eigenschaften
|
|
parzelle_bebaut ja_nein,
|
|
parzelle_erschlossen ja_nein,
|
|
hanglage ja_nein,
|
|
|
|
created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
|
|
updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP
|
|
);
|
|
|
|
-- GeoPunkt
|
|
CREATE TABLE geo_punkt (
|
|
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
|
|
x DECIMAL(12,3) NOT NULL, -- LV95 Ostwert
|
|
y DECIMAL(12,3) NOT NULL, -- LV95 Nordwert
|
|
z DECIMAL(8,3), -- Höhe über Meer
|
|
referenzen geo_tag_typ[],
|
|
projekt_id UUID REFERENCES projekt(id) ON DELETE CASCADE,
|
|
created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
|
|
updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP
|
|
);
|
|
|
|
-- Kontext (Polymorphe Beziehung)
|
|
CREATE TABLE kontext (
|
|
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
|
|
thema VARCHAR(255) NOT NULL,
|
|
inhalt TEXT NOT NULL,
|
|
|
|
-- Polymorphe Foreign Keys
|
|
projekt_id UUID REFERENCES projekt(id) ON DELETE CASCADE,
|
|
parzelle_id UUID REFERENCES parzelle(id) ON DELETE CASCADE,
|
|
land_id UUID REFERENCES land(id) ON DELETE CASCADE,
|
|
kanton_id UUID REFERENCES kanton(id) ON DELETE CASCADE,
|
|
gemeinde_id UUID REFERENCES gemeinde(id) ON DELETE CASCADE,
|
|
|
|
created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
|
|
updated_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
|
|
|
|
-- Constraint: Kontext muss zu genau einer Entität gehören
|
|
CONSTRAINT kontext_single_parent CHECK (
|
|
(projekt_id IS NOT NULL)::INTEGER +
|
|
(parzelle_id IS NOT NULL)::INTEGER +
|
|
(land_id IS NOT NULL)::INTEGER +
|
|
(kanton_id IS NOT NULL)::INTEGER +
|
|
(gemeinde_id IS NOT NULL)::INTEGER = 1
|
|
)
|
|
);
|
|
|
|
-- ============================================================================
|
|
-- JUNCTION TABLES (Many-to-Many Beziehungen)
|
|
-- ============================================================================
|
|
|
|
-- Projekt <-> Parzelle
|
|
CREATE TABLE projekt_parzelle (
|
|
projekt_id UUID NOT NULL REFERENCES projekt(id) ON DELETE CASCADE,
|
|
parzelle_id UUID NOT NULL REFERENCES parzelle(id) ON DELETE CASCADE,
|
|
PRIMARY KEY (projekt_id, parzelle_id)
|
|
);
|
|
|
|
-- Projekt <-> Dokument (Bauherrschaft)
|
|
CREATE TABLE projekt_dokument_bauherrschaft (
|
|
projekt_id UUID NOT NULL REFERENCES projekt(id) ON DELETE CASCADE,
|
|
dokument_id UUID NOT NULL REFERENCES dokument(id) ON DELETE CASCADE,
|
|
PRIMARY KEY (projekt_id, dokument_id)
|
|
);
|
|
|
|
-- Projekt <-> Dokument (Planung)
|
|
CREATE TABLE projekt_dokument_planung (
|
|
projekt_id UUID NOT NULL REFERENCES projekt(id) ON DELETE CASCADE,
|
|
dokument_id UUID NOT NULL REFERENCES dokument(id) ON DELETE CASCADE,
|
|
PRIMARY KEY (projekt_id, dokument_id)
|
|
);
|
|
|
|
-- Parzelle <-> Parzelle (Nachbarn)
|
|
CREATE TABLE parzelle_nachbar (
|
|
parzelle_id UUID NOT NULL REFERENCES parzelle(id) ON DELETE CASCADE,
|
|
nachbar_id UUID NOT NULL REFERENCES parzelle(id) ON DELETE CASCADE,
|
|
PRIMARY KEY (parzelle_id, nachbar_id),
|
|
CHECK (parzelle_id != nachbar_id) -- Parzelle kann nicht ihr eigener Nachbar sein
|
|
);
|
|
|
|
-- Parzelle <-> Dokument
|
|
CREATE TABLE parzelle_dokument (
|
|
parzelle_id UUID NOT NULL REFERENCES parzelle(id) ON DELETE CASCADE,
|
|
dokument_id UUID NOT NULL REFERENCES dokument(id) ON DELETE CASCADE,
|
|
PRIMARY KEY (parzelle_id, dokument_id)
|
|
);
|
|
|
|
-- ============================================================================
|
|
-- FOREIGN KEY CONSTRAINTS (nachträglich für Kanton/Gemeinde Dokumente)
|
|
-- ============================================================================
|
|
|
|
ALTER TABLE kanton
|
|
ADD CONSTRAINT fk_kanton_baureglement_aktuell
|
|
FOREIGN KEY (baureglement_aktuell_id) REFERENCES dokument(id),
|
|
ADD CONSTRAINT fk_kanton_baureglement_revision
|
|
FOREIGN KEY (baureglement_revision_id) REFERENCES dokument(id),
|
|
ADD CONSTRAINT fk_kanton_bauverordnung_aktuell
|
|
FOREIGN KEY (bauverordnung_aktuell_id) REFERENCES dokument(id),
|
|
ADD CONSTRAINT fk_kanton_bauverordnung_revision
|
|
FOREIGN KEY (bauverordnung_revision_id) REFERENCES dokument(id);
|
|
|
|
ALTER TABLE gemeinde
|
|
ADD CONSTRAINT fk_gemeinde_bzo_aktuell
|
|
FOREIGN KEY (bzo_aktuell_id) REFERENCES dokument(id),
|
|
ADD CONSTRAINT fk_gemeinde_bzo_revision
|
|
FOREIGN KEY (bzo_revision_id) REFERENCES dokument(id);
|
|
|
|
-- ============================================================================
|
|
-- INDICES für Performance
|
|
-- ============================================================================
|
|
|
|
-- Projekt Indices
|
|
CREATE INDEX idx_projekt_status ON projekt USING GIN (status_prozess);
|
|
|
|
-- Parzelle Indices
|
|
CREATE INDEX idx_parzelle_land ON parzelle(land_id);
|
|
CREATE INDEX idx_parzelle_kanton ON parzelle(kanton_id);
|
|
CREATE INDEX idx_parzelle_gemeinde ON parzelle(gemeinde_id);
|
|
CREATE INDEX idx_parzelle_bauzone ON parzelle(bauzone);
|
|
CREATE INDEX idx_parzelle_geo_umfang ON parzelle USING GIST(geo_umfang);
|
|
|
|
-- Dokument Indices
|
|
CREATE INDEX idx_dokument_typ ON dokument(typ);
|
|
CREATE INDEX idx_dokument_tags ON dokument USING GIN (tags);
|
|
|
|
-- GeoPunkt Indices
|
|
CREATE INDEX idx_geopunkt_projekt ON geo_punkt(projekt_id);
|
|
CREATE INDEX idx_geopunkt_referenzen ON geo_punkt USING GIN (referenzen);
|
|
|
|
-- Kontext Indices
|
|
CREATE INDEX idx_kontext_projekt ON kontext(projekt_id);
|
|
CREATE INDEX idx_kontext_parzelle ON kontext(parzelle_id);
|
|
CREATE INDEX idx_kontext_land ON kontext(land_id);
|
|
CREATE INDEX idx_kontext_kanton ON kontext(kanton_id);
|
|
CREATE INDEX idx_kontext_gemeinde ON kontext(gemeinde_id);
|
|
CREATE INDEX idx_kontext_thema ON kontext(thema);
|
|
|
|
-- ============================================================================
|
|
-- TRIGGER für updated_at
|
|
-- ============================================================================
|
|
|
|
CREATE OR REPLACE FUNCTION update_updated_at_column()
|
|
RETURNS TRIGGER AS $$
|
|
BEGIN
|
|
NEW.updated_at = CURRENT_TIMESTAMP;
|
|
RETURN NEW;
|
|
END;
|
|
$$ language 'plpgsql';
|
|
|
|
CREATE TRIGGER update_land_updated_at BEFORE UPDATE ON land
|
|
FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
|
|
|
|
CREATE TRIGGER update_kanton_updated_at BEFORE UPDATE ON kanton
|
|
FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
|
|
|
|
CREATE TRIGGER update_gemeinde_updated_at BEFORE UPDATE ON gemeinde
|
|
FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
|
|
|
|
CREATE TRIGGER update_dokument_updated_at BEFORE UPDATE ON dokument
|
|
FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
|
|
|
|
CREATE TRIGGER update_projekt_updated_at BEFORE UPDATE ON projekt
|
|
FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
|
|
|
|
CREATE TRIGGER update_parzelle_updated_at BEFORE UPDATE ON parzelle
|
|
FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
|
|
|
|
CREATE TRIGGER update_geo_punkt_updated_at BEFORE UPDATE ON geo_punkt
|
|
FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
|
|
|
|
CREATE TRIGGER update_kontext_updated_at BEFORE UPDATE ON kontext
|
|
FOR EACH ROW EXECUTE FUNCTION update_updated_at_column();
|
|
|
|
-- ============================================================================
|
|
-- VIEWS für häufige Abfragen
|
|
-- ============================================================================
|
|
|
|
-- View: Parzellen mit vollständigem geografischem Kontext
|
|
CREATE VIEW v_parzelle_vollstaendig AS
|
|
SELECT
|
|
p.*,
|
|
l.label as land_name,
|
|
k.label as kanton_name,
|
|
g.label as gemeinde_name,
|
|
g.plz as gemeinde_plz,
|
|
ST_AsGeoJSON(p.geo_umfang) as geo_umfang_geojson,
|
|
ST_Area(p.geo_umfang) as flaeche_m2
|
|
FROM parzelle p
|
|
LEFT JOIN land l ON p.land_id = l.id
|
|
LEFT JOIN kanton k ON p.kanton_id = k.id
|
|
LEFT JOIN gemeinde g ON p.gemeinde_id = g.id;
|
|
|
|
-- View: Projekte mit Perimeter-Information
|
|
CREATE VIEW v_projekt_mit_perimeter AS
|
|
SELECT
|
|
pr.id,
|
|
pr.label,
|
|
pr.status_prozess,
|
|
COUNT(DISTINCT pp.parzelle_id) as anzahl_parzellen,
|
|
STRING_AGG(DISTINCT pa.label, ', ') as parzellen_labels
|
|
FROM projekt pr
|
|
LEFT JOIN projekt_parzelle pp ON pr.id = pp.projekt_id
|
|
LEFT JOIN parzelle pa ON pp.parzelle_id = pa.id
|
|
GROUP BY pr.id, pr.label, pr.status_prozess;
|
|
|
|
-- ============================================================================
|
|
-- BEISPIELDATEN
|
|
-- ============================================================================
|
|
|
|
-- Land Schweiz
|
|
INSERT INTO land (label) VALUES ('Schweiz');
|
|
|
|
-- Kantone (Beispiele)
|
|
INSERT INTO kanton (label) VALUES
|
|
('Zürich'),
|
|
('Bern'),
|
|
('Luzern');
|
|
|
|
-- Gemeinden (Beispiele für Zürich)
|
|
INSERT INTO gemeinde (label, plz) VALUES
|
|
('Zürich', '8000'),
|
|
('Winterthur', '8400'),
|
|
('Uster', '8610');
|
|
|
|
-- ============================================================================
|
|
-- KOMMENTARE
|
|
-- ============================================================================
|
|
|
|
COMMENT ON TABLE projekt IS 'Bauprojekte mit Status und Perimeter';
|
|
COMMENT ON TABLE parzelle IS 'Grundstücke mit baulichen und rechtlichen Eigenschaften';
|
|
COMMENT ON TABLE dokument IS 'Dokumente und URLs mit Versionierung';
|
|
COMMENT ON TABLE geo_punkt IS '3D-Punkte im LV95-Koordinatensystem';
|
|
COMMENT ON TABLE kontext IS 'Flexible Kontextinformationen für verschiedene Entitäten';
|
|
|
|
COMMENT ON COLUMN parzelle.geo_umfang IS 'Parzellengrenze als PostGIS Polygon im LV95 (EPSG:2056)';
|
|
COMMENT ON COLUMN parzelle.az IS 'Ausnützungsziffer';
|
|
COMMENT ON COLUMN parzelle.bz IS 'Bebauungsziffer';
|
|
COMMENT ON COLUMN geo_punkt.x IS 'LV95 Ostwert (E), typisch 2480000-2840000';
|
|
COMMENT ON COLUMN geo_punkt.y IS 'LV95 Nordwert (N), typisch 1070000-1300000';
|
|
COMMENT ON COLUMN geo_punkt.z IS 'Höhe über Meer in Metern';
|
|
|
|
-- ============================================================================
|
|
-- Ende der Migration
|
|
-- ============================================================================
|