Подтвердить что ты не робот

Oracle: как "группировать" по диапазону?

Если у меня есть таблица вроде этого:

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-специфические подходы.

4b9b3361

Ответ 1

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

Ответ 2

Try:

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);

Ответ 3

Что вы ищете, это в основном данные для histogram.

У вас будет возраст (или возрастный диапазон) по оси 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

Нам также нужна верхняя граница каждого диапазона, которая имеет нижнюю границу + 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 для его подхода; Я только объяснил детали.

Ответ 4

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

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;

Ответ 5

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

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

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

Ответ 6

вместо этого добавьте таблицу 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 и т.д., но я оставлю это вам.

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

Ответ 7

Если вы используете 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

Ответ 8

Мне приходилось собирать количество проб днем. Вдохновленный @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
/

Ответ 9

Мой подход:

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