Changeset 336 for trunk/import/alkis-functions.sql
- Timestamp:
- 09/19/14 13:52:07 (10 years ago)
- File:
-
- 1 edited
Legend:
- Unmodified
- Added
- Removed
-
trunk/import/alkis-functions.sql
r331 r336 22 22 -- "alkis_mviews()", delete_feature_kill_vers07(), alkis_beziehung_inserted() 23 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 24 32 25 33 -- Table/View/Sequence löschen, wenn vorhanden … … 281 289 $$ LANGUAGE plpgsql; 282 290 283 -- Die folgende Trigger-Version kopiert aus OSGeo4W / apps / alkis-import, Datei:alkis-functions.sql284 291 285 292 -- Im Trigger 'delete_feature_trigger' muss eine dieser beiden Funktionen … … 287 294 -- aktuelle oder auch historische Objekte in der Datenbank gefÃŒhrt werden sollen. 288 295 296 297 /* Beschreibung und Umfeld des "delete_feature_trigger": 298 ----------------------------------------------------- 299 300 Der Konverter "ogr2ogr", in den PostNAS als Eingabe-Modul fÃŒr das NAS-Format eingebettet ist, 301 ist 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 303 einem Ausgabe-GIS-Format. 304 Das ALKIS-Format "NAS" als einmaliger Datenauszug (enthÀlt nur Funktion "insert") könnte in diesem 305 Rahmen vielleicht auch ohne Trigger umgesetzt werden. 306 307 Viel komplexer umzusetzen sind jedoch solche NAS-Daten, die im Rahmen des NBA-Verfahrens von ALKIS abgegeben werden. 308 NBA = Nutzerbezogene Bestandsdaten-Aktualisierung. 309 In einem NBA-Verfahren wird eine primÀre Datenquelle (ALKIS) mit einem SekundÀrbestand (PostGIS) synchronisiert. 310 Es erfolgt zunÀchst eine Erstabgabe fÃŒr einen definierten Zeitpunkt. 311 SpÀter gibt es Aktualisierungen auf weitere Zeitpunkte. Die dazu ÃŒbermittelten Differenzdaten enthalten 312 nicht nur reine Daten (INSERT) sondern auch Anweisungen zur Ãnderung der zu frÃŒheren Zeitpunkten ÃŒbermittelten Daten. 313 Diese Ãnderungs-Anweisungen werden innerhalb des Konverters ogr2ogr nicht komplett verarbeitet. 314 Er verarbeitet zunÀchst nur die enthaltenen Datenfelder zum Objekt, die er in neue Zeilen in die Objekt-Tabellen einstellt. 315 316 AnschlieÃend werden Informationen zum ObjektschlÃŒssel (gml_id) und zur Lebensdauer des Objektes (beginnt) zusammen 317 mit der Operation (Kontext = "delete", "update" oder "replace") in die Tabelle "delete" eingetragen. 318 Dieser Eintrag in "delete" löst den Trigger aus, der sich dann darum kÃŒmmert, Löschungen oder 319 Ãnderungen an VorgÀngerversionen vorzunehmen. 320 321 Im NBA-Verfahren sind verschiedene "Abgabearten" möglich: 322 Die Abgabeart 1000 hat zum Ziel, im SekundÀrbestand jeweils den letzten aktuellen Stand bereit zu stellen. 323 Die Abgabeart 3100 hat zum Ziel, im SekundÀrbestand eine komplette Historie bereit zu stellen, die auch alle 324 ZwischenstÀnde enthÀlt. Ein nicht mehr gÃŒltiges Objekt wird dann mit einem Eintrag in "endet" deaktiviert, 325 verbleibt aber in der Datenbank. Bei der Abgabeart 3100 sind bereits in der Erstabgabe Aktualisierungs- 326 Funktionen (delete, update, replace) enthalten weil mehrere historische Versionen von Objekten geliefert werden. 327 328 Eine NBA-Lieferung fÃŒr ein Gebiet ist in mehrere Dateien aufgeteilt, die nacheinander abgearbeitet werden. 329 Erst mit der Verarbeitung der letzten Datei einer Lieferung ist die Datenbank wieder konsistent und zeigt den 330 Zustand zum neuen Abgabezeitpunkt. 331 332 Jede dieser NAS-Dateien wird von PostNAS in mehreren DurchlÀufen vearbeitet. 333 1. 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. 335 2. 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 338 FÃŒr die Arbeitsweise des Triggers bedeutet das: 339 340 An dem Zeitpunkt, an dem der Trigger ausgelöst wird, stehen bereits alle Daten zu den Objekten in den Objekt-Tabellen. 341 Darin ist aber möglicherweise das Feld "endet" noch nicht gefÃŒllt. 342 343 WÀhrend der Konvertierung der Erstabgabe einer NBA-Abgabe der Abgabeart 3100 können aber Objekte doch schon beendet sein. 344 Bei einer Erstabgabe der Abgabeart 3100 können mehrere Generation des selben Objektes vorhanden sein, 345 die alle in der gleichen NAS-Datei geliefert wurden. 346 Das Feld "endet" ist dann nicht geeignet zu entscheiden, welches die letzte (aktuelle) Version ist. 347 348 Es kann vorkommen, dass Zwischenversionen in der Objekt-Tabelle bereits beendet sind weil sie direkt mit ihrem 349 Endet-Datum geliefert wurden. Dieses wurde bereits beim ersten Durchlauf von ogr2ogr wie ein normales Datenfeld eingetragen. 350 In Beispieldaten wurde analysiert, dass ein bereits beendetes Objekt in einem "insert" kein "endet" mitbringt. 351 Dies muss vom Trigger beendet werden, wenn dieser einen replace fÃŒr den Nachfolger bekommt. 352 353 Im gleichen Bestand wurden jedoch Nachfolger gefunden die mit einem "replace"-Satz gekommen sind 354 und bereits beendet waren, weil sie ihrerseits wieder Nachfolger hatten. 355 356 Das jeweils folgende "replace" kann also ein VorgÀnger-Objekt mit oder ohne "endet"-Eintrag vorfinden. 357 Es können auch sowohl VorgÀnger- als auch bereits Nachfolger-Versionen eines Objektes vorhanden sein, wenn der Trigger 358 ausgelöst wird. 359 360 Aufgabe des Triggers ist es, zu einem verÀnderten Objekt jeweils den unmittelbaren VorgÀnger zu ermitteln 361 und - falls noch nicht geschehen - den passenden endet-Eintrag nachzutragen. 362 Wenn in den Daten kein "endet" mitgeliefert wird, dann wird der Beginn der Folge-Version des Objektes verwendet 363 um den VorgÀnger zu beenden. 364 365 Wenn ein Objekt bereits mit endet-Datum geliefert wurde, dann wird dies zwar in die Obkjekt-Tabelle eingetragen, 366 der endet-Eintrag in dem replace-Satz in der delete-Tabelle, der den Trigger auslöst, ist trotzdem leer. 367 Es ist ÃŒberlegen, ob dies im PostNAS-Programm geÀndert werden sollte. 368 369 Aufgrund der KomplexitÀt dieser Mechanismen ist davon auszugehen, dass es Hersteller-spezifische Unterschiede 370 gibt und auch Unterschiede zuwischen verschiedenen Versions-StÀnden des selben Herstellers. 371 Die 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 289 378 -- Löschsatz verarbeiten (MIT Historie) 290 -- context='update' => "endet" auf ÃŒbergebene Zeit setzen und anlass festhalten291 379 -- context='delete' => "endet" auf aktuelle Zeit setzen 292 380 -- context='replace' => "endet" des ersetzten auf "beginnt" des neuen Objekts setzen 381 -- context='update' => "endet" auf ÃŒbergebene Zeit setzen und "anlass" festhalten 293 382 CREATE OR REPLACE FUNCTION delete_feature_hist() RETURNS TRIGGER AS $$ 294 383 DECLARE 384 n INTEGER; 385 vbeginnt TEXT; 386 replgml TEXT; 387 featgml TEXT; 295 388 s TEXT; 296 alt_id TEXT; 297 neu_id TEXT; 298 beginnt TEXT; 299 endete TEXT; 300 n INTEGER; 301 BEGIN 302 NEW.context := lower(NEW.context); 303 IF NEW.context IS NULL THEN 304 NEW.context := 'delete'; 305 END IF; 306 307 -- TIMESTAMP weder in gml_id noch identifier verlÀÃlich. 308 -- also ggf. aus Datenbank holen 389 BEGIN 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 309 396 310 397 IF length(NEW.featureid)=32 THEN 311 alt_id := substr(NEW.featureid, 1, 16); 312 313 IF NEW.featureid<>NEW.replacedBy THEN 314 -- Beginnt-Datum aus Timestamp 315 beginnt := substr(NEW.featureid, 17, 4) || '-' 316 || substr(NEW.featureid, 21, 2) || '-' 317 || substr(NEW.featureid, 23, 2) || 'T' 318 || substr(NEW.featureid, 26, 2) || ':' 319 || substr(NEW.featureid, 28, 2) || ':' 320 || substr(NEW.featureid, 30, 2) || 'Z' 321 ; 322 END IF; 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' ; 323 405 ELSIF length(NEW.featureid)=16 THEN 324 alt_id := NEW.featureid; 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; 325 415 ELSE 326 RAISE EXCEPTION '%: LÀnge 16 oder 32 statt % erwartet.', NEW.featureid, length(NEW.featureid); 327 END IF; 328 329 IF beginnt IS NULL THEN 330 -- Beginnt-Datum des Àltesten Eintrag, der nicht untergegangen ist 331 -- => der Satz dessen 'endet' gesetzt werden muà 332 EXECUTE 'SELECT min(beginnt) FROM ' || NEW.typename 333 || ' WHERE gml_id=''' || alt_id || '''' 334 || ' AND endet IS NULL' 335 INTO beginnt; 336 END IF; 337 338 IF beginnt IS NULL THEN 339 IF NEW.context = 'delete' OR NEW.safetoignore = 'true' THEN 340 RAISE NOTICE 'Kein Beginndatum fÃŒr Objekt % gefunden - ignoriert.', alt_id; 341 NEW.ignored := true; 342 RETURN NEW; 343 ELSE 344 RAISE EXCEPTION 'Kein Beginndatum fÃŒr Objekt % gefunden.', alt_id; 345 END IF; 416 RAISE EXCEPTION '%: Identifikator gescheitert.', NEW.featureid; 346 417 END IF; 347 418 348 419 IF NEW.context='delete' THEN 349 endete := to_char(CURRENT_TIMESTAMP AT TIME ZONE 'UTC','YYYY-MM-DD"T"HH24:MI:SS"Z"'); 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; 350 426 351 427 ELSIF NEW.context='replace' THEN 352 428 NEW.safetoignore := lower(NEW.safetoignore); 353 429 replgml := substr(NEW.replacedby, 1, 16); -- ReplcedBy gml_id ohne Timestamp 354 430 IF NEW.safetoignore IS NULL THEN 355 431 RAISE EXCEPTION '%: safeToIgnore nicht gesetzt.', NEW.featureid; … … 358 434 END IF; 359 435 360 IF length(NEW.replacedBy)=32 THEN 361 -- Beginnt-Datum aus Timestamp 362 IF NEW.featureid<>NEW.replacedBy THEN 363 endete := substr(NEW.replacedBy, 17, 4) || '-' 364 || substr(NEW.replacedBy, 21, 2) || '-' 365 || substr(NEW.replacedBy, 23, 2) || 'T' 366 || substr(NEW.replacedBy, 26, 2) || ':' 367 || substr(NEW.replacedBy, 28, 2) || ':' 368 || substr(NEW.replacedBy, 30, 2) || 'Z' 369 ; 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; 370 474 END IF; 371 ELSIF length(NEW.replacedBy)<>16 THEN 372 RAISE EXCEPTION '%: LÀnge 16 oder 32 statt % erwartet.', NEW.replacedBy, length(NEW.replacedBy); 373 END IF; 374 375 neu_id := NEW.replacedBy; 376 IF endete IS NULL THEN 377 -- Beginnt-Datum des neuesten Eintrag, der nicht untergegangen ist 378 -- => Enddatum fÃŒr vorherigen Satz 379 EXECUTE 'SELECT max(beginnt) FROM ' || NEW.typename 380 || ' WHERE gml_id=''' || NEW.replacedBy || '''' 381 || ' AND beginnt>''' || beginnt || '''' 382 || ' AND endet IS NULL' 383 INTO endete; 384 IF endete IS NULL AND length(NEW.replacedBy)=32 THEN 385 EXECUTE 'SELECT max(beginnt) FROM ' || NEW.typename 386 || ' WHERE gml_id=''' || substr(NEW.replacedBy, 1, 16) || '''' 387 || ' AND beginnt>''' || beginnt || '''' 388 || ' AND endet IS NULL' 389 INTO endete; 390 neu_id := substr(NEW.replacedBy, 1, 16); 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; 391 480 END IF; 392 END IF; 393 394 IF alt_id<>substr(neu_id, 1, 16) THEN 395 RAISE NOTICE 'Objekt % wird durch Objekt % ersetzt.', alt_id, neu_id; 396 END IF; 397 398 IF endete IS NULL THEN 399 RAISE NOTICE 'Kein Beginndatum fÃŒr Objekt % gefunden.', NEW.replacedBy; 400 END IF; 401 402 IF endete IS NULL OR beginnt=endete THEN 403 RAISE EXCEPTION 'Objekt % wird durch Objekt % ersetzt (leere Lebensdauer?).', alt_id, neu_id; 404 END IF; 405 ELSIF NEW.context='update' THEN 406 endete := NEW.endet; 481 NEW.ignored=true; 482 RETURN NEW; 483 END IF; 407 484 ELSE 408 485 RAISE EXCEPTION '%: UngÃŒltiger Kontext % (''delete'', ''replace'' oder ''update'' erwartet).', NEW.featureid, NEW.context; 409 486 END IF; 410 487 411 s := 'UPDATE ' || NEW.typename 412 || ' SET endet=''' || endete || '''' 413 || ',anlass=''' || coalesce(NEW.anlass,'000000') || '''' 414 || ' WHERE gml_id=''' || NEW.featureid || '''' 415 || ' AND beginnt=''' || beginnt || '''' 416 || ' AND endet IS NULL'; 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 || '''' ; 417 492 EXECUTE s; 418 493 GET DIAGNOSTICS n = ROW_COUNT; 419 IF n=0 AND alt_id<>NEW.featureid THEN 420 s := 'UPDATE ' || NEW.typename 421 || ' SET endet=''' || endete || '''' 422 || ',anlass=''' || coalesce(NEW.anlass,'000000') || '''' 423 || ' WHERE gml_id=''' || alt_id || '''' 424 || ' AND beginnt=''' || beginnt || '''' 425 || ' AND endet IS NULL'; 426 EXECUTE s; 427 GET DIAGNOSTICS n = ROW_COUNT; 428 END IF; 429 494 -- RAISE NOTICE 'SQL[%]:%', n, s; 430 495 IF n<>1 THEN 431 RAISE NOTICE 'SQL[%<>1]: %', n, s; 432 IF NEW.context = 'delete' OR NEW.safetoignore = 'true' THEN 433 RAISE NOTICE '%: Untergangsdatum von % Objekten statt einem auf % gesetzt - ignoriert', NEW.featureid, n, endete; 434 NEW.ignored := true; 435 RETURN NEW; 436 ELSIF n=0 THEN 437 EXECUTE 'SELECT endet FROM ' || NEW.typename || 438 ' WHERE gml_id=''' || alt_id || '''' || 439 ' AND beginnt=''' || beginnt || '''' 440 INTO endete; 441 442 IF NOT endete IS NULL THEN 443 RAISE NOTICE '%: Objekt bereits % untergegangen - ignoriert', NEW.featureid, endete; 444 ELSE 445 RAISE NOTICE '%: Objekt nicht gefunden - ignoriert', NEW.featureid; 446 END IF; 447 448 NEW.ignored := true; 449 RETURN NEW; 450 ELSE 451 RAISE EXCEPTION '%: Untergangsdatum von % Objekten statt einem auf % gesetzt - Abbruch', NEW.featureid, n, endete; 452 END IF; 496 RAISE EXCEPTION '%: % schlug fehl [%]', NEW.featureid, NEW.context, n; 453 497 END IF; 454 498 … … 460 504 461 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. 462 512 CREATE OR REPLACE FUNCTION delete_feature_kill() RETURNS TRIGGER AS $$ 463 513 DECLARE … … 492 542 END; 493 543 $$ LANGUAGE plpgsql; 494 495 496 -- BeziehungssÀtze aufrÀumen497 /*498 CREATE OR REPLACE FUNCTION alkis_beziehung_inserted() RETURNS TRIGGER AS $$499 BEGIN500 DELETE FROM alkis_beziehungen WHERE ogc_fid<NEW.ogc_fid AND beziehung_von=NEW.beziehung_von AND beziehungsart=NEW.beziehungsart AND beziehung_zu=NEW.beziehung_zu;501 RETURN NEW;502 END;503 $$ LANGUAGE plpgsql;504 */505 544 506 545
Note: See TracChangeset
for help on using the changeset viewer.