Oracle: как «сгруппировать» по диапазону? - PullRequest
30 голосов
/ 20 марта 2010

Если у меня есть такая таблица:

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 ]

56 голосов
/ 20 марта 2010
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
25 голосов
/ 20 марта 2010

Попробуйте:

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);
10 голосов
/ 18 июля 2014

То, что вы ищете, это в основном данные для гистограммы .

Вы бы имели возраст (или возрастной диапазон) на оси X и число n (или частоту) на оси Y.

В простейшей форме можно просто посчитать количество каждого отдельного значения возраста, как вы уже описали:

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

Однако, если для оси x слишком много разных значений, может потребоваться создать группы (или кластеры, или сегменты). В вашем случае вы группируете по постоянному диапазону 10.

Мы можем избежать написания строки WHEN ... THEN для каждого диапазона - их может быть несколько, если речь не идет о возрасте. Вместо этого подход @MatthewFlaschen предпочтительнее по причинам, указанным @ NitinMidha.

Теперь давайте создадим 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 голосов
/ 20 марта 2010

Вот решение, которое создает таблицу «диапазона» в подзапросе, а затем использует ее для разделения данных из основной таблицы:

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;
2 голосов
/ 07 сентября 2012

Мне пришлось сгруппировать данные по количеству транзакций, выполненных за час. Я сделал это, извлекая час из отметки времени:

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

Как вы можете видеть, это дает хороший простой способ сгруппировать количество записей в час.

1 голос
/ 14 июня 2014

Я должен был получить количество образцов по дням. Вдохновленный @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
/
1 голос
/ 21 марта 2010

Если вы используете Oracle 9i +, вы можете использовать аналитическую функцию NTILE :

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
1 голос
/ 20 марта 2010

добавьте таблицу 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;

Вы можете уточнить эту идею, если хотите - добавить столбцы from_age to_age в таблицу age_range и т. Д., - но я оставлю это вам.

надеюсь, это поможет:)

0 голосов
/ 24 апреля 2019

Можете ли вы попробовать следующее решение:

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 
0 голосов
/ 14 июня 2017

Мой подход:

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
  • но кто-нибудь подскажите пожалуйста, как их упорядочить по величине!
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...