logo       

Re: Verwaltungshierarchie mit Inkonsistenzen?: msg#00067

Subject: Re: Verwaltungshierarchie mit Inkonsistenzen?
Hallo,

hier die versprochenden Ergebnisse der Analyse. Zuvor noch Anmerkungen als 
Antwort auf einige Fragen:

Ich beziehe mich auf folgende Version:
opengeodb-0.2.4d-UTF8-postgres.zip (Download am 18.07.2006)
opengeodb-0.2.4d-UTF8-postgres.sql (aus der Zip-Datei, Datum 14.03.2006)

Alle Hinweise sind als Anregung zu verstehen, wie man die Qualität der Daten 
verbessern könnte.

Bezüglich der Regeln für die Datenkonsistenz würde ich zunächst drei Fälle 
unterscheiden:
Typ 1: Die Bedingung muss eingehalten werden. Ein Verstoß ist ein Fehler.
Typ 2: Das Einhalten der Bedingung ist angestrebt, aber z. B. mangels Daten 
nicht erreicht. Ausnahmen müssen definiert sein. Ein Verstoß außerhalb der 
definierten Ausnahmen ist ein Fehler.
Typ 3: Die Bedingung ist eingehalten, aber nicht als Regel formuliert. Das ist 
eigentlich keine Regel. Die Formulierung einer entsprechenden Regel vom Typ 1 
oder 2 oder die Klarstellung, dass es sich um einen Zufall handelt, könnte 
sinnvoll sein.

Alle im folgenden genannten Regeln sind Vorschläge:

Typ-1-Regel: In geodb_hierarchies.id_lvln (n = 1, ..., 9) dürfen nur Verweise 
auf geodb_locations.loc_id stehen.
Diese Bedingung ist eingehalten:

SELECT id_lvl1 FROM geodb_hierarchies
UNION
SELECT id_lvl2 FROM geodb_hierarchies
UNION
SELECT id_lvl3 FROM geodb_hierarchies
UNION
SELECT id_lvl4 FROM geodb_hierarchies
UNION
SELECT id_lvl5 FROM geodb_hierarchies
UNION
SELECT id_lvl6 FROM geodb_hierarchies
UNION
SELECT id_lvl7 FROM geodb_hierarchies
UNION
SELECT id_lvl8 FROM geodb_hierarchies
UNION
SELECT id_lvl9 FROM geodb_hierarchies
EXCEPT
SELECT loc_id FROM geodb_locations;
-- 1 Zeile mit NULL, OK

Typ-1-Regel: In geodb_textdata darf es keine Duplikate geben.

SELECT loc_id, text_val, text_type, text_locale, is_native_lang, 
is_default_name,
        valid_since, date_type_since, valid_until, date_type_until, count(*) AS 
anzahl
FROM geodb_textdata 
GROUP BY loc_id, text_val, text_type, text_locale, is_native_lang, 
is_default_name,
        valid_since, date_type_since, valid_until, date_type_until
HAVING count(*) > 1;
-- liefert PLZ 06308 bei loc_id 35877

Das wäre ein Verstoß gegen eine Typ-1-Regel.

Korrekturvorschlag: Datensätze löschen und einen neuen einfügen.
Es ist nicht möglich nur einen Datensatz zu löschen, da es keinen Schlüssel 
gibt.

DELETE FROM geodb_textdata
WHERE loc_id = 35877 AND text_type = 500300000 AND text_val = '06308';
INSERT INTO geodb_textdata (loc_id, text_val, text_type, text_locale, 
is_native_lang, is_default_name,
        valid_since, date_type_since, valid_until, date_type_until) VALUES
        (35877, '06308', 500300000, NULL, NULL, NULL, '2005-01-01', 300100000, 
'3000-01-01', 300500000);

Typ-1-Regel: In geodb_hierarchies darf es keine Duplikate geben.

SELECT loc_id, level, id_lvl1, id_lvl2, id_lvl3, id_lvl4, id_lvl5, id_lvl6, 
id_lvl7, id_lvl8, id_lvl9,
        valid_since, date_type_since, valid_until, date_type_until, count(*) AS 
anzahl
FROM geodb_hierarchies
GROUP BY loc_id, level, id_lvl1, id_lvl2, id_lvl3, id_lvl4, id_lvl5, id_lvl6, 
id_lvl7, id_lvl8, id_lvl9,
        valid_since, date_type_since, valid_until, date_type_until
HAVING count(*) > 1;
-- liefert einen Eintrag auf Level 7

Korrekturvorschlag: Datensätze löschen und einen neuen einfügen.
Es ist nicht möglich nur einen Datensatz zu löschen, da es keinen Schlüssel 
gibt.

DELETE FROM geodb_hierarchies
WHERE (loc_id, level, id_lvl1, id_lvl2, id_lvl3, id_lvl4, id_lvl5, id_lvl6, 
id_lvl7,
        valid_since, date_type_since, valid_until, date_type_until) =
        (17540, 7, 104, 105, 122, 0, 312, 35877, 17540, '2005-01-01' :: date, 
300100000, '3000-01-01' :: date, 300500000)
AND id_lvl8 IS NULL AND id_lvl9 IS NULL;
INSERT INTO geodb_hierarchies (loc_id, level, id_lvl1, id_lvl2, id_lvl3, 
id_lvl4, id_lvl5, id_lvl6, id_lvl7, id_lvl8, id_lvl9,
        valid_since, date_type_since, valid_until, date_type_until)
