在 PostgreSQL(PostGIS) 中优化 ST_Intersects

Posted

技术标签:

【中文标题】在 PostgreSQL(PostGIS) 中优化 ST_Intersects【英文标题】:Optimizing ST_Intersects in PostgreSQL(PostGIS) 【发布时间】:2015-08-04 02:43:27 【问题描述】:

下面的查询大约需要 15 分钟才能显示结果。我想知道为什么?因为数据?还是几何的顶点?当我尝试使用不同的表(小型 shapefile)进行查询时,它运行得很快。

这是查询。 (感谢Patrick):

WITH hi AS (
  SELECT ps.id, ps.brgy_locat, ps.municipali
  FROM evidensapp_polystructures ps
  JOIN evidensapp_seniangcbr fh ON fh.hazard = 'High'
                                 AND ST_Intersects(fh.geom, ps.geom)
), med AS (
  SELECT ps.id, ps.brgy_locat, ps.municipali
  FROM evidensapp_polystructures ps
  JOIN evidensapp_seniangcbr fh ON fh.hazard = 'Medium'
                                 AND ST_Intersects(fh.geom, ps.geom)
  EXCEPT SELECT * FROM hi
), low AS (
  SELECT ps.id, ps.brgy_locat, ps.municipali
  FROM evidensapp_polystructures ps
  JOIN evidensapp_seniangcbr fh ON fh.hazard = 'Low'
                                 AND ST_Intersects(fh.geom, ps.geom)
  EXCEPT SELECT * FROM hi
  EXCEPT SELECT * FROM med
)
SELECT brgy_locat AS barangay, municipali AS municipality, high, medium, low
FROM (SELECT brgy_locat, municipali, count(*) AS high
      FROM hi
      GROUP BY 1, 2) cnt_hi
FULL JOIN (SELECT brgy_locat, municipali, count(*) AS medium
      FROM med
      GROUP BY 1, 2) cnt_med USING (brgy_locat, municipali)
FULL JOIN (SELECT brgy_locat, municipali, count(*) AS low
      FROM low
      GROUP BY 1, 2) cnt_low USING (brgy_locat, municipali);

PostgreSQL 9.3、PostGIS 2.1.5

Polystructures:包含9847行:

CREATE TABLE evidensapp_polystructures (
  id serial NOT NULL PRIMARY KEY,
  bldg_name character varying(100) NOT NULL,
  bldg_type character varying(50) NOT NULL,
  brgy_locat character varying(50) NOT NULL,
  municipali character varying(50) NOT NULL,
  province character varying(50) NOT NULL,
  geom geometry(MultiPolygon,32651)
);

CREATE INDEX evidensapp_polystructures_geom_id
  ON evidensapp_polystructures USING gist (geom);
ALTER TABLE evidensapp_polystructures CLUSTER ON evidensapp_polystructures_geom_id;

SeniangCBR:只有 6 行,shapefile 大小(如果重要):52,060 KB

CREATE TABLE evidensapp_seniangcbr (
  id serial NOT NULL PRIMARY KEY,
  hazard character varying(16) NOT NULL,
  geom geometry(MultiPolygon,32651)
);

CREATE INDEX evidensapp_seniangcbr_geom_id ON evidensapp_seniangcbr USING gist (geom);
ALTER TABLE evidensapp_seniangcbr CLUSTER ON evidensapp_seniangcbr_geom_id;

使用LayerMapping 实用程序自动将所有数据加载到数据库中,就像我使用Django(GeoDjango) 一样。

EXPLAIN ANALYZE LINK HERE.

我现在没有服务器,我在我的电脑上运行查询。

处理器:Intel(R) Core(TM) i7-4790 CPU @ 3.60GHz(8 个 CPU),~3.6GHz 内存:8192MB RAM 操作系统:Windows 7 64 位

【问题讨论】:

【参考方案1】:

EXPLAIN ANALYZE 输出很难阅读,因为所有字段和函数都被打乱到radio alphabet 中。也就是说,有两点很突出:

    大部分时间都花在ST_Intersects() 函数上,这并不奇怪。 EXCEPT 子句似乎也相当低效。

所以请试试这个,而不是冗长的版本:

SELECT brgy_locat AS barangay, municipali AS municipality,
       sum(CASE max_hz_id WHEN 3 THEN 1 ELSE 0 END) AS high,
       sum(CASE max_hz_id WHEN 2 THEN 1 ELSE 0 END) AS medium,
       sum(CASE max_hz_id WHEN 1 THEN 1 ELSE 0 END) AS low
