甲骨文:如何在一个范围内“分组”?

时间:2010-03-20 13:17:47

标签: sql oracle plsql oracle10g

如果我有这样的表:

pkey   age
----   ---
   1     8
   2     5
   3    12
   4    12
   5    22

我可以“分组”来计算每个年龄。

select age,count(*) n from tbl group by age;
age  n
---  -
  5  1
  8  1
 12  2
 22  1

我可以使用哪些查询按年龄段进行分组?

  age  n
-----  -
 1-10  2
11-20  2
20+    1

我在10gR2上,但我也对任何11g特定的方法感兴趣。

10 个答案:

答案 0 :(得分:56)

SELECT CASE 
         WHEN age <= 10 THEN '1-10' 
         WHEN age <= 20 THEN '11-20' 
         ELSE '21+' 
       END AS age, 
       COUNT(*) AS n
FROM age
GROUP BY CASE 
           WHEN age <= 10 THEN '1-10' 
           WHEN age <= 20 THEN '11-20' 
           ELSE '21+' 
         END

答案 1 :(得分:25)

尝试:

select to_char(floor(age/10) * 10) || '-' 
|| to_char(ceil(age/10) * 10 - 1)) as age, 
count(*) as n from tbl group by floor(age/10);

答案 2 :(得分:10)

您正在寻找的内容,基本上是histogram的数据。

您将在x轴上具有年龄(或年龄范围),在y轴上具有计数n(或频率)。

在最简单的形式中,人们可以像您已经描述的那样简单地计算每个不同年龄值的数量:

SELECT age, count(*)
FROM tbl
GROUP BY age

但是,当x轴的值不同时,可能需要创建组(或群集或存储桶)。在您的情况下,您按10的恒定范围分组。

我们可以避免为每个范围编写WHEN ... THEN行 - 如果不是年龄,可能会有数百行。相反,出于@NitinMidha提到的原因,@ MatthewFlaschen采用的方法更为可取。

现在让我们构建SQL ...

首先,我们需要将年龄分成10个范围组,如下所示:

  • 0-9
  • 10-19
  • 20 - 29

这可以通过将年龄列除以10,然后计算结果的FLOOR:

来实现
FLOOR(age/10)

“FLOOR返回等于或小于n的最大整数” http://docs.oracle.com/cd/E11882_01/server.112/e26088/functions067.htm#SQLRF00643

然后我们采用原始SQL并将 age 替换为该表达式:

SELECT FLOOR(age/10), count(*)
FROM tbl
GROUP BY FLOOR(age/10)

这没关系,但我们还看不到范围。相反,我们只会看到计算的楼层值0, 1, 2 ... n

要获得实际的下限,我们需要将其再次乘以10,以便得到0, 10, 20 ... n

FLOOR(age/10) * 10

我们还需要每个范围的上限 bound + 10 - 1

FLOOR(age/10) * 10 + 10 - 1

最后,我们将两者连接成一个这样的字符串:

TO_CHAR(FLOOR(age/10) * 10) || '-' || TO_CHAR(FLOOR(age/10) * 10 + 10 - 1)

这会创建'0-9', '10-19', '20-29'等。

现在我们的SQL看起来像这样:

SELECT 
TO_CHAR(FLOOR(age/10) * 10) || ' - ' || TO_CHAR(FLOOR(age/10) * 10 + 10 - 1),
COUNT(*)
FROM tbl
GROUP BY FLOOR(age/10)

最后,应用订单和漂亮的列别名:

SELECT 
TO_CHAR(FLOOR(age/10) * 10) || ' - ' || TO_CHAR(FLOOR(age/10) * 10 + 10 - 1) AS range,
COUNT(*) AS frequency
FROM tbl
GROUP BY FLOOR(age/10)
ORDER BY FLOOR(age/10)

但是,在更复杂的情况下,这些范围可能不会被分组为大小为10的常量块,但需要动态聚类。 Oracle包含更高级的直方图函数,请参阅http://docs.oracle.com/cd/E16655_01/server.121/e15858/tgsql_histo.htm#TGSQL366

致@MatthewFlaschen的态度;我只解释了细节。

答案 3 :(得分:3)

这是一个解决方案,它在子查询中创建“范围”表,然后使用它来对主表中的数据进行分区:

SELECT DISTINCT descr
  , COUNT(*) OVER (PARTITION BY descr) n
