PostgreSQL
 sql >> Base de Dados >  >> RDS >> PostgreSQL

Atribuição única de pontos mais próximos entre duas tabelas

Esquema de tabela


Para impor sua regra, simplesmente declare pvanlagen.buildid UNIQUE :
ALTER TABLE pvanlagen ADD CONSTRAINT pvanlagen_buildid_uni UNIQUE (buildid);

building.gid é o PK, como sua atualização revelou. Para também impor a integridade referencial, adicione um CHAVE ESTRANGEIRA restrição para buildings.gid .

Você implementou ambos até agora. Mas seria mais eficiente executar o grande UPDATE abaixo antes você adiciona essas restrições.

Há muito mais que deve ser melhorado na definição da sua tabela. Por um lado, buildings.gid bem como pvanlagen.buildid deve ser do tipo inteiro (ou possivelmente bigint se você queimar muito de valores de PK). numérico é um absurdo caro.

Vamos nos concentrar no problema central:

Consulta básica para encontrar o prédio mais próximo


O caso não é tão simples quanto parece. É um "vizinho mais próximo" problema, com a complicação adicional de atribuição única.

Esta consulta encontra o um mais próximo edifício para cada PV (abreviação de PV Anlage - linha em pvanlagen ), onde nenhum é atribuído, ainda:
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 tornar essa consulta rápida, você precisa um índice GiST espacial e funcional em edifícios para torná-lo muito mais rápido:
CREATE INDEX build_centroid_gix ON buildings USING gist (ST_Transform(centroid, 31467));

Não tenho certeza por que você não

Respostas relacionadas com mais explicações:

Leitura adicional:

Com o índice em vigor, não precisamos restringir as correspondências ao mesmo gemname para desempenho. Faça isso apenas se for uma regra real a ser aplicada. Se tiver que ser observado o tempo todo, inclua a coluna na restrição FK:

Problema restante


Podemos usar a consulta acima em um UPDATE declaração. Cada PV é usado apenas uma vez, mas mais de um PV ainda pode encontrar o mesmo prédio estar mais próximo. Você só permite um PV por edifício. Então, como você resolveria isso?

Em outras palavras, como você atribuiria objetos aqui?


Solução simples


Uma solução simples seria:
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;

Eu uso DISTINCT ON (b_gid) para reduzir exatamente um linha por edifício, escolhendo o PV com a menor distância. Detalhes:

Para qualquer edifício que esteja mais próximo de mais um PV, apenas o PV mais próximo é atribuído. A coluna PK gid (alias pv_gid ) serve como desempate se dois estiverem igualmente próximos. Nesse caso, alguns PV são descartados da atualização e permanecem não atribuídos . Repetir a consulta até que todos os PV sejam atribuídos.

Este ainda é um algoritmo simplista , no entanto. Olhando para o meu diagrama acima, isso atribui o edifício 4 ao PV 4 e o edifício 5 ao PV 5, enquanto 4-5 e 5-4 provavelmente seriam uma solução melhor em geral ...

Aparte:digite para dist coluna


Atualmente você usa numérico por isso. sua consulta original atribuiu uma constante integer , não adianta fazer em numérico .

Na minha nova consulta ST_Distance() retorna a distância real em metros como double precisão . Se simplesmente atribuirmos isso, obteremos 15 ou mais dígitos fracionários no numérico tipo de dados e o número não é esse exato para começar. Eu duvido seriamente que você queira desperdiçar o armazenamento.

Prefiro salvar a precisão dupla original a partir do cálculo. ou melhor ainda , redondo conforme necessário. Se os medidores forem exatos o suficiente, apenas converta e salve um integer (arredondando o número automaticamente). Ou multiplique por 100 primeiro para economizar cm:
(ST_Distance(...) * 100)::int