source: trunk/import/alkis-functions.sql @ 336

Revision 336, 25.0 KB checked in by frank.jaeger, 10 years ago (diff)

Schema zurück auf 16stellige gml_id.
Überarbeiten Trigger-Funktion "delete_feature_hist()" - noch nicht ausgetestet.

Line 
1-- alkis-functions.sql - Trigger-Funktionen fÃŒr die FortfÃŒhrung der
2--                       alkis_beziehungen aus EintrÀgen der delete-Tabelle
3
4-- 2013-07-10: Erweiterung zur Verarbeitung der Replace-SÀtze in ALKIS-Beziehungen
5
6-- 2013-12-10:  In der Function "update_fields_beziehungen" den Fall behandeln, dass ein Objekt einer
7--             neuen Beziehung in keiner Tabelle gefunden wird.
8--             Wenn ein einzelnes Objekt fehlt, soll dies keine Auswirkungen auf andere Objekte haben.
9--             FÃŒllen von "zu_typename" auskommentiert.
10
11-- 2014-01-31: Deaktivieren von "update_fields_beziehungen",
12--             statt dessen verwenden der "import_id" um alte Relationen zu identifizieren und zu löschen.
13
14-- 2014-08-27: Angleichung des Datenbank-Schema an die NorBIT-Version.
15--             Die Trigger-Function "delete_feature_kill()" arbeitet falsch, wenn "gml_id" als "character varying" angelegt ist.
16--             Das Format war bisher charachter(16).
17--             Zugriff auf die Spalte gml_id umgestellt von "=" auf "like" um den individuellen Timestamp zu ignorieren.
18
19-- 2014-09-04  Trigger-Funktion "delete_feature_kill()" angepasst: keine Tabelle "alkis_beziehungen" mehr.
20
21-- 2014-09-11  Functions auskommentiert oder gelöscht, die "alkis_beziehungen" benötigen:
22--               "alkis_mviews()", delete_feature_kill_vers07(), alkis_beziehung_inserted()
23--             Trigger-Function "delete_feature_hist" durch aktuelle Version aus OSGeo4W ersetzt.
24
25-- 2014-09-19  FJ: Korrektur "delete_feature_hist()":
26--             Ausgehend von Version: https://raw.githubusercontent.com/norBIT/alkisimport/master/alkis-functions.sql
27--             Bei der Erstabagabe mit Vollhistorie (ibR) werden mehrere ZwischenstÀnde von Objekten eingelesen.
28--             Einige davon wurden bereits mit "endet" ausgeliefert (in replace-SÀtzen).
29--             Wenn der Trigger ausgelöst wird (in einem zweiten Durchlauf von PostNAS) kann es jeweils
30--             mehrerer VorgÀnger- und Nachfolger-Objekte mit und ohne "endet IS NULL" geben.
31
32
33-- Table/View/Sequence löschen, wenn vorhanden
34CREATE OR REPLACE FUNCTION alkis_dropobject(t TEXT) RETURNS varchar AS $$
35DECLARE
36        c RECORD;
37        s varchar;
38        r varchar;
39        d varchar;
40        i integer;
41        tn varchar;
42BEGIN
43        r := '';
44        d := '';
45
46        -- drop objects
47        FOR c IN SELECT relkind,relname
48                FROM pg_class
49                JOIN pg_namespace ON pg_class.relnamespace=pg_namespace.oid
50                WHERE pg_namespace.nspname='public' AND pg_class.relname=t
51                ORDER BY relkind
52        LOOP
53                IF c.relkind = 'v' THEN
54                        r := r || d || 'Sicht ' || c.relname || ' gelöscht.';
55                        EXECUTE 'DROP VIEW ' || c.relname || ' CASCADE';
56                ELSIF c.relkind = 'r' THEN
57                        r := r || d || 'Tabelle ' || c.relname || ' gelöscht.';
58                        EXECUTE 'DROP TABLE ' || c.relname || ' CASCADE';
59                ELSIF c.relkind = 'S' THEN
60                        r := r || d || 'Sequenz ' || c.relname || ' gelöscht.';
61                        EXECUTE 'DROP SEQUENCE ' || c.relname;
62                ELSIF c.relkind <> 'i' THEN
63                        r := r || d || 'Typ ' || c.table_type || '.' || c.table_name || ' unerwartet.';
64                END IF;
65                d := E'\n';
66        END LOOP;
67
68        FOR c IN SELECT indexname FROM pg_indexes WHERE schemaname='public' AND indexname=t
69        LOOP
70                r := r || d || 'Index ' || c.indexname || ' gelöscht.';
71                EXECUTE 'DROP INDEX ' || c.indexname;
72                d := E'\n';
73        END LOOP;
74
75        FOR c IN SELECT proname,proargtypes
76                FROM pg_proc
77                JOIN pg_namespace ON pg_proc.pronamespace=pg_namespace.oid
78                WHERE pg_namespace.nspname='public' AND pg_proc.proname=t
79        LOOP
80                r := r || d || 'Funktion ' || c.proname || ' gelöscht.';
81
82                s := 'DROP FUNCTION ' || c.proname || '(';
83                d := '';
84
85                FOR i IN array_lower(c.proargtypes,1)..array_upper(c.proargtypes,1) LOOP
86                        SELECT typname INTO tn FROM pg_type WHERE oid=c.proargtypes[i];
87                        s := s || d || tn;
88                        d := ',';
89                END LOOP;
90
91                s := s || ')';
92
93                EXECUTE s;
94
95                d := E'\n';
96        END LOOP;
97
98        FOR c IN SELECT relname,conname
99                FROM pg_constraint
100                JOIN pg_class ON pg_constraint.conrelid=pg_constraint.oid
101                JOIN pg_namespace ON pg_constraint.connamespace=pg_namespace.oid
102                WHERE pg_namespace.nspname='public' AND pg_constraint.conname=t
103        LOOP
104                r := r || d || 'Constraint ' || c.conname || ' von ' || c.relname || ' gelöscht.';
105                EXECUTE 'ALTER TABLE ' || c.relname || ' DROP CONSTRAINT ' || c.conname;
106                d := E'\n';
107        END LOOP;
108
109        RETURN r;
110END;
111$$ LANGUAGE plpgsql;
112
113-- Alle ALKIS-Tabellen löschen
114SELECT alkis_dropobject('alkis_drop');
115CREATE FUNCTION alkis_drop() RETURNS varchar AS $$
116DECLARE
117        c RECORD;
118        r VARCHAR;
119        d VARCHAR;
120BEGIN
121        r := '';
122        d := '';
123        -- drop tables & views
124        FOR c IN SELECT table_type,table_name FROM information_schema.tables WHERE table_schema='public' AND ( substr(table_name,1,3) IN ('ax_','ap_','ks_') OR table_name IN ('alkis_beziehungen','delete')) ORDER BY table_type DESC LOOP
125                IF c.table_type = 'VIEW' THEN
126                        r := r || d || 'Sicht ' || c.table_name || ' gelöscht.';
127                        EXECUTE 'DROP VIEW ' || c.table_name || ' CASCADE';
128                ELSIF c.table_type = 'BASE TABLE' THEN
129                        r := r || d || 'Tabelle ' || c.table_name || ' gelöscht.';
130                        EXECUTE 'DROP TABLE ' || c.table_name || ' CASCADE';
131                ELSE
132                        r := r || d || 'Typ ' || c.table_type || '.' || c.table_name || ' unerwartet.';
133                END IF;
134                d := E'\n';
135        END LOOP;
136
137        -- clean geometry_columns
138        DELETE FROM geometry_columns
139                WHERE f_table_schema='public'
140                AND ( substr(f_table_name,1,2) IN ('ax_','ap_','ks_')
141                 OR f_table_name IN ('alkis_beziehungen','delete') );
142
143        RETURN r;
144END;
145$$ LANGUAGE plpgsql;
146
147-- Alle ALKIS-Tabellen leeren
148SELECT alkis_dropobject('alkis_delete');
149CREATE FUNCTION alkis_delete() RETURNS varchar AS $$
150DECLARE
151        c RECORD;
152        r varchar;
153        d varchar;
154BEGIN
155        r := '';
156        d := '';
157
158        -- drop views
159        FOR c IN
160                SELECT table_name
161                FROM information_schema.tables
162                WHERE table_schema='public' AND table_type='BASE TABLE'
163                  AND ( substr(table_name,1,3) IN ('ax_','ap_','ks_')
164                        OR table_name IN ('alkis_beziehungen','delete') )
165        LOOP
166                r := r || d || c.table_name || ' wurde geleert.';
167                EXECUTE 'DELETE FROM '||c.table_name;
168                d := E'\n';
169        END LOOP;
170
171        RETURN r;
172END;
173$$ LANGUAGE plpgsql;
174
175-- Übersicht erzeugen, die alle alkis_beziehungen mit den Typen der beteiligen ALKIS-Objekte versieht
176/*
177SELECT alkis_dropobject('alkis_mviews');
178CREATE FUNCTION alkis_mviews() RETURNS varchar AS $$
179DECLARE
180        sql TEXT;
181        delim TEXT;
182        c RECORD;
183BEGIN
184        SELECT alkis_dropobject('vbeziehungen') INTO sql;
185        SELECT alkis_dropobject('vobjekte') INTO sql;
186
187        delim := '';
188        sql := 'CREATE VIEW vobjekte AS ';
189
190        FOR c IN SELECT table_name FROM information_schema.columns WHERE column_name='gml_id' AND substr(table_name,1,3) IN ('ax_','ap_','ks_') LOOP
191                sql := sql || delim || 'SELECT gml_id,beginnt,''' || c.table_name || ''' AS table_name FROM ' || c.table_name;
192                delim := ' UNION ';
193        END LOOP;
194
195        EXECUTE sql;
196
197        CREATE VIEW vbeziehungen AS
198                SELECT  beziehung_von,(SELECT table_name FROM vobjekte WHERE gml_id=beziehung_von) AS typ_von
199                        ,beziehungsart
200                        ,beziehung_zu,(SELECT table_name FROM vobjekte WHERE gml_id=beziehung_zu) AS typ_zu
201                FROM alkis_beziehungen;
202
203        RETURN 'ALKIS-Views erzeugt.';
204END;
205$$ LANGUAGE plpgsql;
206*/
207
208-- Indizes erzeugen
209SELECT alkis_dropobject('alkis_update_schema');
210CREATE FUNCTION alkis_update_schema() RETURNS varchar AS $$
211DECLARE
212        sql TEXT;
213        c RECORD;
214        i RECORD;
215        n INTEGER;
216BEGIN
217        -- Spalten in delete ergÀnzen
218        SELECT count(*) INTO n FROM information_schema.columns WHERE table_schema='public' AND table_name='delete' AND column_name='ignored';
219        IF n=0 THEN
220                ALTER TABLE "delete" ADD ignored BOOLEAN;
221        END IF;
222
223        SELECT count(*) INTO n FROM information_schema.columns WHERE table_schema='public' AND table_name='delete' AND column_name='context';
224        IF n=0 THEN
225                ALTER TABLE "delete" ADD context VARCHAR;
226        END IF;
227
228        SELECT count(*) INTO n FROM information_schema.columns WHERE table_schema='public' AND table_name='delete' AND column_name='safetoignore';
229        IF n=0 THEN
230                ALTER TABLE "delete" ADD safetoignore VARCHAR;
231        END IF;
232
233        SELECT count(*) INTO n FROM information_schema.columns WHERE table_schema='public' AND table_name='delete' AND column_name='replacedby';
234        IF n=0 THEN
235                ALTER TABLE "delete" ADD replacedBy VARCHAR;
236        END IF;
237
238        -- Spalte identifier ergÀnzen, wo sie fehlt
239        FOR c IN SELECT table_name FROM information_schema.columns a WHERE a.column_name='gml_id'
240                AND     EXISTS (SELECT * FROM information_schema.columns b WHERE b.column_name='beginnt'    AND a.table_catalog=b.table_catalog AND a.table_schema=b.table_schema AND a.table_name=b.table_name)
241                AND NOT EXISTS (SELECT * FROM information_schema.columns b WHERE b.column_name='identifier' AND a.table_catalog=b.table_catalog AND a.table_schema=b.table_schema AND a.table_name=b.table_name)
242        LOOP
243                EXECUTE 'ALTER TABLE ' || c.table_name || ' ADD identifier character(44)';
244        END LOOP;
245
246        -- Spalte endet ergÀnzen, wo sie fehlt
247        FOR c IN SELECT table_name FROM information_schema.columns a WHERE a.column_name='gml_id'
248                AND     EXISTS (SELECT * FROM information_schema.columns b WHERE b.column_name='beginnt' AND a.table_catalog=b.table_catalog AND a.table_schema=b.table_schema AND a.table_name=b.table_name)
249                AND NOT EXISTS (SELECT * FROM information_schema.columns b WHERE b.column_name='endet'   AND a.table_catalog=b.table_catalog AND a.table_schema=b.table_schema AND a.table_name=b.table_name)
250        LOOP
251                EXECUTE 'ALTER TABLE ' || c.table_name || ' ADD endet character(20) CHECK (endet>beginnt)';
252        END LOOP;
253
254        -- Lebensdauer-Constraint ergÀnzen
255        FOR c IN SELECT table_name FROM information_schema.columns a WHERE a.column_name='gml_id'
256                AND EXISTS (SELECT * FROM information_schema.columns b WHERE b.column_name='beginnt' AND a.table_catalog=b.table_catalog AND a.table_schema=b.table_schema AND a.table_name=b.table_name)
257                AND EXISTS (SELECT * FROM information_schema.columns b WHERE b.column_name='endet'   AND a.table_catalog=b.table_catalog AND a.table_schema=b.table_schema AND a.table_name=b.table_name)
258        LOOP
259                SELECT alkis_dropobject(c.table_name||'_lebensdauer');
260                EXECUTE 'ALTER TABLE ' || c.table_name || ' ADD CONSTRAINT ' || c.table_name || '_lebensdauer CHECK (beginnt IS NOT NULL AND endet>beginnt)';
261        END LOOP;
262
263        -- Indizes aktualisieren
264        FOR c IN SELECT table_name FROM information_schema.columns a WHERE a.column_name='gml_id'
265                AND EXISTS (SELECT * FROM information_schema.columns b WHERE b.column_name='beginnt' AND a.table_catalog=b.table_catalog AND a.table_schema=b.table_schema AND a.table_name=b.table_name)
266        LOOP
267                -- Vorhandene Indizes droppen (TODO: Löscht auch die SonderfÀlle - entfernen)
268                FOR i IN EXECUTE 'SELECT indexname FROM pg_indexes WHERE NOT indexname LIKE ''%_pk'' AND schemaname=''public'' AND tablename='''||c.table_name||'''' LOOP
269                        EXECUTE 'DROP INDEX ' || i.indexname;
270                END LOOP;
271
272                -- Indizes erzeugen
273                EXECUTE 'CREATE UNIQUE INDEX ' || c.table_name || '_id ON ' || c.table_name || '(gml_id,beginnt)';
274                EXECUTE 'CREATE UNIQUE INDEX ' || c.table_name || '_ident ON ' || c.table_name || '(identifier)';
275                EXECUTE 'CREATE INDEX ' || c.table_name || '_gmlid ON ' || c.table_name || '(gml_id)';
276                EXECUTE 'CREATE INDEX ' || c.table_name || '_beginnt ON ' || c.table_name || '(beginnt)';
277                EXECUTE 'CREATE INDEX ' || c.table_name || '_endet ON ' || c.table_name || '(endet)';
278        END LOOP;
279
280        -- Geometrieindizes aktualisieren
281        FOR c IN SELECT table_name FROM information_schema.columns a WHERE a.column_name='gml_id'
282                AND EXISTS (SELECT * FROM information_schema.columns b WHERE b.column_name='wkb_geometry' AND a.table_catalog=b.table_catalog AND a.table_schema=b.table_schema AND a.table_name=b.table_name)
283        LOOP
284                EXECUTE 'CREATE INDEX ' || c.table_name || '_geom ON ' || c.table_name || ' USING GIST (wkb_geometry)';
285        END LOOP;
286
287        RETURN 'Schema aktualisiert.';
288END;
289$$ LANGUAGE plpgsql;
290
291
292-- Im Trigger 'delete_feature_trigger' muss eine dieser beiden Funktionen
293-- (delete_feature_hist oder delete_feature_kill) verlinkt werden, je nachdem ob nur
294-- aktuelle oder auch historische Objekte in der Datenbank gefÃŒhrt werden sollen.
295
296
297/*      Beschreibung und Umfeld des "delete_feature_trigger":
298        -----------------------------------------------------
299
300Der Konverter "ogr2ogr", in den PostNAS als Eingabe-Modul fÃŒr das NAS-Format eingebettet ist,
301ist vom Wesen her eigentlich ein 1:1-Konverter.
302Üblicherweise liest er ein Eingabe-GIS-Format, analysiert es und erzeugt dann die gleiche Struktur in
303einem Ausgabe-GIS-Format.
304Das ALKIS-Format "NAS" als einmaliger Datenauszug (enthÀlt nur Funktion "insert") könnte in diesem
305Rahmen vielleicht auch ohne Trigger umgesetzt werden.
306
307Viel komplexer umzusetzen sind jedoch solche NAS-Daten, die im Rahmen des NBA-Verfahrens von ALKIS abgegeben werden.
308                NBA  =  Nutzerbezogene Bestandsdaten-Aktualisierung.
309In einem NBA-Verfahren wird eine primÀre Datenquelle (ALKIS) mit einem SekundÀrbestand (PostGIS) synchronisiert.
310Es erfolgt zunÀchst eine Erstabgabe fÌr einen definierten Zeitpunkt.
311SpÀter gibt es Aktualisierungen auf weitere Zeitpunkte. Die dazu Ìbermittelten Differenzdaten enthalten
312nicht nur reine Daten (INSERT) sondern auch Anweisungen zur Änderung der zu frÃŒheren Zeitpunkten ÃŒbermittelten Daten.
313Diese Änderungs-Anweisungen werden innerhalb des Konverters ogr2ogr nicht komplett verarbeitet.
314Er verarbeitet zunÀchst nur die enthaltenen Datenfelder zum Objekt, die er in neue Zeilen in die Objekt-Tabellen einstellt.
315
316Anschließend werden Informationen zum ObjektschlÃŒssel (gml_id) und zur Lebensdauer des Objektes (beginnt) zusammen
317mit der Operation (Kontext = "delete", "update" oder "replace") in die Tabelle "delete" eingetragen.
318Dieser Eintrag in "delete" löst den Trigger aus, der sich dann darum kÌmmert, Löschungen oder
319Änderungen an VorgÀngerversionen vorzunehmen.
320
321Im NBA-Verfahren sind verschiedene "Abgabearten" möglich:
322Die Abgabeart 1000 hat zum Ziel, im SekundÀrbestand jeweils den letzten aktuellen Stand bereit zu stellen.
323Die Abgabeart 3100 hat zum Ziel, im SekundÀrbestand eine komplette Historie bereit zu stellen, die auch alle
324ZwischenstÀnde enthÀlt. Ein nicht mehr gÌltiges Objekt wird dann mit einem Eintrag in "endet" deaktiviert,
325verbleibt aber in der Datenbank. Bei der Abgabeart 3100 sind bereits in der Erstabgabe Aktualisierungs-
326Funktionen (delete, update, replace) enthalten weil mehrere historische Versionen von Objekten geliefert werden.
327
328Eine NBA-Lieferung fÃŒr ein Gebiet ist in mehrere Dateien aufgeteilt, die nacheinander abgearbeitet werden.
329Erst mit der Verarbeitung der letzten Datei einer Lieferung ist die Datenbank wieder konsistent und zeigt den
330Zustand zum neuen Abgabezeitpunkt.
331
332Jede dieser NAS-Dateien wird von PostNAS in mehreren DurchlÀufen vearbeitet.
3331. Im ersten Durchlauf wird die 1:1-Konvertierung der Daten vorgenommen.
334   Die Feldinhalte der NAS-Datei werden in neue Zeilen in die Objekttabellen der Datenbank ÃŒbertragen.
3352. Dann werden in einem weiteren Durchlauf die Operationen "delete", "update" und "replace" verarbeitet.
336   Diese werden von PostNAS in die Tabelle "delete" eingetragen, dies löst den Trigger aus.
337       
338FÃŒr die Arbeitsweise des Triggers bedeutet das:
339
340An dem Zeitpunkt, an dem der Trigger ausgelöst wird, stehen bereits alle Daten zu den Objekten in den Objekt-Tabellen.
341Darin ist aber möglicherweise das Feld "endet" noch nicht gefÌllt.
342
343WÀhrend der Konvertierung der Erstabgabe einer NBA-Abgabe der Abgabeart 3100 können aber Objekte doch schon beendet sein.
344Bei einer Erstabgabe der Abgabeart 3100 können mehrere Generation des selben Objektes vorhanden sein,
345die alle in der gleichen NAS-Datei geliefert wurden.
346Das Feld "endet" ist dann nicht geeignet zu entscheiden, welches die letzte (aktuelle) Version ist.
347
348Es kann vorkommen, dass Zwischenversionen in der Objekt-Tabelle bereits beendet sind weil sie direkt mit ihrem
349Endet-Datum geliefert wurden. Dieses wurde bereits beim ersten Durchlauf von ogr2ogr wie ein normales Datenfeld eingetragen.
350In Beispieldaten wurde analysiert, dass ein bereits beendetes Objekt in einem "insert" kein "endet" mitbringt.
351Dies muss vom Trigger beendet werden, wenn dieser einen replace fÃŒr den Nachfolger bekommt.
352
353Im gleichen Bestand wurden jedoch Nachfolger gefunden die mit einem "replace"-Satz gekommen sind
354und bereits beendet waren, weil sie ihrerseits wieder Nachfolger hatten.
355
356Das jeweils folgende "replace" kann also ein VorgÀnger-Objekt mit oder ohne "endet"-Eintrag vorfinden.
357Es können auch sowohl VorgÀnger- als auch bereits Nachfolger-Versionen eines Objektes vorhanden sein, wenn der Trigger
358ausgelöst wird.
359
360Aufgabe des Triggers ist es, zu einem verÀnderten Objekt jeweils den unmittelbaren VorgÀnger zu ermitteln
361und - falls noch nicht geschehen - den passenden endet-Eintrag nachzutragen.
362Wenn in den Daten kein "endet" mitgeliefert wird, dann wird der Beginn der Folge-Version des Objektes verwendet
363um den VorgÀnger zu beenden.
364
365Wenn ein Objekt bereits mit endet-Datum geliefert wurde, dann wird dies zwar in die Obkjekt-Tabelle eingetragen,
366der endet-Eintrag in dem replace-Satz in der delete-Tabelle, der den Trigger auslöst, ist trotzdem leer.
367Es ist Ìberlegen, ob dies im PostNAS-Programm geÀndert werden sollte.
368
369Aufgrund der KomplexitÀt dieser Mechanismen ist davon auszugehen, dass es Hersteller-spezifische Unterschiede
370gibt und auch Unterschiede zuwischen verschiedenen Versions-StÀnden des selben Herstellers.
371Die Arbeitsweise des Triggers muss daher regelmÀßig ÃŒberprÃŒft werden.
372
373*/
374
375-- Achtung: FÃŒr diese Trigger-Version mÃŒssen die SchlÃŒsselfelder "gml_id" in allen Tabellen
376--          wieder auf 16 Stellen fix gekÃŒrzt werden!
377
378-- Löschsatz verarbeiten (MIT Historie)
379-- context='delete'        => "endet" auf aktuelle Zeit setzen
380-- context='replace'       => "endet" des ersetzten auf "beginnt" des neuen Objekts setzen
381-- context='update'        => "endet" auf ÃŒbergebene Zeit setzen und "anlass" festhalten
382CREATE OR REPLACE FUNCTION delete_feature_hist() RETURNS TRIGGER AS $$
383DECLARE
384        n INTEGER;
385        vbeginnt TEXT;
386        replgml TEXT;
387        featgml TEXT;
388        s TEXT;
389BEGIN
390        NEW.context := coalesce(lower(NEW.context),'delete');
391
392        IF NEW.anlass IS NULL THEN
393                NEW.anlass := '';
394        END IF;
395        featgml := substr(NEW.featureid, 1, 16); -- gml_id ohne Timestamp
396
397        IF length(NEW.featureid)=32 THEN
398                -- beginnt-Zeit der zu ersetzenden Vorgaenger-Version des Objektes
399                vbeginnt := substr(NEW.featureid, 17, 4) || '-'
400                        || substr(NEW.featureid, 21, 2) || '-'
401                        || substr(NEW.featureid, 23, 2) || 'T'
402                        || substr(NEW.featureid, 26, 2) || ':'
403                        || substr(NEW.featureid, 28, 2) || ':'
404                        || substr(NEW.featureid, 30, 2) || 'Z' ;
405        ELSIF length(NEW.featureid)=16 THEN
406                -- Ältestes nicht gelöschtes Objekt
407                EXECUTE 'SELECT min(beginnt) FROM ' || NEW.typename
408                        || ' WHERE gml_id=''' || featgml || ''''
409                        || ' AND endet IS NULL'
410                        INTO vbeginnt;
411
412                IF vbeginnt IS NULL THEN
413                        RAISE EXCEPTION '%: Keinen Kandidaten zum Löschen gefunden.', NEW.featureid;
414                END IF;
415        ELSE
416                RAISE EXCEPTION '%: Identifikator gescheitert.', NEW.featureid;
417        END IF;
418
419        IF NEW.context='delete' THEN
420                NEW.endet := to_char(CURRENT_TIMESTAMP AT TIME ZONE 'UTC','YYYY-MM-DD"T"HH24:MI:SS"Z"');
421
422        ELSIF NEW.context='update' THEN
423                IF NEW.endet IS NULL THEN
424                        RAISE EXCEPTION '%: Endedatum nicht gesetzt', NEW.featureid;
425                END IF;
426
427        ELSIF NEW.context='replace' THEN
428                NEW.safetoignore := lower(NEW.safetoignore);
429                replgml := substr(NEW.replacedby, 1, 16); -- ReplcedBy gml_id ohne Timestamp
430                IF NEW.safetoignore IS NULL THEN
431                        RAISE EXCEPTION '%: safeToIgnore nicht gesetzt.', NEW.featureid;
432                ELSIF NEW.safetoignore<>'true' AND NEW.safetoignore<>'false' THEN
433                        RAISE EXCEPTION '%: safeToIgnore ''%'' ungÃŒltig (''true'' oder ''false'' erwartet).', NEW.featureid, NEW.safetoignore;
434                END IF;
435
436                IF length(NEW.replacedby)=32 AND NEW.replacedby<>NEW.featureid THEN
437                        NEW.endet := substr(NEW.replacedby, 17, 4) || '-'
438                                || substr(NEW.replacedby, 21, 2) || '-'
439                                || substr(NEW.replacedby, 23, 2) || 'T'
440                                || substr(NEW.replacedby, 26, 2) || ':'
441                                || substr(NEW.replacedby, 28, 2) || ':'
442                                || substr(NEW.replacedby, 30, 2) || 'Z' ;
443                END IF;
444
445                -- Satz-Paarung VorgÀnger-Nachfolger in der Objekttabelle suchen.
446                -- Der VorgÀnger muss noch beendet werden. Der Nachfolger kann bereits beendet sein.
447                -- Das "beginn" des Nachfolgers anschließend als "endet" des Vorgaengers verwenden.
448                -- Normalfall bei NBA-Aktualisierungslaeufen. v=VorgÀnger, n=Nachfolger.
449                IF NEW.endet IS NULL THEN
450                        EXECUTE 'SELECT min(n.beginnt) FROM ' || NEW.typename || ' n'
451                                || ' JOIN ' || NEW.typename || ' v ON v.ogc_fid<n.ogc_fid'
452                                || ' WHERE v.gml_id=''' || featgml
453                                || ''' AND n.gml_id=''' || replgml
454                                || ''' AND v.endet IS NULL'
455                                INTO NEW.endet;
456                --      RAISE NOTICE 'endet setzen fuer Vorgaenger % ', NEW.endet;
457                END IF;
458
459                -- Satz-Paarung VorgÀnger-Nachfolger in der Objekttabelle suchen.
460                -- Der VorgÀnger ist bereits beendet worden weil "endet" in den Daten gefÃŒllt war.
461                -- Dieser Fall kommt bei der Erstabgabe mit Vollhistorie vor.
462                IF NEW.endet IS NULL THEN
463                        EXECUTE 'SELECT min(n.beginnt) FROM ' || NEW.typename || ' n'
464                                || ' JOIN ' || NEW.typename || ' v ON v.endet=n.beginnt '
465                                || ' WHERE v.gml_id=''' || featgml
466                                || ''' AND n.gml_id=''' || replgml
467                                || ''' AND v.beginnt=''' || vbeginnt || ''''
468                                INTO NEW.endet;
469
470                        IF NOT NEW.endet IS NULL THEN
471                        --      RAISE NOTICE '%: Vorgaenger ist schon endet', NEW.featureid;
472                                NEW.ignored=false;
473                                RETURN NEW;
474                        END IF;
475                END IF;
476
477                IF NEW.endet IS NULL THEN -- "endet" fÃŒr den VorgÀnger konnte nicht ermittelt werden
478                        IF NEW.safetoignore='false' THEN
479                                RAISE EXCEPTION '%: Beginn des ersetzenden Objekts % nicht gefunden.', NEW.featureid, NEW.replacedby;
480                        END IF;
481                        NEW.ignored=true;
482                        RETURN NEW;
483                END IF;
484        ELSE
485                RAISE EXCEPTION '%: UngÃŒltiger Kontext % (''delete'', ''replace'' oder ''update'' erwartet).', NEW.featureid, NEW.context;
486        END IF;
487
488        -- Vorgaenger ALKIS-Objekt nun beenden
489        s := 'UPDATE ' || NEW.typename
490          || ' SET endet=''' || NEW.endet || ''' ,anlass=''' || NEW.anlass || ''''
491          || ' WHERE gml_id=''' || featgml || ''' AND beginnt=''' || vbeginnt || '''' ;
492        EXECUTE s;
493        GET DIAGNOSTICS n = ROW_COUNT;
494        -- RAISE NOTICE 'SQL[%]:%', n, s;
495        IF n<>1 THEN
496                RAISE EXCEPTION '%: % schlug fehl [%]', NEW.featureid, NEW.context, n;
497        END IF;
498
499        NEW.ignored := false;
500        RETURN NEW;
501END;
502$$ LANGUAGE plpgsql;
503
504
505-- 2014-09-04: Version 0.8 ohne "alkis_beziehungen"-Tabelle
506-- Unterschied von "delete_feature_kill" zur Version "delete_feature_hist":
507--  Historisch gewordene Objekte werden nicht auf "endet" gesetzt sondern ganz aus der Datenbank gelöscht.
508-- Achtung: Wenn diese Funktion mit dem "delete_feature_trigger" der Tabelle "delete" verknÃŒpft ist,
509-- dann dÃŒrfen nur NAS-NBA-Daten verarbeitet werden, die mit der Abgabeart 1000 erzeugt wurden.
510-- Wenn Daten der Abgabeart 3100 verarbeitet werden kommen update-Anweisungen in den Daten vor,
511-- die dieser Trigger nicht verarbeiten kann.
512CREATE OR REPLACE FUNCTION delete_feature_kill() RETURNS TRIGGER AS $$
513DECLARE
514        begsql TEXT;
515        aktbeg TEXT;
516        gml_id TEXT;
517BEGIN
518        NEW.typename := lower(NEW.typename); -- Objektart=Tabellen-Name
519        NEW.context := lower(NEW.context);   -- Operation 'delete'/'replace'/'update'
520        gml_id      := substr(NEW.featureid, 1, 16); -- ID-Teil der gml_id, ohne Timestamp
521
522        IF NEW.context IS NULL THEN
523                NEW.context := 'delete'; -- default
524        END IF;
525        IF NEW.context='delete' THEN -- Löschen des Objektes
526          -- In der Objekt-Tabelle
527                EXECUTE 'DELETE FROM ' || NEW.typename || ' WHERE gml_id like ''' || gml_id || '%''';
528                --RAISE NOTICE 'Lösche gml_id % in %', gml_id, NEW.typename;
529        ELSE -- Ersetzen des Objektes (Replace). In der Objekt-Tabelle sind jetzt bereits 2 Objekte vorhanden (alt und neu).
530
531                -- beginnt-Wert des aktuellen Objektes ermitteln
532                begsql := 'SELECT max(beginnt) FROM ' || NEW.typename || ' WHERE gml_id like ''' || substr(NEW.replacedBy, 1, 16) || '%'' AND endet IS NULL';
533                EXECUTE begsql INTO aktbeg;
534
535                -- Alte Objekte entfernen
536                EXECUTE 'DELETE FROM ' || NEW.typename || ' WHERE gml_id like ''' || gml_id || '%'' AND beginnt < ''' || aktbeg || '''';
537
538        END IF;
539
540        NEW.ignored := false;
541        RETURN NEW;
542END;
543$$ LANGUAGE plpgsql;
544
545
546-- Wenn die Datenbank MIT Historie angelegt wurde, kann nach dem Laden hiermit aufgerÀumt werden.
547CREATE OR REPLACE FUNCTION alkis_delete_all_endet() RETURNS void AS $$
548DECLARE
549        c RECORD;
550BEGIN
551        -- In allen Tabellen die Objekte löschen, die ein Ende-Datum haben
552        FOR c IN
553                SELECT table_name
554                 FROM information_schema.columns a
555                WHERE a.column_name='endet'
556            AND a.is_updatable='YES' -- keine Views, die endet-Spalte haben
557                ORDER BY table_name
558        LOOP
559                EXECUTE 'DELETE FROM ' || c.table_name || ' WHERE NOT endet IS NULL';
560                -- RAISE NOTICE 'Lösche ''endet'' in: %', c.table_name;
561        END LOOP;
562END;
563$$ LANGUAGE plpgsql;
564
565
566-- Wenn die Datenbank ohne Historie gefÃŒhrt wird, ist das Feld "identifier" verzichtbar.
567-- Diese wird nur von der Trigger-Version fÃŒr die Version MIT Historie verwendet.
568-- Es kann aus allen Tabellen entfernt werden.
569CREATE OR REPLACE FUNCTION alkis_drop_all_identifier() RETURNS void AS $$
570DECLARE
571        c RECORD;
572BEGIN
573        FOR c IN
574                SELECT table_name
575                FROM information_schema.columns a
576                WHERE a.column_name='identifier'
577                ORDER BY table_name
578        LOOP
579                EXECUTE 'ALTER TABLE ' || c.table_name || ' DROP COLUMN identifier';
580                RAISE NOTICE 'Entferne ''identifier'' aus: %', c.table_name;
581        END LOOP;
582END;
583$$ LANGUAGE plpgsql;
584
585
586-- Funktion zum Ermitteln der letzten import_id
587CREATE OR REPLACE FUNCTION get_import_id() RETURNS TRIGGER AS $$
588BEGIN
589        EXECUTE 'SELECT max(id) FROM import' INTO NEW.import_id;
590        RETURN NEW;
591END;
592$$ LANGUAGE plpgsql;
Note: See TracBrowser for help on using the repository browser.