FROM (
  SELECT ps.id, ps.brgy_locat, ps.municipali,
         max(CASE fh.hazard WHEN 'Low' THEN 1 WHEN 'Medium' THEN 2 WHEN 'High' THEN 3 END) AS max_hz_id
  FROM evidensapp_polystructures ps
  JOIN evidensapp_seniangcbr fh ON ST_Intersects(fh.geom, ps.geom)
  GROUP BY 1, 2, 3
) AS ps_fh
GROUP BY 1, 2;

现在只有一次调用 ST_Intersects(),这可能(希望)比对危险地图子集的三次调用快很多(由于 PostGIS 代码中的内部效率)。

很明显,危险等级字符串被转换为整数范围,便于排序和比较。在内部查询中,根据您的要求选择最大危险值。在主查询中,每个结构的最大值被汇总到它们各自的列中。如果可能,请更改您的表结构以使用这三个整数代码并链接到类标签的帮助表:您的表会变得更小,因此更快,并且可以删除内部查询中的 CASE 语句。或者,添加具有整数代码的列并根据“危险”列更新值。

请注意,这些CASE 语句效率不高(我在上一个答案中使用EXCEPT 子句的原因)。在 PG 9.4 中,引入了关于聚合函数的新 FILTER 子句,这将使查询更快、更易于阅读:

count(id) FILTER (WHERE max_hz_id = 3) AS high

您可能需要考虑升级。

Selamat mula Maynila

【讨论】:

再次感谢!我会考虑升级我的 PostgreSQL。它返回一个错误:ERROR: syntax error at or near "THEN" LINE 2: sum(CASE max_hz_id = 3 THEN 1 ELSE 0 END) AS high, 其实我可以给表格加一列evidensapp_seniangcbr; gridcode,其中 1 表示低,2 表示中,3 表示高。 如果我将使用新添加的列gridcode 而不是hazard 怎么办?它是否使查询更快? 是的,因为CASE 结构相当低效。有兴趣了解此查询的执行情况。 我现在将尝试运行查询。我将我的 Postgre 升级到 9.4。【参考方案2】:

与我的suggested and explained under your related question 类似,我会在外部SELECT 中使用UNION ALL 而不是FULL JOIN

WITH hi AS (
   SELECT ps.brgy_locat, ps.municipali, fh.hazard, count(*) AS ct
   FROM   evidensapp_seniangcbr     fh
   JOIN   evidensapp_polystructures ps ON ST_Intersects(fh.geom, ps.geom)
   WHERE  fh.hazard = 'High'
   GROUP  BY 1, 2, 3
   )
, med AS (
   SELECT ps.brgy_locat, ps.municipali, fh.hazard, count(*) AS ct
   FROM   evidensapp_seniangcbr     fh
   JOIN   evidensapp_polystructures ps ON ST_Intersects(fh.geom, ps.geom)
   LEFT   JOIN hi USING (brgy_locat, municipali)
   WHERE  fh.hazard = 'Medium'
   AND    hi.brgy_locat IS NULL
   GROUP  BY 1, 2, 3
   )
TABLE hi

UNION ALL
TABLE med

UNION ALL
   SELECT ps.brgy_locat, ps.municipali, fh.hazard, count(*) AS ct
   FROM   evidensapp_seniangcbr     fh
   JOIN   evidensapp_polystructures ps ON ST_Intersects(fh.geom, ps.geom)
   LEFT   JOIN hi  USING (brgy_locat, municipali)
   LEFT   JOIN med USING (brgy_locat, municipali)
   WHERE  fh.hazard = 'Low'
   AND    hi.brgy_locat IS NULL
   AND    med.brgy_locat IS NULL
   GROUP BY 1, 2, 3;

这仅考虑具有相同(brgy_locat, municipali) 的每组行的最高危险级别。只有与evidensapp_seniangcbr 中任何相关危险级别的行实际相交的行才会出现在结果中。此外,计数仅计算实际相交的行。 evidensapp_polystructures 中可能有更多行具有相同的(brgy_locat, municipali),只是没有与相同的危险级别相交,因此被忽略。

选择一种标准方法来排除您已经在较低级别的较高危险级别中找到匹配项的行。

Select rows which are not present in other table

LEFT JOIN / IS NULL 应该使用id 上的索引并且在这里表现得很好。肯定比基于整行使用EXCEPT 快,不能使用索引。

索引

您确实不需要需要像另一个答案建议的那样向您的表格添加一个 bounding_box 几何列。 PostGIS 在现代版本中自动使用(索引支持的)边界框比较。 The PostGIS documentation:

此函数调用将自动包含一个边界框 将使用几何上可用的任何索引进行比较。

事实上,我们已经在explain output you posted.看到了索引扫描

您现有的 GiST 索引 evidensapp_polystructures_geom_id 应该可以加快查询速度。另外:索引的名称可能应该是 evidensapp_polystructures_geom_idx

此外,如果您还没有索引,请在 (brgy_locat, municipali) 上创建一个索引:

CREATE INDEX foo_idx ON evidensapp_polystructures (brgy_locat, municipali);

LATERAL 加入的替代方案

由于evidensapp_seniangcbr 中只有 6 行,LATERAL 加入 可能会更快:

WITH hi AS (
   SELECT ps.brgy_locat, ps.municipali, fh.hazard, count(*) AS ct
   FROM   evidensapp_seniangcbr fh
        , LATERAL (
      SELECT ps.brgy_locat, ps.municipali
      FROM   evidensapp_polystructures ps
      WHERE  ST_Intersects(fh.geom, ps.geom)
      ) ps
   WHERE  fh.hazard = 'High'
   GROUP  BY 1, 2, 3
   )
, med AS (
   SELECT ps.brgy_locat, ps.municipali, fh.hazard, count(*) AS ct
   FROM   evidensapp_seniangcbr fh
        , LATERAL (
      SELECT ps.brgy_locat, ps.municipali
      FROM   evidensapp_polystructures ps
      LEFT   JOIN hi USING (brgy_locat, municipali)
      WHERE  hi.brgy_locat IS NULL
      AND    ST_Intersects(fh.geom, ps.geom)
      ) ps
   WHERE  fh.hazard = 'Medium'
   GROUP  BY 1, 2, 3
   )
TABLE hi

UNION ALL
TABLE med

UNION ALL
   SELECT ps.brgy_locat, ps.municipali, fh.hazard, count(*) AS ct
   FROM   evidensapp_seniangcbr fh
        , LATERAL (
      SELECT ps.id, ps.brgy_locat, ps.municipali
      FROM   evidensapp_polystructures ps
      LEFT   JOIN hi  USING (brgy_locat, municipali)
      LEFT   JOIN med USING (brgy_locat, municipali)
      WHERE  hi.brgy_locat IS NULL
      AND    med.brgy_locat IS NULL
      AND    ST_Intersects(fh.geom, ps.geom)
      ) ps
   WHERE  fh.hazard = 'Low'
   GROUP  BY 1, 2, 3;

关于LATERAL加入:

What is the difference between LATERAL and a subquery in PostgreSQL?

【讨论】:

@Sachi:您已经接受了我的回答,我仍然添加了缺少的聚合和计数。我还基于LEFT JOIN 排除(brgy_locat, municipali) 上具有较高危险级别的行,因为我们按它分组。取决于您的要求的详细信息。考虑更新。 在某些情况下,多边形可能有太多需要索引的点。在这些情况下,添加一个额外的 bounding_box 列会很有用。 @caps:在这种情况下当然是个好主意。但这在这里不适用,因为 GiST 索引已经成功创建并且已经在使用中(参见问题)。【参考方案3】:

bounding_box geometry(Polygon,4326) 列添加到您的表中。该列的值将是一个完全封装multipolygon 的边界框(multipolygon 的最大 x,y 和最小 x,y)。

那么您的查询将如下所示:

AND ST_Intersects(fh.bounding_box, ps.bounding_box)
AND ST_Intersects(fh.geom, ps.geom)

这样做的好处是第一个ST_Intersects 调用非常快。如果它返回 false,则永远不会调用第二个更复杂的 ST_Intersects 调用,从而在这种情况下为您节省一些时间。

【讨论】:

由于我是新手,您能详细说明一下添加边界框吗? 要添加一个边界框,您需要计算 4 个点并从中创建一个多边形。这些点是((max_y, max_x), (max_y, min_x), (min_y, min_x), (min_y, max_x)),其中max_y 是多边形中最大的Y,min_y 是多边形中最小的Y,等等。

以上是关于在 PostgreSQL(PostGIS) 中优化 ST_Intersects的主要内容,如果未能解决你的问题,请参考以下文章

在java中存储PostgreSQL/PostGIS“几何(多多边形)”数据类型

我想创建一个用于地理处理的桌面基础应用程序并将结果存储在 PostgreSQL(postgis)中!

postgresql 和postgis区别是啥?

postgresql 和postgis区别是啥?

postgresql与postgis结合示例

带有 Postgis Geodjango 安装的 Postgresql