【问题标题】:Unique assignment of closest points between two tables两个表之间最近点的唯一分配
【发布时间】:2015-12-29 19:04:27
【问题描述】:

在安装了 PostGis 2.2.0Postgres 9.5 数据库中,我有两张带有几何数据(点)的表,我想将一张表中的点分配给来自另一张表的点,但我不希望 buildings.gid 被分配两次。一旦分配了一个buildings.gid,就不应将其分配给另一个pvanlagen.buildid

表定义

buildings:

CREATE TABLE public.buildings (
  gid numeric NOT NULL DEFAULT nextval('buildings_gid_seq'::regclass),
  osm_id character varying(11),
  name character varying(48),
  type character varying(16),
  geom geometry(MultiPolygon,4326),
  centroid geometry(Point,4326),
  gembez character varying(50),
  gemname character varying(50),
  krsbez character varying(50),
  krsname character varying(50),
  pv boolean,
  gr numeric,
  capac numeric,
  instdate date,
  pvid numeric,
  dist numeric,
  CONSTRAINT buildings_pkey PRIMARY KEY (gid)
);

CREATE INDEX build_centroid_gix
  ON public.buildings
  USING gist
  (st_transform(centroid, 31467));

CREATE INDEX buildings_geom_idx
  ON public.buildings
  USING gist
  (geom);

pvanlagen:

CREATE TABLE public.pvanlagen (
  gid integer NOT NULL DEFAULT nextval('pv_bis2010_bayern_wgs84_gid_seq'::regclass),
  tso character varying(254),
  tso_number numeric(10,0),
  system_ope character varying(254),
  system_key character varying(254),
  location character varying(254),
  postal_cod numeric(10,0),
  street character varying(254),
  capacity numeric,
  voltage_le character varying(254),
  energy_sou character varying(254),
  beginning_ date,
  end_operat character varying(254),
  id numeric(10,0),
  kkz numeric(10,0),
  geom geometry(Point,4326),
  gembez character varying(50),
  gemname character varying(50),
  krsbez character varying(50),
  krsname character varying(50),
  buildid numeric,
  dist numeric,
  trans boolean,
  CONSTRAINT pv_bis2010_bayern_wgs84_pkey PRIMARY KEY (gid),
  CONSTRAINT pvanlagen_buildid_fkey FOREIGN KEY (buildid)
      REFERENCES public.buildings (gid) MATCH SIMPLE
      ON UPDATE NO ACTION ON DELETE NO ACTION,
  CONSTRAINT pvanlagen_buildid_uni UNIQUE (buildid)
);

CREATE INDEX pv_bis2010_bayern_wgs84_geom_idx
  ON public.pvanlagen
  USING gist
  (geom);

查询

我的想法是在分配buildings.gid 时设置的buildings 表中添加booleanpv

UPDATE pvanlagen 
SET buildid=buildings.gid, dist='50'
FROM buildings
WHERE buildid IS NULL 
AND buildings.pv is NULL
AND pvanlagen.gemname=buildings.gemname 
AND ST_Distance(ST_Transform(pvanlagen.geom,31467)
               ,ST_Transform(buildings.centroid,31467))<50;

UPDATE buildings 
SET pv=true
FROM pvanlagen
WHERE buildings.gid=pvanlagen.buildid;

我在buildings 中测试了 50 行,但申请所有这些行的时间太长。我有 3.200.000 个建筑物260.000 个 PV

应分配最近建筑物的gid。如果在平局的情况下,分配哪个gid 无关紧要。如果我们需要制定规则,我们可以取gid较低的建筑物。

50 米是作为一个限制。我使用了ST_Distance(),因为它返回的最小距离应该在 50 米以内。后来我多次提出,直到每个PV Anlage都被分配。

建筑物和 PV 被分配到各自的区域 (gemname)。这应该会使分配更便宜,因为我知道最近的建筑物必须在同一区域内 (gemname)。

我在以下反馈后尝试了此查询:

UPDATE pvanlagen p1
SET    buildid = buildings.gid
 , dist = buildings.dist  
FROM (
   SELECT DISTINCT ON (b.gid)
          p.id, b.gid, b.dist::numeric  
   FROM  (
      SELECT id, ST_Transform(geom, 31467) 
      FROM   pvanlagen
      WHERE  buildid IS NULL  -- not assigned yet
      ) p
        , LATERAL (
      SELECT b.gid, ST_Distance(ST_Transform(p1.geom, 31467), ST_Transform(b.centroid, 31467)) AS dist
      FROM   buildings      b
      LEFT   JOIN pvanlagen p1 ON p1.buildid = b.gid  
      WHERE  p1.buildid IS NULL                        
      AND    b.gemname = p1.gemname
      ORDER  BY ST_Transform(p1.geom, 31467) <-> ST_Transform(b.centroid, 31467)
      LIMIT  1
            ) b
       ORDER  BY b.gid, b.dist, p.id  -- tie breaker
       ) x, buildings
 WHERE   p1.id = x.id;