VALUES (17540, 7, 104, 105, 122, 0, 312, 35877, 17540, NULL, NULL, 
'2005-01-01', 300100000, '3000-01-01', 300500000);

Typ-1-Regel: Für jedes Objekt, das kein PLZ-Bereich ist, darf es zu jedem 
Zeitpunkt nur einen gültigen Eintrag in geodb_hierarchies geben.

SELECT * FROM geodb_locations AS lc
INNER JOIN geodb_hierarchies AS h1 ON h1.loc_id = lc.loc_id
AND lc.loc_type <> 100800000 -- keine PLZ
AND lc.loc_id > 0 -- keine Pseudoobjekte
INNER JOIN geodb_hierarchies AS h2 ON h2.loc_id = h1.loc_id
WHERE -- Vergleich eines Datensatzes mit sich selbst ausschließen. Hier müsste 
ein Schlüssel her.
(
        (h1.level <> h2.level)
        OR (h1.id_lvl1 <> h2.id_lvl1)
        OR (h1.id_lvl2 <> h2.id_lvl2) OR (h1.id_lvl2 IS NULL AND h1.id_lvl2 IS 
NOT NULL) OR (h1.id_lvl2 IS NOT NULL AND h1.id_lvl2 IS NULL)
        OR (h1.id_lvl3 <> h2.id_lvl3) OR (h1.id_lvl3 IS NULL AND h1.id_lvl3 IS 
NOT NULL) OR (h1.id_lvl3 IS NOT NULL AND h1.id_lvl3 IS NULL)
        OR (h1.id_lvl4 <> h2.id_lvl4) OR (h1.id_lvl4 IS NULL AND h1.id_lvl4 IS 
NOT NULL) OR (h1.id_lvl4 IS NOT NULL AND h1.id_lvl4 IS NULL)
        OR (h1.id_lvl5 <> h2.id_lvl5) OR (h1.id_lvl5 IS NULL AND h1.id_lvl5 IS 
NOT NULL) OR (h1.id_lvl5 IS NOT NULL AND h1.id_lvl5 IS NULL)
        OR (h1.id_lvl6 <> h2.id_lvl6) OR (h1.id_lvl6 IS NULL AND h1.id_lvl6 IS 
NOT NULL) OR (h1.id_lvl6 IS NOT NULL AND h1.id_lvl6 IS NULL)
        OR (h1.id_lvl7 <> h2.id_lvl7) OR (h1.id_lvl7 IS NULL AND h1.id_lvl7 IS 
NOT NULL) OR (h1.id_lvl7 IS NOT NULL AND h1.id_lvl7 IS NULL)
        OR (h1.id_lvl8 <> h2.id_lvl8) OR (h1.id_lvl8 IS NULL AND h1.id_lvl8 IS 
NOT NULL) OR (h1.id_lvl8 IS NOT NULL AND h1.id_lvl8 IS NULL)
        OR (h1.id_lvl9 <> h2.id_lvl9) OR (h1.id_lvl9 IS NULL AND h1.id_lvl9 IS 
NOT NULL) OR (h1.id_lvl9 IS NOT NULL AND h1.id_lvl9 IS NULL)
        OR (h1.valid_since <> h2.valid_since) OR (h1.valid_since IS NULL AND 
h1.valid_since IS NOT NULL)
        OR (h1.valid_since IS NOT NULL AND h1.valid_since IS NULL)
        OR (h1.date_type_since <> h2.date_type_since) OR (h1.date_type_since IS 
NULL AND h1.date_type_since IS NOT NULL)
        OR (h1.date_type_since IS NOT NULL AND h1.date_type_since IS NULL)
        OR (h1.valid_until <> h2.valid_until)
        OR (h1.date_type_until <> h2.date_type_until)
)
AND -- überlappende Zeiträume (Überlegung: Standardwert statt NULL bei 
valid_since und NOT NULL setzen?)
(
        (h1.valid_since IS NULL AND (h2.valid_since IS NULL OR h2.valid_since 
<= h1.valid_until))
        OR (h1.valid_since <= h2.valid_until AND h2.valid_since <= 
h1.valid_until)
)
ORDER BY h1.loc_id, h1.valid_since;
-- 7 Ortsobjekte, die gleichzeitig auf Level 6 und Level 7 stehen

-- Anzeige der Hierarchiedaten dieser 7 Objekte
SELECT * FROM geodb_locations AS lc
INNER JOIN geodb_hierarchies AS h ON h.loc_id = lc.loc_id
AND lc.loc_type <> 100800000
WHERE h.loc_id IN (17178, 20325, 23495, 23956, 24413, 24716, 26649)
ORDER BY h.loc_id;

Korrekturvorschlag: die Gültigkeitsdaten der betroffenen Objekte korrigieren.

Typ-1-Regel: Eine Gemeinde darf nicht mehr als einen AGS haben.
Anmerkung: Es ist zu klären, ob eine Gemeinde zu verschiedenen Zeitpunkten 
verschiedene AGS haben darf. Die folgende Abfrage berücksichtigt nur die 
aktuellen Daten.

