sql >> Base de Datos >  >> RDS >> PostgreSQL

Asignación única de puntos más cercanos entre dos tablas

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:

Lectura adicional:

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