sql >> Databasteknik >  >> RDS >> PostgreSQL

Unik tilldelning av närmaste punkter mellan två tabeller

Tabellschema

För att upprätthålla din regel, deklarera helt enkelt pvanlagen.buildid UNIQUE :

ALTER TABLE pvanlagen ADD CONSTRAINT pvanlagen_buildid_uni UNIQUE (buildid);

building.gid är PK, som din uppdatering visade. För att även upprätthålla referensintegritet lägg till en UTLANDSNYCKEL begränsning till buildings.gid .

Du har implementerat båda vid det här laget. Men det skulle vara mer effektivt att köra den stora UPDATE nedan före du lägger till dessa begränsningar.

Det finns mycket mer som borde förbättras i din tabelldefinition. För det första, buildings.gid samt pvanlagen.buildid ska vara typ heltal (eller möjligen bigint om du bränner mycket av PK-värden). numerisk är dyrt nonsens.

Låt oss fokusera på kärnproblemet:

Grundläggande fråga för att hitta närmaste byggnad

Fallet är inte så enkelt som det kan verka. Det är en "närmaste granne" problem, med den ytterligare komplikationen av unik tilldelning.

Den här frågan hittar den närmaste en byggnad för varje PV (förkortning för PV Anlage - rad i pvanlagen ), där ingendera är tilldelad, ännu:

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;

För att göra den här frågan snabb behöver du ett rumsligt, funktionellt GiST-index på byggnader för att göra det mycket snabbare:

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

Inte säker på varför det gör du inte

Relaterade svar med mer förklaring:

Mer läsning:

Med indexet på plats behöver vi inte begränsa matchningar till samma gemnamn för prestation. Gör bara detta om det är en faktisk regel som ska tillämpas. Om det måste observeras hela tiden, inkludera kolumnen i FK-begränsningen:

Återstående problem

Vi kan använda ovanstående fråga i en UPPDATERING påstående. Varje PV används bara en gång, men mer än en PV kan fortfarande hitta samma byggnad att vara närmast. Du tillåter bara en PV per byggnad. Så hur skulle du lösa det?

Med andra ord, hur skulle du tilldela objekt här?

Enkel lösning

En enkel lösning skulle vara:

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;

Jag använder DISTINCT ON (b_gid) för att reducera till exakt ett rad per byggnad, välj den PV med det kortaste avståndet. Detaljer:

För varje byggnad som är närmast för mer en PV, tilldelas endast den närmaste PV. PK-kolumnen gid (alias pv_gid ) fungerar som tiebreaker om två är lika nära. I ett sådant fall tas vissa PV bort från uppdateringen och förblir ej tilldelade . Upprepa frågan tills alla PV är tilldelade.

Detta är fortfarande en förenklad algoritm , fastän. Om man tittar på mitt diagram ovan, tilldelar detta byggnad 4 till PV 4 och byggnad 5 till PV 5, medan 4-5 och 5-4 förmodligen skulle vara en bättre lösning totalt sett...

Aside:skriv för dist kolumn

För närvarande använder du numerisk för det. din ursprungliga fråga tilldelade ett konstant heltal , ingen mening med numerisk .

I min nya fråga ST_Distance() returnerar det faktiska avståndet i meter som dubbel precision . Om vi ​​helt enkelt tilldelar att vi får 15 eller så bråksiffror i den numeriska datatyp, och numret är inte det exakt till att börja med. Jag tvivlar allvarligt på att du vill slösa bort lagringen.

Jag skulle hellre spara den ursprungliga dubbla precisionen från beräkningen. eller, bättre än , runda efter behov. Om mätare är tillräckligt exakta, casta bara till och spara ett heltal (avrundar numret automatiskt). Eller multiplicera med 100 först för att spara cm:

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



  1. SQL-identitet med inledande vadderade nollor

  2. Uppdatera en rad, men infoga om rad inte finns i kodantändaren

  3. SQL Server Cursor Types - Framåt endast statisk markör | SQL Server Tutorial / TSQL Tutorial

  4. Hur man använder GROUP_CONCAT-funktionen på MSSQL