-- Gemeinden mit mehr als einem gültigen Gemeindeschlüssel
SELECT * FROM geodb_textdata AS td1
INNER JOIN -- filtern von Duplikaten im Subselect
(
        SELECT td2.loc_id, count(*) AS anzahl FROM geodb_textdata AS td2
        WHERE td2.text_type = 500600000 -- AGS
                AND td2.valid_until >= current_date
        GROUP BY td2.loc_id
        HAVING count(*) > 1 -- mehr als ein Datensatz (AGS)
) AS t1 ON t1.loc_id = td1.loc_id
INNER JOIN geodb_textdata AS td3 ON td3.loc_id = td1.loc_id -- AGS anzeigen
        AND td3.text_type = 500600000 -- AGS
        AND td3.valid_until >= current_date
WHERE td1.text_type = 500100000 -- Name
ORDER BY td1.loc_id;

-- Anzeige aller Textdaten der betroffenen Objekte
SELECT * FROM geodb_hierarchies AS h
INNER JOIN geodb_textdata AS td ON td.loc_id = h.loc_id
WHERE h.loc_id IN (23577, 23578)
ORDER BY h.loc_id, td.text_type;

-- Korrektur (der AGS bezieht sich einfach nur auf das falsche Objekt, oder?)
UPDATE geodb_textdata
SET loc_id = 23578
WHERE loc_id = 23577 AND text_val = '13051086';

Aufpassen: Es könnten weitere Korrekturen erforderlich sein. Das habe ich nicht 
geprüft.

Typ-1-Regel: Die AGS der Gemeinden eines Kreises in Deutschland müssen alle mit 
denselben 5 Ziffern beginnen. (Ziffern 1-2 = Bundesland, Ziffer 3 = 
Regierungsbezirk oder 0, Ziffern 4-5 = Kreis).

Diese Bedingung ist verletzt, wie zufällig das folgende Statement gezeigt hat:

-- Stichprobe für Vollständigkeit der Gemeinden (Landkreis Uckermark im Land 
Brandenburg)
SELECT * FROM geodb_locations AS lc
INNER JOIN geodb_hierarchies AS h ON h.loc_id = lc.loc_id
        AND h.id_lvl2 = 105 -- alles in Deutschland
        AND h.level = 6 AND lc.loc_type = 100700000 -- Gemeinden
        AND h.valid_until >= current_date
INNER JOIN geodb_textdata AS td1 ON td1.loc_id = lc.loc_id
        AND td1.text_type = 500100000 -- Name
        AND td1.is_default_name
        AND td1.valid_until >= current_date
LEFT JOIN geodb_textdata AS td2 ON td2.loc_id = lc.loc_id
        AND td2.text_type = 500600000 -- AGS
        AND td2.valid_until >= current_date
WHERE h.id_lvl5 = 309
AND td2.loc_id IS NOT NULL
ORDER BY td1.text_val;
-- Herzsprung ist falsch
-- Die Gemeinde gibt's nicht im Landkreis und AGS 12068192 passt nicht (AGS 
muss mit 12073 beginnen)
-- Es gibt aber Herzsprung in der Gemeinde Angermünde mit AGS 12073008.

-- loc_id und Textdaten für alle Orte mit Namen Herzsprung anzeigen
SELECT * FROM geodb_textdata WHERE
loc_id IN
(
        SELECT loc_id FROM geodb_textdata
        WHERE text_val LIKE 'Herzsprung%'
)
ORDER BY loc_id, text_type;
-- 18274 und 18275

-- Hierarchie und Textdaten anzeigen für die beiden Herzsprung
SELECT * FROM geodb_hierarchies AS h
INNER JOIN geodb_textdata AS td ON td.loc_id = h.loc_id
WHERE h.loc_id IN (18274, 18275)
ORDER BY h.loc_id, td.text_type;

Korrekturvorschlag: Herzsprung muss auf Level 7 verschoben und der AGS gelöscht 
werden, nur mit welchem Datum?

Die folgende Abfrage testet direkt die Bedingung:

-- Kreise mit Gemeinden, die einen falschen AGS haben
SELECT kreis_id, kreisname, count(*) AS anzahl FROM
(
        SELECT DISTINCT kreis_id, kreisname, kreis_ags FROM
        (
                SELECT DISTINCT td.loc_id AS kreis_id, td.text_val AS 
kreisname, td2.loc_id AS gemeinde_id, td2.text_val :: int / 1000 AS kreis_ags,
                 td3.text_val AS gemeindename
                FROM geodb_textdata AS td
                INNER JOIN geodb_hierarchies AS h ON h.id_lvl5 = td.loc_id AND 
h.valid_until >= current_date AND td.valid_until >= current_date
                INNER JOIN geodb_textdata AS td2 ON td2.loc_id = h.id_lvl6 AND 
td2.text_type = 500600000 AND td2.valid_until >= current_date
                INNER JOIN geodb_textdata AS td3 ON td3.loc_id = h.id_lvl6 AND 
td3.text_type = 500100000 AND td3.valid_until >= current_date
                --WHERE td.loc_id IN (309, 545, 329, 538)
                WHERE h.level = 6
                AND td.text_type = 500100000
        ) AS t1
) AS t2
GROUP BY kreis_id, kreisname
HAVING count(*) > 1;
-- bei Landkreis Uckermark hat Herzsprung einen falschen AGS, oder Herzsprung 
gehört nicht zum Landkreis Uckermark
-- bei Landkreis Müritz ist ein zweites Satow aus einem anderen Kreis 
hineingeraten
-- nach Korrektur 0 Zeilen

-- die betroffenen Landkreise (in der WHERE-Klausel die loc_id aufzählen)
SELECT DISTINCT td.loc_id AS kreis_id, td.text_val AS kreisname, td2.loc_id AS 
gemeinde_id, td2.text_val :: int / 1000 AS kreis_ags,
 td3.text_val AS gemeindename
