sql - 两个表之间最近点的唯一分配

标签 sql postgresql duplicates postgis knn

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

表定义

建筑物:

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 表中添加一个 booleanpv,当 buildings.gid 已分配:

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 行在 234 毫秒的执行时间内受到影响
我哪里错了?

最佳答案

表架构

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

ALTER TABLE pvanlagen ADD CONSTRAINT pvanlagen_buildid_uni UNIQUE (buildid);

building.gid 是 PK,正如您的更新显示的那样。要同时强制执行参照完整性,请添加 FOREIGN KEY constraintbuildings.gid

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

您的表定义还有很多需要改进的地方。其一,buildings.gidpvanlagen.buildid 应该是类型 integer(或者可能是 bigint,如果你燃烧很多 PK 值)。 numeric 是昂贵的废话。

让我们关注核心问题:

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

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

此查询为每个 PV(PV Anlage 的缩写 - pvanlagen 中的行)找到最近的 one 建筑物,但尚未分配任何建筑物:

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。那么您将如何解决这个问题?

换句话说,您将如何在此处分配对象?

Buildings and PV diagram

简单的解决方案

一个简单的解决方案是:

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为了它。您的原始查询分配了一个常量 integer,在 numeric 中毫无意义。

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

我宁愿从计算中保存原始的 double 。或者,更好,根据需要四舍五入。如果米足够精确,只需转换并保存一个 integer(自动四舍五入)。或先乘以 100 以节省 cm:

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

关于sql - 两个表之间最近点的唯一分配,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/34517386/

相关文章:

ruby-on-rails - 出现错误 - 类型 "json"不存在 - 在 rake db 迁移期间在 Postgresql 中

mysql - 首先检查辅助字段来删除重复项

datetime - 如何根据索引列执行pandas drop_duplicates

python pandas 合并两个数据框并处理重复项?

php - 查询 MySQL 数据库

SQL:在没有 INNER JOIN 的情况下比较条目部分的两个表

mysql - SQL: ORDER BY `date` AND START WHERE`value` ="something"?

database - 将 PostgreSQL 数据库复制到另一台服务器

sql - 我该怎么做 Pivot(转置)

sql - 按 ID 选择最近增加值列