PostgreSQL
 sql >> Datenbank >  >> RDS >> PostgreSQL

Eindeutige Zuordnung der nächstgelegenen Punkte zwischen zwei Tabellen

Tabellenschema

Um Ihre Regel durchzusetzen, deklarieren Sie einfach pvanlagen.buildid EINZIGARTIG :

ALTER TABLE pvanlagen ADD CONSTRAINT pvanlagen_buildid_uni UNIQUE (buildid);

building.gid ist die PK, wie dein Update ergeben hat. Um auch die referenzielle Integrität zu erzwingen, fügen Sie einen Fremdschlüssel Einschränkung zu buildings.gid .

Beides haben Sie inzwischen umgesetzt. Aber es wäre effizienter, das große UPDATE auszuführen unten vorher Sie fügen diese Einschränkungen hinzu.

Es gibt noch viel mehr, was in Ihrer Tabellendefinition verbessert werden sollte. Zum einen buildings.gid sowie pvanlagen.buildid sollte vom Typ integer sein (oder möglicherweise bigint wenn Sie viel brennen von PK-Werten). numerisch ist teurer Quatsch.

Konzentrieren wir uns auf das Kernproblem:

Einfache Abfrage, um das nächstgelegene Gebäude zu finden

Der Fall ist nicht so einfach, wie es scheinen mag. Es ist ein "nächster Nachbar" Problem, mit der zusätzlichen Komplikation der eindeutigen Zuordnung.

Diese Abfrage findet die nächste Eins Gebäude für jede PV (kurz für PV Anlage - Zeile in pvanlagen ), wo weder zugewiesen noch:

SELECT pv_gid, b_gid, dist
FROM  (
   SELECT gid AS pv_gid, ST_Transform(geom, 31467) AS geom31467
   FROM   pvanlagen
   WHERE  buildid IS NULL  -- not assigned yet
   ) p
     , LATERAL (
   SELECT b.gid AS b_gid
        , round(ST_Distance(p.geom31467
                      , ST_Transform(b.centroid, 31467))::numeric, 2) AS dist  -- see below
   FROM   buildings b
   LEFT   JOIN pvanlagen p1 ON p1.buildid = b.gid  -- also not assigned ...
   WHERE  p1.buildid IS NULL                       -- ... yet  
   -- AND    p.gemname = b.gemname                 -- not needed for performance, see below
   ORDER  BY p.geom31467 <-> ST_Transform(b.centroid, 31467)
   LIMIT  1
   ) b;

Um diese Abfrage schnell zu machen, brauchen Sie ein räumlicher, funktionaler GiST-Index zu Gebäuden um es viel zu machen schneller:

CREATE INDEX build_centroid_gix ON buildings USING gist (ST_Transform(centroid, 31467));

Nicht sicher warum du nicht

Verwandte Antworten mit mehr Erklärung:

Weiterführende Literatur:

Wenn der Index vorhanden ist, müssen wir Übereinstimmungen nicht auf denselben Edelsteinnamen beschränken für Leistung. Tun Sie dies nur, wenn es sich tatsächlich um eine zu erzwingende Regel handelt. Wenn es immer eingehalten werden muss, nehmen Sie die Spalte in die FK-Einschränkung auf:

Verbleibendes Problem

Wir können die obige Abfrage in einem UPDATE verwenden Aussage. Jedes PV wird nur einmal verwendet, aber mehr als ein PV kann immer noch dasselbe Gebäude finden am nächsten sein. Sie erlauben nur eine PV pro Gebäude. Wie würden Sie das lösen?

Mit anderen Worten, wie würden Sie hier Objekte zuweisen?

Einfache Lösung

Eine einfache Lösung wäre:

UPDATE pvanlagen p1
SET    buildid = sub.b_gid
     , dist    = sub.dist  -- actual distance
FROM  (
   SELECT DISTINCT ON (b_gid)
          pv_gid, b_gid, dist
   FROM  (
      SELECT gid AS pv_gid, ST_Transform(geom, 31467) AS geom31467
      FROM   pvanlagen
      WHERE  buildid IS NULL  -- not assigned yet
      ) p
        , LATERAL (
      SELECT b.gid AS b_gid
           , round(ST_Distance(p.geom31467
                         , ST_Transform(b.centroid, 31467))::numeric, 2) AS dist  -- see below
      FROM   buildings      b
      LEFT   JOIN pvanlagen p1 ON p1.buildid = b.gid  -- also not assigned ...
      WHERE  p1.buildid IS NULL                       -- ... yet  
      -- AND    p.gemname = b.gemname                 -- not needed for performance, see below
      ORDER  BY p.geom31467 <-> ST_Transform(b.centroid, 31467)
      LIMIT  1
      ) b
   ORDER  BY b_gid, dist, pv_gid  -- tie breaker
   ) sub
WHERE   p1.gid = sub.pv_gid;

Ich verwende DISTINCT ON (b_gid) auf genau eins zu reduzieren Reihe pro Gebäude, wobei das PV mit dem kürzesten Abstand ausgewählt wird. Einzelheiten:

Für jedes Gebäude, das für mehrere PV am nächsten ist, wird nur das nächste PV zugewiesen. Die PK-Spalte gid (alias pv_gid ) dient als Tiebreaker, wenn zwei gleich nah dran sind. In einem solchen Fall werden einige PV aus dem Update gelöscht und bleiben nicht zugewiesen . Wiederholen die Abfrage, bis alle PV zugewiesen sind.

Dies ist immer noch ein einfacher Algorithmus , obwohl. Betrachtet man mein Diagramm oben, weist dies Gebäude 4 PV 4 und Gebäude 5 PV 5 zu, während 4-5 und 5-4 insgesamt wahrscheinlich eine bessere Lösung wären ...

Beiseite:Geben Sie für dist ein Spalte

Derzeit verwenden Sie numeric dafür. Ihrer ursprünglichen Abfrage wurde eine Konstante integer zugewiesen , macht keinen Sinn in numerisch .

In meiner neuen Abfrage ST_Distance() gibt die tatsächliche Entfernung in Metern als double zurück Präzision . Wenn wir das einfach zuweisen, erhalten wir ungefähr 15 Nachkommastellen im numeric Datentyp, und die Nummer ist nicht das anfangs genau. Ich bezweifle ernsthaft, dass Sie den Speicherplatz verschwenden wollen.

Ich würde lieber die ursprüngliche double precision speichern aus der Berechnung. oder noch besser , nach Bedarf runden. Wenn Meter genau genug sind, wandeln Sie einfach um und speichern Sie eine Ganzzahl (Rundung der Zahl automatisch). Oder erst mit 100 multiplizieren, um cm zu sparen:

(ST_Distance(...) * 100)::int