FROM geodb_textdata AS td
INNER JOIN geodb_hierarchies AS h ON h.id_lvl5 = td.loc_id AND h.valid_until >= 
current_date AND td.valid_until >= current_date
INNER JOIN geodb_textdata AS td2 ON td2.loc_id = h.id_lvl6 AND td2.text_type = 
500600000 AND td2.valid_until >= current_date
INNER JOIN geodb_textdata AS td3 ON td3.loc_id = h.id_lvl6 AND td3.text_type = 
500100000 AND td3.valid_until >= current_date
WHERE td.loc_id IN (309, 545, 329, 538)
AND h.level = 6
AND td.text_type = 500100000
-- nach Korrektur nicht mehr relevant

Typ-3-Regel: Jeder PLZ-Bereich (loc_type 100800000) hat genau 2 Einträge in 
geodb_textdata, einen mit text_type 500100000 (PLZ als Name) und einen mit 
text_type 500100004. Ist das Zufall oder eine Regel vom Typ 1 oder 2?

-- testen, ob es PLZ-Bereiche gibt, bei denen die PLZ oder der Name fehlt
SELECT * FROM geodb_locations AS lc
LEFT JOIN geodb_textdata AS td1 ON td1.loc_id = lc.loc_id AND td1.text_type = 
500100000
LEFT JOIN geodb_textdata AS td2 ON td2.loc_id = lc.loc_id AND td2.text_type = 
500100004
WHERE lc.loc_type = 100800000
AND (td1.loc_id IS NULL OR td2.loc_id IS NULL)
ORDER BY td1.text_val, td1.loc_id;
-- 0 Datensätze

Zur Übersicht alle PLZ-Bereiche abfragen.
Die Einhaltung der letzten Regel wird vorausgesetzt, was die Verwendung von 
INNER JOIN statt LEFT JOIN gestattet:
SELECT * FROM geodb_locations AS lc
INNER JOIN geodb_textdata AS td1 ON td1.loc_id = lc.loc_id AND td1.text_type = 
500100000
INNER JOIN geodb_textdata AS td2 ON td2.loc_id = lc.loc_id AND td2.text_type = 
500100004
WHERE lc.loc_type = 100800000
ORDER BY td1.text_val, td1.loc_id;
-- 8270 PLZ
-- alles auf Level 6, 7 oder 8 (Gemeinde, Ort, Ortsteil)
-- alles unbegrenzt gültig
-- nur für DE

Typ-1-Regel: Jeder PLZ-Bereich hat genau eine PLZ.

-- Test, ob ein PLZ-Bereich mehrere PLZ hat
SELECT loc_id, count(*) AS anzahl FROM
(
        SELECT DISTINCT td1.loc_id, td1.text_val FROM geodb_locations AS lc
        INNER JOIN geodb_textdata AS td1 ON td1.loc_id = lc.loc_id AND 
td1.text_type = 500100000 -- die Nummer des PLZ-Bereichs
        AND lc.loc_type = 100800000
) AS t1
GROUP BY t1.loc_id;
-- OK

Typ-1-Regel: Jede PLZ hat genau einen PLZ-Bereich.

SELECT text_val, count(*) AS anzahl FROM
(
        SELECT DISTINCT td1.loc_id, td1.text_val FROM geodb_locations AS lc
        INNER JOIN geodb_textdata AS td1 ON td1.loc_id = lc.loc_id AND 
td1.text_type = 500100000 -- die Nummer des PLZ-Bereichs
        AND lc.loc_type = 100800000
) AS t1
GROUP BY t1.text_val;
-- OK

Typ-2-Regel: Jeder PLZ-Bereich wird in geodb_hierarchies verwendet. Was sind 
die Ausnahmen?

-- unbenutzte PLZ-Bereiche
SELECT lc.loc_id, td1.text_val FROM geodb_locations AS lc
INNER JOIN geodb_textdata AS td1 ON td1.loc_id = lc.loc_id AND td1.text_type = 
500100000 -- die Nummer des PLZ-Bereichs
AND lc.loc_type = 100800000
LEFT JOIN geodb_hierarchies AS h ON h.loc_id = lc.loc_id
WHERE h.loc_id IS NULL;
-- 89 Datensätze

Die Nummern der unbenutzten PLZ-Bereiche werden auch nicht in geodb_textdata 
benutzt, wie das folgende Statement zeigt:

-- unbenutzte PLZ-Bereiche mit benutzten Nummern
SELECT lc.loc_id, td1.text_val FROM geodb_locations AS lc
INNER JOIN geodb_textdata AS td1 ON td1.loc_id = lc.loc_id AND td1.text_type = 
500100000 -- die Nummer des PLZ-Bereichs
AND lc.loc_type = 100800000 -- PLZ-Bereich
LEFT JOIN geodb_hierarchies AS h ON h.loc_id = lc.loc_id
INNER JOIN geodb_textdata AS td2 ON td2.text_val = td1.text_val AND 
td2.text_type = 500300000 -- PLZ des Ortes
WHERE h.loc_id IS NULL; -- kein Eintrag in geodb_hierarchies fuer den 
PLZ-Bereich
-- 0 Datensätze