FROM age_table INNER JOIN (
  select '1-10' descr, 1 rng_start, 10 rng_stop from dual
  union (
  select '11-20', 11, 20 from dual
  ) union (
  select '20+', 21, null from dual
)) ON age BETWEEN nvl(rng_start, age) AND nvl(rng_stop, age)
ORDER BY descr;

答案 4 :(得分:2)

我不得不按一小时内出现的交易数量对数据进行分组。我是通过从时间戳中提取小时来完成的:

select extract(hour from transaction_time) as hour
      ,count(*)
from   table
where  transaction_date='01-jan-2000'
group by
       extract(hour from transaction_time)
order by
       extract(hour from transaction_time) asc
;

给出输出:

HOUR COUNT(*)
---- --------
   1     9199 
   2     9167 
   3     9997 
   4     7218

正如您所看到的,这提供了一种简单的方法来分组每小时的记录数。

答案 5 :(得分:1)

将age_range表和age_range_id字段添加到您的表和组中。

//请原谅DDL,但你应该明白这个想法

create table age_range(
age_range_id tinyint unsigned not null primary key,
name varchar(255) not null);

insert into age_range values 
(1, '18-24'),(2, '25-34'),(3, '35-44'),(4, '45-54'),(5, '55-64');

//再次原谅DML,但你应该明白

select
 count(*) as counter, p.age_range_id, ar.name
from
  person p
inner join age_range ar on p.age_range_id = ar.age_range_id
group by
  p.age_range_id, ar.name order by counter desc;

如果您愿意,可以改进这个想法 - 在age_range表中添加from_age to_age列等 - 但我会留给您。

希望这有助于:)

答案 6 :(得分:1)

如果使用Oracle 9i +,可能可以使用NTILE analytic function

WITH tiles AS (
  SELECT t.age,
         NTILE(3) OVER (ORDER BY t.age) AS tile
    FROM TABLE t)
  SELECT MIN(t.age) AS min_age,
         MAX(t.age) AS max_age,
         COUNT(t.tile) As n
    FROM tiles t
GROUP BY t.tile

对NTILE的警告是,您只能指定分区数,而不能指定断点本身。所以你需要指定一个合适的数字。 IE:对于100行,NTILE(4)将为四个桶/分区中的每一个分配25行。您无法嵌套分析函数,因此您必须使用子查询/子查询因子对它们进行分层以获得所需的粒度。否则,请使用:

  SELECT CASE t.age
           WHEN BETWEEN 1 AND 10 THEN '1-10' 
           WHEN BETWEEN 11 AND 20 THEN '11-20' 
           ELSE '21+' 
         END AS age, 
         COUNT(*) AS n
    FROM TABLE t
GROUP BY CASE t.age
           WHEN BETWEEN 1 AND 10 THEN '1-10' 
           WHEN BETWEEN 11 AND 20 THEN '11-20' 
           ELSE '21+' 
         END

答案 7 :(得分:1)

我必须白天得到一些样本。受@Clarkey的启发,我使用TO_CHAR将样本日期从时间戳提取为ISO-8601日期格式,并在GROUP BY和ORDER BY子句中使用它。 (进一步的启发,我也在这里发布,以防其他人有用。)

SELECT 
  TO_CHAR(X.TS_TIMESTAMP, 'YYYY-MM-DD') AS TS_DAY, 
  COUNT(*) 
FROM   
  TABLE X
GROUP BY
  TO_CHAR(X.TS_TIMESTAMP, 'YYYY-MM-DD')
ORDER BY
  TO_CHAR(X.TS_TIMESTAMP, 'YYYY-MM-DD') ASC
/

答案 8 :(得分:0)

我的方法:

select range, count(1) from (
select case 
  when age < 5 then '0-4' 
  when age < 10 then '5-9' 
  when age < 15 then '10-14' 
  when age < 20 then '15-20' 
  when age < 30 then '21-30' 
  when age < 40 then '31-40' 
  when age < 50 then '41-50' 
  else                '51+' 
end 
as range from
(select round(extract(day from feedback_update_time - feedback_time), 1) as age
from txn_history
) ) group by range  
  • 我可以灵活定义范围
  • 我不重复select和group子句中的范围
  • 但有些人请告诉我,如何按规模订购它们!

答案 9 :(得分:0)

您可以尝试以下解决方案吗?

SELECT count (1), '1-10'  where age between 1 and 10
union all 
SELECT count (1), '11-20'  where age between 11 and 20
union all
select count (1), '21+' where age >20
from age