2012-03-13 56 views
8

考虑:SQL频率分布查询数与组的范围,包括0计数

table 'thing': 

age 
--- 
3.4 
3.4 
10.1 
40 
45 
49 

我要计算的东西,每10年范围内的数量,例如,

age_range | count 
----------+------- 
     0 |  2 
     10|  1 
     20|  0 
     30|  0 
     40|  3 

这个查询接近:

SELECT FLOOR(age/10) as age_range, COUNT(*) 
FROM thing 
GROUP BY FLOOR(age/10) ORDER BY FLOOR(age/10); 

输出:

age_range | count 
-----------+------- 
     0 |  1 
     1 |  2 
     4 |  3 

但是,它没有显示出具有0计数的范围。我怎样才能修改查询,以便它也显示了0计数之间的范围?

我发现了类似的计算范围的问题,一些为0计数,但它们涉及必须指定每个范围(或者将范围硬编码到查询中,或者将范围放在表中)。我宁愿使用上面的通用查询,我不必明确指定每个范围(例如0-10,10-20,20-30,...)。我使用的是PostgreSQL 9.1.3。

有没有办法修改上面的简单查询以包含0计数?

类似:
Oracle: how to "group by" over a range?
Get frequency distribution of a decimal range in MySQL

回答

10

generate_series救援:

select 10 * s.d, count(t.age) 
from generate_series(0, 10) s(d) 
left outer join thing t on s.d = floor(t.age/10) 
group by s.d 
order by s.d 

搞清楚上限generate_series应该是微不足道的一个单独的查询,我只是用10作为占位符。

此:

generate_series(0, 10) s(d) 

基本上产生与其中包含的值从0至10(含)的单个列d称为s内嵌表。

如果需要,可以将两个查询(一个计算出范围,一个计算计数)包装到一个函数中。

+0

这很好。好的功能知道。 – Glenn 2012-03-13 02:46:05

+0

优雅的答案,@ mu_is_too_short!我试过了,它工作。正是我在找的东西。谢谢! – 2012-03-13 02:47:03

+0

@Glenn:是的,'generate_series'非常有用,它也可以处理时间戳,所以再见日历表。 – 2012-03-13 05:44:53

1

您需要一些方法来发明年龄段的表格。行号通常很好地工作。对一张大桌子做一个笛卡尔产品以获取大量数字。

WITH RANGES AS (
SELECT (rownum - 1) * 10 AS age_range 
    FROM (SELECT row_number() OVER() as rownum 
      FROM pg_tables 
     ) n 
     ,(SELECT ceil(max(age)/10) range_end 
      FROM thing 
     ) m 
    WHERE n. rownum <= range_end 
) 
SELECT r.age_range, COUNT(t.age) AS count 
    FROM ranges r 
    LEFT JOIN thing t ON r.age_range = FLOOR(t.age/10) * 10 
    GROUP BY r.age_range 
    ORDER BY r.age_range; 

编辑:亩太短有一个更优雅的答案,但如果你没有在一个数据库中的generate_series功能,... :)