Typ-2-Regel: Eine PLZ muss einen PLZ-Bereich haben. Ausnahmen: außerhalb 
Deutschlands dürfen die PLZ-Bereiche fehlen (oder sogar: nicht existieren - was 
nützen unvollständige PLZ-Bereichsdaten).

-- PLZ ohne PLZ-Bereich mit Staat
SELECT DISTINCT h.id_lvl2 AS staat, td2.text_val AS plz FROM geodb_textdata AS 
td2
INNER JOIN geodb_hierarchies AS h ON h.loc_id = td2.loc_id
AND td2.text_type = 500300000
AND NOT EXISTS
(
        SELECT * FROM geodb_locations AS lc
        INNER JOIN geodb_textdata AS td1 ON td1.loc_id = lc.loc_id AND 
td1.text_type = 500100000 -- die Nummer des PLZ-Bereichs
        AND lc.loc_type = 100800000
        WHERE td1.text_val = td2.text_val
)
ORDER BY staat, plz;
-- 4825 Datensätze (nach den Korrekturen 4826)

-- PLZ ohne PLZ-Bereich mit Staat und Ort
SELECT * FROM geodb_textdata AS td2
INNER JOIN geodb_hierarchies AS h ON h.loc_id = td2.loc_id
AND td2.text_type = 500300000
-- AND td2.valid_until >= current_date
INNER JOIN geodb_textdata AS td3 ON td3.loc_id = td2.loc_id
AND td3.text_type = 500100000
WHERE NOT EXISTS
(
        SELECT * FROM geodb_locations AS lc
        INNER JOIN geodb_textdata AS td1 ON td1.loc_id = lc.loc_id AND 
td1.text_type = 500100000 -- die Nummer des PLZ-Bereichs
        AND lc.loc_type = 100800000
        WHERE td1.text_val = td2.text_val
)
ORDER BY h.id_lvl2, td2.text_val;
-- mit gültigem Datum 7637 Datensätze (darunter 
-- mit beliebigem Datum 7644 Datensätze
-- Differenz sind 7 Datensätze mit 4-stelligen PLZ für Hannover, Braunschweig, 
Bad Harzburg

Es gibt also wenige Abweichungen in Deutschland. Fehler?

Typ-1-Regel: Eine PLZ darf bei einem Ort höchstens einmal gespeichert sein.

-- PLZ höchstens einmal bei einem Ort gespeichert
SELECT loc_id, plz FROM
(
        SELECT loc_id, text_val AS plz FROM geodb_textdata AS td
        WHERE valid_until >= current_date
        AND text_type = 500300000 -- PLZ
) AS t1
GROUP BY loc_id, plz
HAVING count(*) > 1;
-- 0 Zeilen (1 Zeile vor der Entfernung der Duplikate oben)

-- alle Textdaten für die betroffenen Orte
SELECT * FROM geodb_textdata AS td1
INNER JOIN
(
        SELECT loc_id, plz FROM
        (
                SELECT loc_id, text_val AS plz FROM geodb_textdata AS td
                WHERE valid_until >= current_date
                AND text_type = 500300000 -- PLZ
        ) AS t1
        GROUP BY loc_id, plz
        HAVING count(*) > 1
) AS t2 ON t2.loc_id = td1.loc_id
ORDER BY td1.loc_id, td1.text_type;

Typ-1-Regel: Auf jedes Objekt, dass keine PLZ ist, muss einmal in 
geodb_hierarchies verwiesen werden.

SELECT loc_id FROM geodb_locations
EXCEPT
(
        SELECT id_lvl1 FROM geodb_hierarchies
        UNION
        SELECT id_lvl2 FROM geodb_hierarchies
        UNION
        SELECT id_lvl3 FROM geodb_hierarchies
        UNION
        SELECT id_lvl4 FROM geodb_hierarchies
        UNION
        SELECT id_lvl5 FROM geodb_hierarchies
        UNION
        SELECT id_lvl6 FROM geodb_hierarchies
        UNION
        SELECT id_lvl7 FROM geodb_hierarchies
        UNION
        SELECT id_lvl8 FROM geodb_hierarchies
        UNION
        SELECT id_lvl9 FROM geodb_hierarchies
)
EXCEPT
SELECT loc_id FROM geodb_locations
WHERE loc_type = 100800000;
-- 0 Zeilen, OK
-- bei Weglassen des letzten EXCEPT kommen die 8270 Postleitzahlbereiche

Typ-1-Regel: bei einem Ort in geodb_hierarchies auf Level n muss loc_id mit 
id_lvln übereinstimmen.

-- alle Orte (also alle Objekte, die keine PLZ-Bereiche sind) auf Level n,
-- bei denen loc_id nicht mit id_lvln übereinstimmt
SELECT * FROM geodb_locations AS lc
INNER JOIN geodb_textdata AS td ON td.loc_id = lc.loc_id
INNER JOIN geodb_hierarchies AS h ON h.loc_id = lc.loc_id
WHERE lc.loc_type <> 100800000 AND -- kein PLZ-Bereich
(
        (level = 1 AND h.loc_id <> h.id_lvl1) OR
        (level = 2 AND h.loc_id <> h.id_lvl2) OR
        (level = 3 AND h.loc_id <> h.id_lvl3) OR
        (level = 4 AND h.loc_id <> h.id_lvl4) OR
        (level = 5 AND h.loc_id <> h.id_lvl5) OR
        (level = 6 AND h.loc_id <> h.id_lvl6) OR
        (level = 7 AND h.loc_id <> h.id_lvl7) OR
        (level = 8 AND h.loc_id <> h.id_lvl8) OR
        (level = 9 AND h.loc_id <> h.id_lvl9)
);
-- 4 Zeilen, betreffend loc_id 579, Kreisfreie Stadt Hannover
-- immer mit Verweis auf loc_id 32382, Landkreis Region Hannover

Typ-1-Regel: Zu jedem Zeitpunkt muss die Hierachie für einen Ort eindeutig 
sein. Das ist nicht durch die Regel für die Duplikate oben abgedeckt. Es wäre 
zum Beispiel denkbar, dass bei einem Ort A ein Verweis auf Gemeinde B und ein 
Verweis auf Kreis C steht, bei Gemeinde A aber gleichzeitig ein Verweis auf 
Kreis D. Die Hierarchie für Gemeinde B wäre nicht eindeutig.
Die folgenden Abfragen berücksichtigen nur die aktuellen Daten.

-- eindeutige "Vorgängerlisten" (nur für das aktuelle Datum)
-- Level 8, Ortsteile
SELECT * FROM geodb_textdata AS td
INNER JOIN geodb_hierarchies AS h ON h.loc_id = td.loc_id
WHERE h.loc_id IN
(
        SELECT id_lvl8 FROM
        (
                SELECT DISTINCT h.id_lvl1, h.id_lvl2, h.id_lvl3, h.id_lvl4, 
h.id_lvl5, h.id_lvl6, h.id_lvl7, h.id_lvl8
                FROM geodb_locations AS lc
                INNER JOIN geodb_hierarchies AS h ON h.loc_id = lc.loc_id
                AND lc.loc_type <> 100800000 -- kein PLZ-Bereich
                WHERE level = 8
                AND valid_until >= current_date -- aktueller Datensatz
        ) AS t1
        GROUP BY id_lvl8
        HAVING count(*) > 1 -- mehr als eine "Vorgänger"-Liste
)
ORDER BY h.loc_id, td.text_type;

-- Level 7, Orte
-- in den mit *** markierten Zeilen Level berücksichtigen
SELECT * FROM geodb_textdata AS td
INNER JOIN geodb_hierarchies AS h ON h.loc_id = td.loc_id
WHERE h.loc_id IN
(
        SELECT id_lvl7 FROM -- ***
        (
                SELECT DISTINCT h.id_lvl1, h.id_lvl2, h.id_lvl3, h.id_lvl4, 
h.id_lvl5, h.id_lvl6, h.id_lvl7 -- ***
                FROM geodb_locations AS lc
                INNER JOIN geodb_hierarchies AS h ON h.loc_id = lc.loc_id
                AND lc.loc_type <> 100800000 -- kein PLZ-Bereich
                WHERE level = 7 -- ***
                AND valid_until >= current_date -- aktueller Datensatz
        ) AS t1
        GROUP BY id_lvl7 -- ***
        HAVING count(*) > 1 -- mehr als eine "Vorgänger"-Liste
)
ORDER BY h.loc_id, h.valid_until, td.text_type;

-- Level 6, Gemeinden
-- in den mit *** markierten Zeilen Level berücksichtigen
SELECT * FROM geodb_textdata AS td
INNER JOIN geodb_hierarchies AS h ON h.loc_id = td.loc_id
AND td.text_type IN (500100000, 500300000, 500600000) -- nur Name, PLZ und AGS 
anzeigen
WHERE h.loc_id IN
(
        SELECT id_lvl6 FROM -- ***
        (
                SELECT DISTINCT h.id_lvl1, h.id_lvl2, h.id_lvl3, h.id_lvl4, 
h.id_lvl5, h.id_lvl6 -- ***
                FROM geodb_locations AS lc
                INNER JOIN geodb_hierarchies AS h ON h.loc_id = lc.loc_id
                AND lc.loc_type <> 100800000 -- kein PLZ-Bereich
                WHERE level = 6 -- ***
                AND valid_until >= current_date -- aktueller Datensatz
        ) AS t1
        GROUP BY id_lvl6 -- ***
        HAVING count(*) > 1 -- mehr als eine "Vorgänger"-Liste
)
ORDER BY id_lvl2, h.loc_id, h.valid_until, td.text_type;

-- Level 5 Kreise
-- in den mit *** markierten Zeilen Level berücksichtigen
SELECT * FROM geodb_textdata AS td
INNER JOIN geodb_hierarchies AS h ON h.loc_id = td.loc_id
AND td.text_type IN (500100000, 500300000, 500600000) -- nur Name, PLZ und AGS 
anzeigen
WHERE h.loc_id IN
(
        SELECT id_lvl5 FROM -- ***
        (
                SELECT DISTINCT h.id_lvl1, h.id_lvl2, h.id_lvl3, h.id_lvl4, 
h.id_lvl5 -- ***
                FROM geodb_locations AS lc
                INNER JOIN geodb_hierarchies AS h ON h.loc_id = lc.loc_id
                AND lc.loc_type <> 100800000 -- kein PLZ-Bereich
                WHERE level = 5 -- ***
                AND valid_until >= current_date -- aktueller Datensatz
        ) AS t1
        GROUP BY id_lvl5 -- ***
        HAVING count(*) > 1 -- mehr als eine "Vorgänger"-Liste
)
ORDER BY id_lvl2, h.loc_id, h.valid_until, td.text_type;

-- Level 4, Regierungsbezirke
-- in den mit *** markierten Zeilen Level berücksichtigen
SELECT * FROM geodb_textdata AS td
INNER JOIN geodb_hierarchies AS h ON h.loc_id = td.loc_id
AND td.text_type IN (500100000, 500300000) -- nur Name, PLZ anzeigen
WHERE h.loc_id IN
(
        SELECT id_lvl4 FROM -- ***
        (
                SELECT DISTINCT h.id_lvl1, h.id_lvl2, h.id_lvl3, h.id_lvl4 -- 
***
                FROM geodb_locations AS lc
                INNER JOIN geodb_hierarchies AS h ON h.loc_id = lc.loc_id
                AND lc.loc_type <> 100800000 -- kein PLZ-Bereich
                WHERE level = 4 -- ***
                AND valid_until >= current_date -- aktueller Datensatz
        ) AS t1
        GROUP BY id_lvl4 -- ***
        HAVING count(*) > 1 -- mehr als eine "Vorgänger"-Liste
)
ORDER BY id_lvl2, h.loc_id, h.valid_until, td.text_type;
-- 15 Zeilen mit loc_id 0
-- Ausnahmeregel: wenn bei id_lvl4 0 steht, darf es unterschiedliche 
Vorgängerlisten geben
-- Allgemeiner: wenn ein übergeordneter Level nicht verwendet wird.

Typ-2-Regel: Jeder Ort (Level 6 bis 8) hat eine PLZ. Was sind die Ausnahmen? 
Oder besser Typ-1-Regel?

-- Orte ohne PLZ
SELECT * FROM geodb_locations AS lc
INNER JOIN geodb_hierarchies AS h ON h.loc_id = lc.loc_id
AND h.valid_until >= current_date
AND h.level >= 6
AND lc.loc_type <> 100800000
INNER JOIN geodb_textdata AS td1 ON td1.loc_id = h.loc_id
AND td1.text_type = 500100000 AND is_default_name -- Name
LEFT JOIN geodb_textdata AS td2 ON td2.loc_id = h.loc_id
AND td2.text_type = 500300000 -- PLZ
WHERE td2.loc_id IS NULL
ORDER BY h.id_lvl2, h.id_lvl3, h.id_lvl5, h.loc_id, h.valid_until;
-- 25 Zeilen

Typ-2-Regel: Jeder Ort (in Deutschland?) auf Level 6 hat einen AGS. Was sind 
die Ausnahmen?

-- Orte auf Level 6, die keinen AGS haben (Gemeinden in Deutschland)
-- Gemeinden in Deutschland ohne Schlüssel
SELECT * FROM geodb_locations AS lc
INNER JOIN geodb_hierarchies AS h ON h.loc_id = lc.loc_id
AND h.valid_until >= current_date
AND h.level = 6
AND h.id_lvl2 = 105 -- Deutschland, sonst sind sowieso keine AGS vorhanden
AND lc.loc_type <> 100800000
INNER JOIN geodb_textdata AS td1 ON td1.loc_id = lc.loc_id
AND td1.text_type = 500100000 AND is_default_name -- Name
AND td1.valid_until >= current_date
LEFT JOIN geodb_textdata AS td2 ON td2.loc_id = lc.loc_id
AND td2.text_type = 500600000 -- AGS
AND td2.valid_until >= current_date -- ergibt 1 Zeile mehr
WHERE td2.loc_id IS NULL
ORDER BY h.id_lvl2, h.id_lvl3, h.id_lvl5, h.loc_id, h.valid_until;

Wenn das "normale" Orte sind, wie jemand geschrieben hat, was sind dann Orte 
auf Level 7?

-- Orte auf Level 7, die einen Verweis auf einen "normalen" Ort auf Level 6 
haben
-- Orte mit Verweis auf Gemeinden (Level 6) ohne AGS
SELECT * FROM geodb_locations AS lc
INNER JOIN geodb_hierarchies AS h ON h.loc_id = lc.loc_id
AND h.valid_until >= current_date
AND h.level = 7
AND lc.loc_type <> 100800000
INNER JOIN geodb_textdata AS td1 ON td1.loc_id = h.loc_id
AND td1.text_type = 500100000 AND is_default_name -- Name
AND td1.valid_until >= current_date
LEFT JOIN geodb_textdata AS td2 ON td2.loc_id = h.id_lvl6 -- auf diese 
Bedingung achten
AND td2.text_type = 500600000 -- AGS
AND td2.valid_until >= current_date -- ergibt 3 Zeilen mehr
WHERE td2.loc_id IS NULL
ORDER BY h.id_lvl2, h.id_lvl3, h.id_lvl5, h.loc_id, h.valid_until;
-- 2738 Zeilen

-- Ortsteile (Level 8) in Belgien mit Verweisen auf Gemeinden ohne 
Gemeindeschlüssel
SELECT * FROM geodb_locations AS lc
INNER JOIN geodb_hierarchies AS h ON h.loc_id = lc.loc_id
--AND h.valid_until >= current_date
AND h.level = 8
AND h.id_lvl2 = 633 -- Belgien
--AND h.id_lvl7 < 2 -- unbekannter Ort?
AND lc.loc_type <> 100800000
INNER JOIN geodb_textdata AS td1 ON td1.loc_id = h.loc_id
AND td1.text_type = 500100000 AND is_default_name -- Name
AND td1.valid_until >= current_date;
-- 29 Zeilen, davon 13 mit id_lvl7 = -1

-- Ortsteile in Deutschland mit Verweisen auf Gemeinden ohne Gemeindeschlüssel
SELECT * FROM geodb_locations AS lc
INNER JOIN geodb_hierarchies AS h ON h.loc_id = lc.loc_id
AND h.valid_until >= current_date -- erhebliche Auswirkung
AND h.level = 8
AND h.id_lvl2 = 105 -- Deutschland
--AND h.id_lvl7 < 2 -- unbekannter Ort?
AND lc.loc_type <> 100800000
INNER JOIN geodb_textdata AS td1 ON td1.loc_id = h.loc_id
AND td1.text_type = 500100000 AND is_default_name -- Name
AND td1.valid_until >= current_date
LEFT JOIN geodb_textdata AS td2 ON td2.loc_id = h.id_lvl6 -- auf diese 
Bedingung achten
AND td2.text_type = 500600000 -- AGS
AND td2.valid_until >= current_date
WHERE td2.loc_id IS NULL;
-- 0 Zeilen, OK

Typ-1-Regel: Wird bei einem Ort eine PLZ eingetragen, so muss die PLZ auch bei 
der übergeordneten Gemeinde eingetragen sein.

-- prüfen, ob für jeden Ort mit PLZ dieselbe PLZ bei der Gemeinde vorhanden ist
SELECT * FROM geodb_locations AS lc
INNER JOIN geodb_hierarchies AS h ON h.loc_id = lc.loc_id
AND h.valid_until >= current_date -- mit Wirkung
AND h.level IN (7, 8)
AND lc.loc_type <> 100800000
INNER JOIN geodb_textdata AS td1 ON td1.loc_id = h.loc_id
AND td1.text_type = 500100000 AND td1.is_default_name -- Name
AND td1.valid_until >= current_date
INNER JOIN geodb_textdata AS td2 ON td2.loc_id = h.loc_id
AND td2.text_type = 500300000 -- PLZ
AND td2.valid_until >= current_date -- mit Wirkung
INNER JOIN geodb_textdata AS td3 ON td3.loc_id = h.id_lvl6
AND td3.text_type = 500100000 AND td3.is_default_name -- Name
INNER JOIN geodb_textdata AS td4 ON td4.loc_id = h.id_lvl6
AND td4.text_type = 500600000 -- AGS
AND td4.valid_until >= current_date -- mit Wirkung
LEFT JOIN geodb_textdata AS td5 ON td5.loc_id = h.id_lvl6
AND td5.text_type = 500300000 -- PLZ
AND td5.valid_until >= current_date -- ohne Wirkung
AND td5.text_val = td2.text_val -- dieselbe PLZ vorhanden?
WHERE td5.loc_id IS NULL -- dieselbe PLZ bei Gemeinde nicht vorhanden
ORDER BY h.id_lvl2, h.id_lvl3, h.id_lvl5, h.loc_id, h.valid_until;
-- 92 Zeilen (nach Korrekturen 94)

Frage: Kommt es tatsächlich vor, dass eine PLZ in verschiedenen Kreisen benutzt 
wird?

-- dieselbe PLZ in verschiedenen Kreisen?
SELECT t1.id_lvl2, t1.plz, count(*) AS anzahl FROM
(
        SELECT DISTINCT h.id_lvl2, h.id_lvl5, td.text_val AS plz FROM 
geodb_hierarchies AS h
        INNER JOIN geodb_textdata AS td ON td.loc_id = h.loc_id
        AND td.text_type = 500300000 -- PLZ
) AS t1
GROUP BY t1.id_lvl2, t1.plz
HAVING count(*) > 1
ORDER BY t1.id_lvl2, t1.plz;
-- 229 Zeilen
-- Ist das korrekt?

Gruß Ingmar
-- 
Mailingliste OpenGeoDB
Listenadresse: opengeodb-r1mDYR0DdAyzQB+pC5nmwQ@xxxxxxxxxxxxxxxx
Informationen: http://opengeodb.de
Mit freundlicher Unterstütztung von php::bar (http://phpbar.de)



<Prev in Thread] Current Thread [Next in Thread>
Google Custom Search

Recently Viewed:
linux.arklinux....    user-groups.lin...    kde.usability/2...    ietf.ipp/2002-0...    mail.spam.spamc...    os.netbsd.devel...    audio.cd-record...    text.unicode.de...    php.documentati...    games.fps.halfl...    window-managers...    suse.oracle.gen...    bug-tracking.gn...    video.dvdrip.us...    xfree86.cvs/200...    java.netbeans.m...    network.argus/2...    culture.sf.kill...    debian.ports.al...    freebsd.questio...    qplus.devel/200...    handhelds.palm....   
Home | blog view | USPTO Patent Archive | advertise | OSDir is an inevitable website. super tiny logo

Free Magazines

Cisco News
Receive a free quarterly e-newsletter with exclusive articles on how Cisco IT uses its own products and solutions to enable the business.
subscribe

Systems Management News, the newspaper for IT systems administration and data center managers! Each issue of Systems Management News is chock-full of news and analysis to help you understand what's happening in your field.
subscribe

The Enterprise Newsweekly eWeek is the essential technology information source for builders of e-business.
subscribe

Oracle Magazine Oracle Magazine contains technology strategy articles, sample code, tips, Oracle and partner news, how to articles for developers and DBAs, and more. Oracle (NASDAQ: ORCL) is the world's largest enterprise software company.
subscribe

Total Telecom Total Telecom is "The Economist of the communications industry".
subscribe