但它返回0 rows affected in 234 ms execution time
我哪里错了?

【问题讨论】:

  • @ErwinBrandstetter 表定义已更新。再次感谢您的帮助!
  • 考虑更新的解决方案。
  • @ErwinBrandstetter 新查询运行,但它设置了buildid=sub.pv_gid,这实际上是 pvanlage 本身的确切 gid。不应该是SET build.id=sub.b_gid吗?
  • 是的,这是一个错字。已修复,谢谢。

标签: sql postgresql duplicates postgis knn


【解决方案1】:

表架构

要执行您的规则,只需声明 pvanlagen.buildid UNIQUE

ALTER TABLE pvanlagen ADD CONSTRAINT pvanlagen_buildid_uni UNIQUE (buildid);

building.gid 是 PK,正如您的更新所揭示的。要同时强制引用完整性,请将 FOREIGN KEY constraint 添加到 buildings.gid

您现在已经实现了这两个。但是在您添加这些约束之前运行下面的大UPDATE 会更有效。

您的表格定义还有很多需要改进的地方。一方面,buildings.gidpvanlagen.buildid 应该输入 integer(或者如果您烧掉 很多 PK 值,则可能是 bigint)。 numeric 是昂贵的废话。

让我们关注核心问题:

查找最近建筑物的基本查询

案件并不像看起来那么简单。这是一个"nearest neighbour" 问题,具有唯一分配的额外复杂性。

此查询为每个 PV 找到最近的 一个 建筑物(PV Anlage 的缩写 - pvanlagen 中的行),但两者均未分配:

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;

为了使这个查询更快,您需要buildings 上建立一个空间、功能性 GiST 索引以使其更多 更快:

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

不确定为什么你不这样做

更多解释的相关答案:

进一步阅读:

有了索引,我们不需要将匹配限制为相同的gemname 以提高性能。仅当这是要强制执行的实际规则时才这样做。如果必须始终观察,请在 FK 约束中包含该列:

剩下的问题

我们可以在UPDATE 语句中使用上面的查询。每个 PV 仅使用一次,但多个 PV 可能仍会找到最接近的同一建筑物。每个建筑物只允许 一个 PV。那你会怎么解决呢?

换句话说,你将如何在这里分配对象?

简单的解决方案

一个简单的解决方案是:

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;

我使用DISTINCT ON (b_gid) 将每栋建筑物精确地减少到 一个 行,选择距离最短的 PV。详情:

对于任何离多个 PV 最近的建筑物,仅分配最近的 PV。 PK 列gid(别名pv_gid)作为决胜局,如果两个同样接近。在这种情况下,一些 PV 会从更新中删除并保持未分配重复查询,直到分配所有 PV。

不过,这仍然是一个简单的算法。看我上面的图表,这将建筑物 4 分配给 PV 4,将建筑物 5 分配给 PV 5,而整体而言,4-5 和 5-4 可能是一个更好的解决方案......

旁白:输入dist

目前您使用numeric。您的原始查询分配了一个常量integernumeric 没有意义。

在我的新查询ST_Distance() 中,以米为单位的实际距离返回为double precision。如果我们简单地分配,我们会在 numeric 数据类型中得到 15 个左右的小数位,而这个数字并不是 那个 确切的开头。我严重怀疑你想浪费存储空间。

我宁愿从计算中保存原始的double precision。或者,更好,根据需要进行舍入。如果米数足够准确,只需投射并保存integer(自动将数字四舍五入)。或先乘以 100 以节省 cm:

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

【讨论】:

  • @ErwinBrandstetter,+1 以获得更详细的答案。
  • 查询有效,但效率呈对数曲线。首次运行 158376 行受影响,执行时间 02:40:7247 小时。当前运行(第 16 次运行)984 行受影响,执行时间 06:12:21648 小时(现在总共执行时间 72 小时)。剩下 9788 个未分配的行。我已经在使用您建议的 GiST 索引。关于如何固定东西的任何想法?最后我会用buildid NOT NULL AND dist&gt;1000 切断 PV Anlagen。上次运行 984 中的 835 有 dist&gt;1000。我们可以在查询中考虑这一点来解决问题吗?
  • @NewbieNeedsHelp:这些数字似乎方式太慢了。您可能需要配置您的数据库服务器(除其他外?)。更多work_mem 将是我的第一个猜测。您可能会在 dba.SE 上发布另一个关于性能的问题,但请考虑 instructions for postgresql performance questions first
猜你喜欢
  • 2013-08-08
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-03-03
  • 2021-03-31
  • 2019-09-22
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多