====== Vollhistorie von Tabellen mit beginnt/endet Zeitstempel ====== ---- ===== Vollhistorie mit einer Tabelle ===== ==== CAST beginnt/endet AS text ==== SELECT * FROM alkis.ax_grenzpunkt WHERE CASE WHEN '$hist_timestamp' = ''::text THEN endet IS NULL ELSE beginnt::text <= '$hist_timestamp' AND ('$hist_timestamp' < endet::text OR endet IS NULL) END; Nachteil: Die date-Felder beginnt und endet werden für den Vergleich in das Text-Format umgewandelt. Das kostet Zeit und macht sich bei großen Datensatzmengen deutlich bemerkbar. ==== CAST $hist_timestamp AS timestamp ==== SELECT * FROM alkis.ax_grenzpunkt WHERE CASE WHEN '$hist_timestamp' = ''::text THEN endet IS NULL ELSE beginnt <= NULLIF('$hist_timestamp', '')::timestamp AND (NULLIF('$hist_timestamp', '')::timestamp < endet OR endet IS NULL) END; Nachteil: Die WHERE-Klausel wird unübersichtlich. Wenn zwei Tabellen abgefragt werden, ist das WHERE gerade noch so verständlich, ab drei verknüpften Tabellen wirds völlig unübersichtlich. ==== CAST beginnt und endet AS tsrange ==== SELECT * FROM alkis.ax_grenzpunkt WHERE tsrange(beginnt, endet) @> COALESCE(NULLIF('$hist_timestamp', '')::timestamp, localtimestamp); Vorteil: Die WHERE-Bedingung mit tsrange arbeitet deutlich schneller. Die Übersichtlichkeit bleibt auch bei mehreren verknüpften Tabellen erhalten. ===== Vollhistorie über mehrere Tabellen ===== ==== CAST beginnt/endet AS text ==== SELECT * FROM alkis.ax_grenzpunkt AS g JOIN alkis.ax_punktortta AS p ON g.gml_id = ANY (p.istteilvon) WHERE CASE WHEN '$hist_timestamp' = '' THEN g.endet IS NULL AND p.endet IS NULL ELSE g.beginnt::text <= '$hist_timestamp' AND ('$hist_timestamp' < g.endet::text OR g.endet IS NULL) AND p.beginnt::text <= '$hist_timestamp' AND ('$hist_timestamp' < p.endet::text OR p.endet IS NULL) END; Nachteil: Im "historischen Fall" (dem ELSE-Zweig) werden alle Stände der ersten Tabelle und alle Fälle der zweiten Tabelle ermittelt und dann miteinander zu Objekten verknüpft - egal, ob sie zeitgleich existierten oder zeitlich nichts miteinander zu tun haben. Das generiert etliche überflüssige "Treffer", die gar keine sind. ==== CAST beginnt und endet AS tsrange ==== SELECT * FROM alkis.ax_grenzpunkt AS g JOIN alkis.ax_punktortta AS p ON g.gml_id = ANY (p.istteilvon) AND tsrange(g.beginnt, g.endet) && tsrange(p.beginnt, p.endet) WHERE tsrange(g.beginnt, g.endet) * tsrange(p.beginnt, p.endet) @> COALESCE(NULLIF('$hist_timestamp', '')::timestamp, localtimestamp); Vorteil: Der Vergleich der Lebenszeitintervalle der Objekte aus der ersten und aus der zweiten Tabelle liefert nur die Objekte, die gleichzeitig existierten (''&&'' = a intersects b). In der WHERE-Bedingung wird nur das gemeinsame Lebenszeitintervall des verknüpften Objekts gegen $hist_timestamp geprüft (''*'' = Intersection von a und b). ===== Operatoren auf range-Datentypen ===== ==== Rückgabe-Datentyp ''boolean'' ==== * ''='' (equal), ''<>'' (not equal), ''<'' (less than), ''>'' (greater than), ''<='' (less than or equal) und ''>='' (greater than or equal) * ''&&'' (overlap) und ''-|-'' (is adjacent to) * ''<@'' (is contained by) und ''@>'' (contains) * ''<<'' (strictly left of) und ''>>'' (strictly right of) * ''&<'' (does not extend to the right of) und ''&>'' (does not extend to the left of) ==== Rückgabe-Datentyp ''range'' ==== * ''*'' (intersection) * ''+'' (union) * ''-'' (difference) ===== Performance-Verbesserung von range-Operationen ===== Anlegen eines funktionalen Index: CREATE INDEX idx_ax_grenzpunkt_range_gist ON alkis.ax_grenzpunkt USING gist (tsrange(beginnt, endet)); Die range-Operatoren ''='', ''&&'', ''-|-'', ''<@'', ''@>'', ''<<'', ''>>'' und ''&<'' nutzen ''gist''- und ''sp_gist''-Indizes.