0

我有一个具有以下结构的表

|user_id | place | type_of_place | money_earned| time |
|--------+-------+---------------+-------------+------|
|        |       |               |             |      |

该表非常大,有几百万行。数据位于 PostgreSQL 9.1 数据库中。

我想根据 user_id 和 type_of_place 计算:平均值、标准差和前 5 个位置(按计数排序),以及最常用的时间(模式)。

结果数据必须采用以下形式:

| user_id | type_of_place | avg | stddev |   top5_places    | mode |
+---------+---------------+-----+--------+------------------+------+
|     1   |      tp1      | 10  |   1    | {p1,p2,p3,p4,p5} |   8  |
|     2   |      tp1      |  3  |   2    | {p3,p4}          |   23 |
|     1   |      tp3      |  1  |   1    | {p1}             |   4  |

等等

有没有一种方法可以有效地使用窗口函数?

如果我想按周分组怎么办?(即代表周数的另一列)

谢谢!

4

1 回答 1

1

标准的 GROUP BY 查询将为您提供大部分帮助:

SELECT
    user_id,
    type_of_place,
    avg(money_earned) AS avg,
    stddev(money_earned) AS stddev
FROM
    earnings  -- I'm not sure what your data table is called...
GROUP BY
    user_id,
    type_of_place

这留下了top5_placesmode列。这些也是聚合,但不是标准 PostgreSQL 安装中定义的聚合。幸运的是,您可以添加它们。

这是一个讨论如何定义mode聚合函数的页面: http ://wiki.postgresql.org/wiki/Aggregate_Mode

一旦你有了一个mode聚合函数,假设time是某种时间戳,你将添加到选择列表中的表达式将是:

SELECT
    ...
    mode(extract(hour FROM time)) AS mode  -- Add this expression
FROM
    ...

假设按钱订购

对于top5_places,有几种方法,但最快的可能是使用 PostgreSQL 的内置array_agg函数,并取前 5 个元素:

SELECT
    ...
    (array_agg(place ORDER BY money_earned DESC))[1:5] AS top5_places  -- Add this expression
FROM
    ...

一种替代方法是定义另一个名为 (for instance) 的聚合top5,它执行相同的功能。如果每个用户/地点类型的组合有许多不同的地点,这可能会更有效,因为它可以在前 5 个之后停止累积,而上面的表达式通常会构建所有地点的完整数组,然后截断到第一个5.

这假设一个地方对于每个用户/类型组合都有一个唯一的收入条目。 如果一个地方可以出现多次,并且您想sum(money_earned)为每个地方排序,那么您需要使用如下示例中的子查询...

按数量排序

好的,所以这些地方应该按照它们出现的频率来排序。这是一种使用几个子查询的快速方法——将其作为表达式添加到上述查询的选择子句中:

(SELECT
    (array_agg(place ORDER BY cnt DESC))[1:5]
FROM
    (SELECT place, count(*) FROM earnings AS t2
     WHERE t2.user_id = earnings.user_id AND t2.type_of_place = earnings.type_of_place
     GROUP BY place) AS s (place, cnt)
) AS top5_places

调用的内部子查询s为每个用户/类型组合计算一个表place,以及它出现的次数(我称之为cnt)。array_agg然后按该计数的降序将这些输入。

我怀疑可能会有更简洁(并且可能更有效)的编写方式。如果没有,那么我建议您尝试将这个复杂的表达式移动到函数或聚合中,如果可以的话......

每小时地点的直方图

我们将使用类似的表达式,它将返回按小时排序的计数数组:

(SELECT
    array_agg(cnt ORDER BY hour DESC)
FROM
    (SELECT extract(hour FROM time), count(*) FROM earnings AS t2
     WHERE t2.user_id = earnings.user_id AND t2.type_of_place = earnings.type_of_place
     GROUP BY 1) AS s (hour, cnt)
) AS hourly_histogram

(将其添加到原始查询的选择子句中。)

于 2012-11-13T01:32:47.503 回答