PostgreSQL
 sql >> Base de données >  >> RDS >> PostgreSQL

Affectation unique des points les plus proches entre deux tables

Schéma du tableau

Pour appliquer votre règle, déclarez simplement pvanlagen.buildid UNIQUE :

ALTER TABLE pvanlagen ADD CONSTRAINT pvanlagen_buildid_uni UNIQUE (buildid);

building.gid est le PK, comme votre mise à jour l'a révélé. Pour appliquer également l'intégrité référentielle, ajoutez un FOREIGN KEY contrainte vers buildings.gid .

Vous avez implémenté les deux maintenant. Mais il serait plus efficace d'exécuter le gros UPDATE en dessous avant vous ajoutez ces contraintes.

Il y a beaucoup plus à améliorer dans la définition de votre table. D'une part, buildings.gid ainsi que pvanlagen.buildid doit être de type integer (ou éventuellement bigint si vous brûlez beaucoup des valeurs PK). numeric est un non-sens coûteux.

Concentrons-nous sur le problème principal :

Requête de base pour trouver le bâtiment le plus proche

L'affaire n'est pas aussi simple qu'il y paraît. C'est un "plus proche voisin" problème, avec la complication supplémentaire d'une affectation unique.

Cette requête trouve le un le plus proche bâtiment pour chaque PV (abréviation de PV Anlage - rangée dans pvanlagen ), où ni l'un ni l'autre n'est encore attribué :

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;

Pour accélérer cette requête, vous avez besoin un index GiST spatial et fonctionnel sur les buildings pour en faire beaucoup plus rapide :

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

Je ne sais pas pourquoi ce n'est pas le cas

Réponses associées avec plus d'explications :

Lectures complémentaires :

Avec l'index en place, nous n'avons pas besoin de restreindre les correspondances au même gemname pour les performances. Ne le faites que s'il s'agit d'une règle réelle à appliquer. Si cela doit être observé à tout moment, incluez la colonne dans la contrainte FK :

Problème restant

Nous pouvons utiliser la requête ci-dessus dans un UPDATE déclaration. Chaque PV n'est utilisé qu'une seule fois, mais plusieurs PV peuvent toujours trouver le même bâtiment être le plus proche. Vous n'en autorisez qu'un PV par bâtiment. Alors, comment résoudriez-vous cela ?

En d'autres termes, comment assigneriez-vous des objets ici ?

Solution simple

Une solution simple serait :

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;

J'utilise DISTINCT ON (b_gid) réduire à exactement un ligne par bâtiment, en choisissant le PV avec la distance la plus courte. Détails :

Pour tout bâtiment le plus proche pour plus d'un PV, seul le PV le plus proche est attribué. La colonne PK gid (alias pv_gid ) sert de bris d'égalité si deux sont également proches. Dans ce cas, certains PV sont supprimés de la mise à jour et restent non attribués . Répéter la requête jusqu'à ce que tous les PV soient attribués.

Il s'agit toujours d'un algorithme simpliste , pourtant. En regardant mon diagramme ci-dessus, cela affecte le bâtiment 4 au PV 4 et le bâtiment 5 au PV 5, tandis que 4-5 et 5-4 seraient probablement une meilleure solution globale...

À part :tapez pour dist colonne

Actuellement, vous utilisez numeric pour ça. votre requête d'origine a assigné un integer constant , pas de point faisant en numeric .

Dans ma nouvelle requête ST_Distance() renvoie la distance réelle en mètres sous la forme double precision . Si nous attribuons simplement cela, nous obtenons environ 15 chiffres fractionnaires dans le numeric type de données, et le nombre n'est pas celui exact pour commencer. Je doute sérieusement que vous vouliez gaspiller le stockage.

Je préfère enregistrer la double precision d'origine du calcul. ou, mieux encore , rond au besoin. Si les mètres sont suffisamment précis, il suffit de convertir et d'enregistrer un integer (arrondir le nombre automatiquement). Ou multipliez d'abord par 100 pour économiser cm :

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