PostgreSQL
 sql >> Baza danych >  >> RDS >> PostgreSQL

Unikalne przypisanie najbliższych punktów między dwoma stołami

Schemat tabeli

Aby wymusić regułę, po prostu zadeklaruj pvanlagen.buildid UNIKALNE :

ALTER TABLE pvanlagen ADD CONSTRAINT pvanlagen_buildid_uni UNIQUE (buildid);

budynek.gid to PK, jak ujawniła twoja aktualizacja. Aby wymusić również integralność referencyjną, dodaj KLUCZ OBCY ograniczenie do buildings.gid .

Do tej pory wdrożyłeś oba. Ale bardziej wydajne byłoby uruchomienie dużej AKTUALIZACJI poniżej przed dodajesz te ograniczenia.

Jest o wiele więcej rzeczy, które należy poprawić w definicji tabeli. Po pierwsze, buildings.gid jak również pvanlagen.buildid należy wpisać liczba całkowita (lub ewentualnie bigint jeśli palisz dużo wartości PK). numeryczny to kosztowny nonsens.

Skupmy się na głównym problemie:

Podstawowe zapytanie, aby znaleźć najbliższy budynek

Sprawa nie jest tak prosta, jak mogłoby się wydawać. To "najbliższy sąsiad" problem, z dodatkową komplikacją unikalnego przypisania.

To zapytanie znajduje najbliższy jeden budynek dla każdego PV (skrót od PV Anlage - wiersz w pvanlagen ), gdzie żadne z nich nie jest jeszcze przypisane:

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;

Aby przyspieszyć to zapytanie, potrzebujesz przestrzenny, funkcjonalny indeks GiST na budynkach aby było dużo szybciej:

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

Nie wiem dlaczego ty nie

Powiązane odpowiedzi z dodatkowym wyjaśnieniem:

Dalsza lektura:

Mając indeks, nie musimy ograniczać dopasowań do tego samego gemname dla wydajności. Zrób to tylko wtedy, gdy jest to reguła do wyegzekwowania. Jeśli ma to być obserwowane przez cały czas, uwzględnij kolumnę w ograniczeniu FK:

Pozostały problem

Możemy użyć powyższego zapytania w UPDATE oświadczenie. Każdy PV jest używany tylko raz, ale więcej niż jeden PV może nadal znaleźć ten sam budynek być najbliżej. Zezwalasz tylko na jeden PV na budynek. Jak więc byś to rozwiązał?

Innymi słowy, jak przypisałbyś tutaj obiekty?

Proste rozwiązanie

Jednym prostym rozwiązaniem byłoby:

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;

Używam DISTINCT ON (b_gid) zmniejszyć do dokładnie jednego wiersz na budynek, wybierając PV o najkrótszej odległości. Szczegóły:

Do każdego budynku, który jest najbliżej więcej niż jednego PV, przypisywany jest tylko najbliższy PV. Kolumna PK gid (alias pv_gid ) służy jako rozstrzygający remis, jeśli dwie osoby są jednakowo blisko siebie. W takim przypadku niektóre PV są usuwane z aktualizacji i pozostają nieprzypisane . Powtórz zapytanie, aż wszystkie PV zostaną przypisane.

To wciąż uproszczony algorytm , chociaż. Patrząc na mój diagram powyżej, to przypisuje budynek 4 do PV 4 i budynek 5 do PV 5, podczas gdy 4-5 i 5-4 byłyby prawdopodobnie lepszym rozwiązaniem ogólnie ...

Na bok:wpisz odleg kolumna

Obecnie używasz numeric dla tego. Twoje oryginalne zapytanie przypisało stałą liczbę całkowitą , nie ma sensu robić w numeric .

W moim nowym zapytaniu ST_Distance() zwraca rzeczywistą odległość w metrach jako double precyzja . Jeśli po prostu przypiszemy to, otrzymamy 15 lub więcej cyfr ułamkowych w numerycznym typ danych, a liczba nie jest ta dokładnie na początek. Poważnie wątpię, czy chcesz marnować magazyn.

Wolałbym zapisać oryginalny podwójnej precyzji z obliczeń. lub jeszcze lepiej , w razie potrzeby zaokrąglić. Jeśli liczniki są wystarczająco dokładne, po prostu rzutuj i zapisz liczbę całkowitą (automatyczne zaokrąglanie liczby). Lub pomnóż najpierw przez 100, aby zaoszczędzić cm:

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



  1. Database
  2.   
  3. Mysql
  4.   
  5. Oracle
  6.   
  7. Sqlserver
  8.   
  9. PostgreSQL
  10.   
  11. Access
  12.   
  13. SQLite
  14.   
  15. MariaDB
  1. Wstaw tekst z pojedynczymi cudzysłowami w PostgreSQL

  2. Geo Django dla systemu Mac OS X

  3. Heroku - ActiveRecord::StatementInvalid (PG::Error:ERROR:żądana kolumna nie istnieje

  4. Laravel 5 z Postgres SQL

  5. Ustaw domyślną wartość zwracaną dla funkcji Postgres