Oracle: comment" Grouper par " sur une gamme?

Si j'ai un tableau comme ceci:

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

je peux "Grouper par" pour obtenir un compte de chaque âge.

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

quelle requête puis-je utiliser pour grouper par tranches d'âge?

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

je suis sur 10gR2, mais je serais intéressé par toute approche 11g spécifique ainsi.

28
demandé sur zb226 2010-03-20 16:17:46

9 réponses

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
52
répondu Einstein 2010-03-20 23:04:43

Essaie:

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);
23
répondu Matthew Flaschen 2010-03-21 00:37:58

ce que vous recherchez, ce sont essentiellement les données d'un histogramme.

Vous avez l'âge (ou l'âge de gamme) sur l'axe des x et le nombre n (ou la fréquence) sur l'axe des ordonnées.

dans la forme la plus simple, on pourrait simplement compter le nombre de chaque valeur d'âge distincte comme vous l'avez déjà décrit:

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

Quand il y a trop de valeurs pour l'axe des x cependant, on peut vouloir créer des groupes (ou clusters ou des seaux). Dans votre case, vous groupez par une gamme constante de 10.

On peut éviter d'écrire un WHEN ... THEN ligne pour chaque rang - il pourrait y en avoir des centaines s'il n'y avait pas l'âge. Au lieu de cela, l'approche de @MatthewFlaschen est préférable pour les raisons mentionnées par @NitinMidha.

maintenant construisons le SQL...

tout d'abord, nous devons diviser les âges en groupes de 10, comme suit:

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

on peut y parvenir en divisant la colonne des âges par 10, puis en calculant le plancher du résultat:

FLOOR(age/10)

"FLOOR renvoie le plus grand entier égal ou inférieur à n" http://docs.oracle.com/cd/E11882_01/server.112/e26088/functions067.htm#SQLRF00643

puis on prend le SQL original et on remplace âge avec cette expression:

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

Ceci est OK, mais on ne peut pas encore voir la portée. Au lieu de cela, nous voyons seulement les valeurs de plancher calculées qui sont 0, 1, 2 ... n.

Pour obtenir la réelle limite inférieure, nous avons besoin de le multiplier avec 10 de nouveau afin d'obtenir 0, 10, 20 ... n:

FLOOR(age/10) * 10

Nous avons aussi besoin de la limite supérieure de chaque plage est inférieur bound + 10-1 ou

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

finalement, nous concaténons les deux en une chaîne comme ceci:

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

Cela crée '0-9', '10-19', '20-29' etc.

maintenant notre SQL ressemble à ceci:

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

enfin, appliquez une commande et de jolis alias de colonne:

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)

toutefois, dans des scénarios plus complexes, ces fourchettes pourraient ne pas être groupées en tranches constantes de taille 10, mais nécessiter un regroupement dynamique. Oracle a des fonctions d'histogramme plus avancées incluses, voir http://docs.oracle.com/cd/E16655_01/server.121/e15858/tgsql_histo.htm#TGSQL366

remerciements à @MatthewFlaschen pour son approche, je n'ai expliqué que les détails.

7
répondu Wintermute 2014-07-18 00:17:33

Voici une solution qui crée une table" range " dans une sous-requête et l'utilise ensuite pour séparer les données de la table principale:

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;
3
répondu Dan 2010-03-20 16:25:11

j'ai dû grouper les données par le nombre de transactions par heure. Je l'ai fait par l'extraction de l'heure de l'horodatage:

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
;

Donner de sortie:

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

comme vous pouvez le voir cela donne un moyen facile de regrouper le nombre d'enregistrements par heure.

2
répondu Clarkey 2017-07-28 14:45:14

ajouter une table age_range et un champ age_range_id à votre table et groupe par cela à la place.

// excuse le DDL mais vous devriez obtenir l'idée

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

// encore une fois excusez le DML mais vous devriez obtenir l'idée

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;

vous pouvez affiner cette idée si vous voulez-ajouter des colonnes from_age to_age dans la table age_range etc-mais je vous laisse cela.

espérons que cela aide :)

1
répondu Jon Black 2010-03-20 13:55:22

si vous utilisez Oracle 9i+, vous être capable d'utiliser le NTILE analytique de la fonction:

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

L'avertissement à NTILE, c'est que vous ne pouvez spécifier que le nombre de partitions, pas les points de rupture eux-mêmes. Donc vous devez spécifier un nombre qui est approprié. IE: avec 100 lignes,NTILE(4) attribuera 25 rangées à chacun des quatre seaux/cloisons. Vous ne pouvez pas nicher fonctions analytiques, donc vous devez les calquer en utilisant les sous-requêtes/sous-requête d'affacturage pour obtenir granularité souhaitée. Sinon, utilisez:

  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
répondu OMG Ponies 2010-03-20 23:35:27

je devais obtenir un nombre d'échantillons par jour. Inspiré par @Clarkey, J'ai utilisé TO_CHAR pour extraire la date de l'échantillon de l'horodatage à un format de date ISO-8601 et j'ai utilisé cela dans le groupe par et pour commander par des clauses. (Plus inspiré, je le poste également ici au cas où il est utile à d'autres.)

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
répondu Kieron Hardy 2014-06-14 07:01:55

Mon approche:

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  
  • j'ai flexibilité dans la définition des gammes de
  • je n'ai pas répéter les gammes de sélectionner et regrouper des clauses
  • mais quelqu'un s'il vous plaît me dire, comment les commander par ordre de grandeur!
0
répondu Ananth N 2017-06-14 09:28:02