Esquema de tabla
Para hacer cumplir su regla simplemente declare pvanlagen.buildid
UNIQUE
:
ALTER TABLE pvanlagen ADD CONSTRAINT pvanlagen_buildid_uni UNIQUE (buildid);
building.gid
es el PK, como reveló su actualización. Para hacer cumplir también la integridad referencial, agregue un FOREIGN KEY
restricción
a buildings.gid
.
Ya has implementado ambos. Pero sería más eficiente ejecutar el gran UPDATE
debajo antes agregas estas restricciones.
Hay mucho más que debería mejorarse en la definición de su tabla. Por un lado, buildings.gid
así como pvanlagen.buildid
debe ser tipo integer
(o posiblemente bigint
si quemas mucho de los valores de PK). numeric
es una tontería cara.
Centrémonos en el problema central:
Consulta básica para encontrar el edificio más cercano
El caso no es tan sencillo como puede parecer. Es un "neighbor más cercano" problema, con la complicación adicional de asignación única.
Esta consulta encuentra el uno más cercano edificio para cada PV (abreviatura de PV Anlage - fila en pvanlagen
), donde ninguno está asignado, todavía:
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;
Para que esta consulta sea rápida, necesita un índice GiST espacial y funcional en buildings
para que sea mucho más rápido:
CREATE INDEX build_centroid_gix ON buildings USING gist (ST_Transform(centroid, 31467));
No estoy seguro de por qué tu no
Respuestas relacionadas con más explicaciones:
- Consulta espacial en una tabla grande con múltiples uniones automáticas con un rendimiento lento
- ¿Cómo consulto todas las filas dentro de un radio de 5 millas de mis coordenadas?
Lectura adicional:
- 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- usted.html
Con el índice en su lugar, no necesitamos restringir las coincidencias al mismo gemname
para el rendimiento Solo haga esto si se trata de una regla real que se debe hacer cumplir. Si tiene que observarse en todo momento, incluya la columna en la restricción FK:
Problema pendiente
Podemos usar la consulta anterior en un UPDATE
declaración. Cada PV solo se usa una vez, pero es posible que más de un PV encuentre el mismo edificio estar más cerca. Solo permites uno PV por edificio. Entonces, ¿cómo resolverías eso?
En otras palabras, ¿cómo asignaría objetos aquí?
Solución sencilla
Una solución simple sería:
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;
Uso DISTINCT ON (b_gid)
para reducir a exactamente uno fila por edificio, eligiendo el PV con la distancia más corta. Detalles:
Para cualquier edificio que esté más cerca de más de un PV, solo se asigna el PV más cercano. La columna PK gid
(alias pv_gid
) sirve como desempate si dos están igualmente cerca. En tal caso, algunos PV se eliminarán de la actualización y permanecerán sin asignar . Repetir la consulta hasta que se asignen todos los PV.
Este sigue siendo un algoritmo simplista , aunque. Mirando mi diagrama anterior, esto asigna el edificio 4 al PV 4 y el edificio 5 al PV 5, mientras que 4-5 y 5-4 probablemente serían una mejor solución en general...
Aparte:escriba para dist
columna
Actualmente utiliza numeric
para ello. su consulta original asignó una constante integer
, no tiene sentido hacerlo en numeric
.
En mi nueva consulta ST_Distance()
devuelve la distancia real en metros como double precision
. Si simplemente asignamos eso, obtenemos aproximadamente 15 dígitos fraccionarios en el numeric
tipo de datos, y el número no es ese exacta para empezar. Dudo seriamente que quieras desperdiciar el almacenamiento.
Preferiría guardar la double precision
original del cálculo. o, mejor aún , redondo según sea necesario. Si los metros son lo suficientemente exactos, simplemente convierta y guarde un integer
(redondeando el número automáticamente). O multiplique con 100 primero para ahorrar cm:
(ST_Distance(...) * 100)::int