sql >> Database teknologi >  >> RDS >> PostgreSQL

Unik tildeling af nærmeste punkter mellem to tabeller

Tabelskema

For at håndhæve din regel skal du blot erklære pvanlagen.buildid UNIQUE :

ALTER TABLE pvanlagen ADD CONSTRAINT pvanlagen_buildid_uni UNIQUE (buildid);

building.gid er PK, som din opdatering afslørede. For også at håndhæve referenceintegritet skal du tilføje en UDLANDS NØGLE begrænsning til buildings.gid .

Du har implementeret begge dele nu. Men det ville være mere effektivt at køre den store OPDATERING nedenfor før du tilføjer disse begrænsninger.

Der er meget mere, der bør forbedres i din tabeldefinition. For det første buildings.gid samt pvanlagen.buildid skal være typen heltal (eller muligvis bigint hvis du brænder meget af PK-værdier). numerisk er dyrt sludder.

Lad os fokusere på kerneproblemet:

Grundlæggende forespørgsel for at finde den nærmeste bygning

Sagen er ikke så enkel, som den kan se ud. Det er en "nærmeste nabo" problem, med den yderligere komplikation af unikke tildelinger.

Denne forespørgsel finder den nærmeste en bygning for hver PV (forkortelse for PV Anlage - række i pvanlagen ), hvor ingen af ​​dem er tildelt endnu:

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;

For at gøre denne forespørgsel hurtig, skal du bruge et rumligt, funktionelt GiST-indeks på bygninger for at gøre det meget hurtigere:

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

Ikke sikker på hvorfor det gør du ikke

Relaterede svar med mere forklaring:

Yderligere læsning:

Med indekset på plads behøver vi ikke at begrænse match til det samme gemname for ydeevne. Gør kun dette, hvis det er en egentlig regel, der skal håndhæves. Hvis det til enhver tid skal overholdes, skal du inkludere kolonnen i FK-begrænsningen:

Resterende problem

Vi kan bruge ovenstående forespørgsel i en OPDATERING udmelding. Hver PV bruges kun én gang, men mere end én PV kan stadig finde den samme bygning at være tættest på. Du tillader kun én PV pr bygning. Så hvordan ville du løse det?

Med andre ord, hvordan ville du tildele objekter her?

Simpel løsning

En simpel løsning ville væ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;

Jeg bruger DISTINCT ON (b_gid) for at reducere til præcis én række pr. bygning, og vælg den PV med den korteste afstand. Detaljer:

For enhver bygning, der er tættest på mere én PV, er kun den nærmeste PV tildelt. PK-kolonnen gid (alias pv_gid ) fungerer som tiebreaker, hvis to er lige tæt på hinanden. I et sådant tilfælde slettes nogle PV fra opdateringen og forbliver utildelt . Gentag forespørgslen, indtil alle PV er tildelt.

Dette er stadig en forenklet algoritme , selvom. Ser man på mit diagram ovenfor, tildeler dette bygning 4 til PV 4 og bygning 5 til PV 5, mens 4-5 og 5-4 sandsynligvis ville være en bedre løsning generelt ...

Aside:Indtast dist kolonne

I øjeblikket bruger du numerisk for det. din oprindelige forespørgsel tildelte et konstant heltal , ingen mening med numerisk .

I min nye forespørgsel ST_Distance() returnerer den faktiske afstand i meter som dobbelt præcision . Hvis vi blot tildeler det, får vi 15 eller deromkring brøkcifre i den numeriske datatype, og nummeret er ikke det præcis til at begynde med. Jeg tvivler alvorligt på, at du vil spilde lageret.

Jeg vil hellere gemme den originale dobbelt præcision ud fra beregningen. eller endnu bedre , rund efter behov. Hvis målerne er nøjagtige nok, skal du blot caste til og gemme et heltal (afrunding af tallet automatisk). Eller gange med 100 først for at spare cm:

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



  1. Tovejs synkronisering mellem lokal mysql-database og AWS RDS

  2. Får du først EKSAKTE resultater fra fuldtekstsøgning returneret?

  3. MySQL får mængder fra sidste 12 måneder grupperet efter måned

  4. pdo-opdateringserklæring ved hjælp af concat virker ikke