在PostgreSQL中执行多个平均查询

时间:2015-04-15 19:42:26

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

我在执行以下操作时遇到了一些问题:我有一个名为条目的数据库表(除了所有意图和用途)除主键外还有3列:valuegps_latgps_long所有这些都是双打。

我的最终目标是能够定义一个网格,比如说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;

这最后的尝试是获得了一堆语法错误,老实说我不知道​​它来自哪里。一般的想法是生成一堆查询,最终计算我关心的值。

如果有人建议如何解决上述任何方法,那将非常感激。

1 个答案:

答案 0 :(得分: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;

<@ is the "contained in" operator for geometric types.

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

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

然后:

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

只有在Postgres 9.2 +中才能获得仅有索引的扫描时,才能将value添加到索引中。

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

这是假设一个平坦的地球(这可能足够你的目的)。如果你想要准确,你将不得不深入研究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)

相关: