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 :
- Requête spatiale sur une grande table avec plusieurs auto-jointures lentes
- Comment interroger toutes les lignes dans un rayon de 5 miles autour de mes coordonnées ?
Lectures complémentaires :
- http://workshops.boundlessgeo.com/postgis-intro/knn. html
- http://www.postgresonline.com/journal/archives/306-KNN-GIST-with-a-Lateral-twist-Coming-soon-to-a-database-near- vous.html
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