【问题标题】:Performing Multiple Avg Queries in PostgreSQL在 PostgreSQL 中执行多个平均查询
【发布时间】:2015-04-15 19:42:25
【问题描述】:

我在执行以下操作时遇到了一些问题:我有一个名为 entries 的数据库表(出于所有意图和目的),除了主键之外还有 3 列:valuegps_latgps_long all其中是双打。

我的最终目标是能够定义一个网格,比如 100x100 的间隔,并以给定的纬度和经度值为界,对于网格的每个正方形,我想计算该网格中所有点的平均值正方形。但是,我在有效地执行此操作时遇到了很多麻烦。

部分问题是我想将其设置为存储过程或查询,我可以使用一段代码生成并在以后重用,因为每次运行查询时,网格都不相同(所以缓存几乎是不可能的)。

我第一次尝试这样做是定义以下函数:

CREATE OR REPLACE FUNCTION gridSquareAverageValue (double precision
             , double precision, double precision, double precision)
RETURNS double precision as $avgValue$
declare
    avgValue double precision;
BEGIN
    SELECT AVG(value) into avgValue FROM entries
    WHERE gps_lat BETWEEN $1 AND $2 AND gps_long BETWEEN $3 AND $4;
    RETURN avgValue;
END;
$avgValue$ LANGUAGE plpgsql;

这个函数工作得很好,完全符合我的需要,除了它只对一个方格进行。为 100x100 网格运行该函数涉及 10,000 个单独的查询,因此速度非常慢。

下一次尝试是这样的:

WITH Grid(lat_offset,long_offset) AS
(SELECT *
 FROM       generate_series(1,10) lat_offset
 CROSS JOIN generate_series(1,10) long_offset)
SELECT AVG(value)
FROM Grid 
JOIN entries 
ON entries.gps_lat BETWEEN 41.79604807005128 + (0.000247908106797 * Grid.lat_offset)
                       AND 41.82083888073101 + (0.002479081067973 * (Grid.lat_offset + 1))
AND entries.gps_long BETWEEN -72.2759199142456 + (0.000527858734131 * Grid.long_offset)
                         AND -72.22313404083252 + (0.005278587341308 * (Grid.long_offset + 1))
GROUP BY lat_offset,long_offset;

不知何故,结果变得更糟了。我尝试生成一系列偏移量,然后将其与条目表连接起来,强制每个条目进入一个使用您可以在上面看到的数学计算的框中。这是不可能的慢。我试图让它只输出值而不计算平均值,它比运行 10k 个单独的查询花费的时间更长。

上述方法也可能是最有前途的方法,因为在生成两个系列的笛卡尔连接后,我真正想做的就是在一个简单的函数中使用它们,但除了你的方法之外,我想不出任何体面的方法来做到这一点见上文 =/

最后我尝试了这个:

#                                           $1 height $2 width $3 lat start      $4 lat interval   $5 long start      $6 long interval
CREATE OR REPLACE FUNCTION gridAverageValue (integer,  integer, double precision, double precision, double precision, double precision)
RETURNS TABLE (avg double precision) as $restbl$
BEGIN
    SELECT * INTO $restbl$ FROM entries WHERE 1 = 2;
    FOR lat_offset IN 0..$1 LOOP
        FOR long_offset IN 0..$2 LOOP
            INSERT INTO restbl 
            SELECT AVG(value) 
            FROM entries 
            WHERE gps_lat 
            BETWEEN $3 + ($4 * lat_offset) AND $3 + ($4 * (lat_offset + 1)) 
            AND gps_long 
            BETWEEN $5 + ($6 * long_offset) AND $5 + ($6 * (long_offset + 1));
        END LOOP;
    END LOOP;
    RETURN QUERY SELECT * FROM restbl;
END;
$restbl$ LANGUAGE plpgsql;

最后一次尝试遇到了一堆语法错误,老实说,我不知道它来自哪里。总体思路是生成一堆查询,最终计算出我关心的值。

如果有人对如何解决上述任何方法有任何建议,将不胜感激。

【问题讨论】:

标签: sql postgresql stored-procedures gps aggregate-functions


【解决方案1】:

仅填充单元格

使用内置函数width_bucket() 仅获取entries 中具有一个或多个匹配行的网格单元格:

对于box(point(_lat_start, _long_start), point(_lat_end, _long_end)) 外框内的 100 x 100 单元格的网格:

SELECT width_bucket(gps_lat , _lat_start , _lat_end , 100) AS grid_lat
     , width_bucket(gps_long, _long_start, _long_end, 100) AS grid_long
     , avg(value) AS avg_val
FROM   entries
WHERE  point(gps_lat, gps_long) <@ box(point(_lat_start, _long_start)
                                     , point(_lat_end  , _long_end))
GROUP  BY 1,2
ORDER  BY 1,2;

&lt;@ is the "contained in" operator for geometric types.

很容易将其包装成一个函数,并参数化外框和网格单元的数量。

多列 GiST 表达式 index 将有助于提高性能如果只有一小部分行位于外框内。您需要先安装 btree_gist 模块,每个数据库一次:

然后:

CREATE INDEX entries_point_idx ON entries
USING gist (point(gps_lat, gps_long), value);

仅当您可以在 Postgres 9.2+ 中获得仅索引扫描时,将 value 添加到索引才有意义。

如果您仍然要读取表的大部分内容,则不需要索引,并且在 WHERE 子句中运行简单的 a between x and y 检查可能会更便宜。

这是假设一个平坦的地球(这可能足以满足您的目的)。如果你想准确,你将不得不深入挖掘PostGIS

网格中的所有单元格

要让 所有 单元格使用 LEFT JOIN 到您已经尝试过的预生成网格:

SELECT grid_lat, grid_long, g.avg_val  -- or use COALESCE
FROM        generate_series(1,100) grid_lat
CROSS  JOIN generate_series(1,100) grid_long
LEFT   JOIN (<query from above>) g USING (grid_lat, grid_long)

相关:

【讨论】:

  • 这看起来有点像我想去的方向,这对我很有帮助。我唯一担心的是,当我尝试调用您建议的第一个查询(带有
  • @RedHack:啊,对,当我从 box literal 切换到由参数构造的 box 时,我忘记添加 point() 函数。尝试更新。它应该开箱即用(没有双关语)。
  • 那是为了帮助,但我想我可能会误解它试图做什么,因为当我运行它时,无论我制作多大的盒子(即我确定那里有点) 但它总是什么也不返回。可能是我对盒子边界的误解造成的:我的印象是 lat_start 和 long_start 应该小于 lat_end 和 long_end。这是正确的吗?
  • @RedHack:是的,正确的。我还有一个错字:point(gps_lat, gps_lat) 而不是 ... long),这已经修复了,你没有偶然发现,是吗?
  • 不,我抓住了那个,但我混淆了负经度开始/结束的顺序并修复了它。再次感谢您,这看起来正是我所需要的。现在(再次抱歉,我问了太多问题,但如果你有时间的话:)我想问一下关于所有细胞的最后陈述。 所有单元是什么意思?您是指数据库中所有条目可能生成的所有单元格吗?
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-08-02
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多