sql - 在 PostgreSQL 中执行多个平均查询

标签 sql postgresql stored-procedures gps aggregate-functions

我在执行以下操作时遇到了一些问题:我有一个名为 entries 的数据库表,除了主键之外,它(出于所有意图和目的)还有 3 列:value, gps_lat, gps_long 都是 double 。

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

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

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

最佳答案

仅填充单元格

使用内置函数 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);

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

如果您无论如何都要读取表的大部分内容,则不需要索引,并且运行简单的 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)

相关:

关于sql - 在 PostgreSQL 中执行多个平均查询,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/29659390/

相关文章:

sql - Oracle 查询连接日期

sql - 通过Microsoft Access创建表DDL

ruby - 格式化查询的 Postgres row_to_json 响应

stored-procedures - Firebird 2.1 存储过程连接多行文本

Mysql在一个查询中多次使用存储过程结果

sql - 如何将一列分成两列并按 id 分组

sql - 使用 PostgreSQL 在一个语句中重命名多个列

perl - 如何识别记录是否被找到或创建:class::dbi find_or_create

mysql - 错误代码: 1062 Duplicate entry for key 'PRIMARY'

sql - 如何将临时表作为参数传递到单独的存储过程中