web-dev-qa-db-fra.com

Oracle: comment "grouper par" sur une plage?

Si j'ai une table comme celle-ci:

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

Je peux "grouper par" pour obtenir un dé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 regrouper par tranche d'âge?

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

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

29
Mark Harrison
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
55
Einstein

Essayer:

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);
25
Matthew Flaschen

Ce que vous recherchez, ce sont essentiellement les données pour un histogramme .

Vous auriez l’âge (ou la tranche d’âge) sur l’axe des x et le nombre n (ou la fréquence) sur l’axe des y.

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

Cependant, quand il y a trop de valeurs différentes pour l'axe des x, on peut vouloir créer des groupes (ou des clusters ou des compartiments). Dans votre cas, vous regroupez par une plage constante de 10.

Nous pouvons éviter d'écrire une ligne WHEN ... THEN pour chaque plage - il pourrait y en avoir des centaines s'il ne s'agissait pas d'âge. Au lieu de cela, l'approche de @MatthewFlaschen est préférable pour les raisons mentionnées par @NitinMidha.

Maintenant construisons le SQL ...

Premièrement, nous devons diviser les âges en groupes de 10, comme suit:

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

Cela peut être réalisé en divisant la colonne d'âge par 10 et en calculant ensuite 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

Ensuite, prenons le code SQL original et remplaçons age par cette expression:

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

C'est OK, mais nous ne pouvons pas voir la plage, pour le moment. Au lieu de cela, nous ne voyons que les valeurs de plancher calculées qui sont 0, 1, 2 ... n.

Pour obtenir la limite inférieure réelle, nous devons la multiplier par 10 afin d'obtenir 0, 10, 20 ... n:

FLOOR(age/10) * 10

Nous avons également besoin de la limite supérieure de chaque plage, qui est inférieure liée + 10 - 1 ou

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

Enfin, nous concaténons les deux dans une chaîne comme celle-ci:

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 code 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 un ordre et des alias de colonne Nice:

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 plages peuvent ne pas être regroupées en blocs constants de taille 10, mais nécessitent une mise en cluster dynamique . Oracle inclut des fonctions d'histogramme plus avancées, voir http://docs.Oracle.com/cd /E16655_01/server.121/e15858/tgsql_histo.htm#TGSQL366

Crédits à @MatthewFlaschen pour son approche; J'ai seulement expliqué les détails.

9
Wintermute

Voici une solution qui crée une table "plage" dans une sous-requête et l'utilise ensuite pour partitionner 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
Dan

Je devais regrouper les données en fonction du nombre de transactions effectuées en une heure. Je l'ai fait en extrayant 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 la sortie:

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

Comme vous pouvez le constater, cela permet de regrouper facilement le nombre d’enregistrements par heure.

2
Clarkey

ajoutez une table age_range et un champ age_range_id à votre table et regroupez-les à la place.

// excuse le DDL mais tu devrais avoir 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 excuse le DML mais tu devrais avoir 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 préciser cette idée si vous le souhaitez - ajoutez des colonnes from_age to_age dans la table age_range, etc. - mais je vais vous laisser cela.

j'espère que cela t'aides :)

1
Jon Black

Je devais obtenir un nombre d'échantillons par jour. Inspiré de @Clarkey, j'ai utilisé TO_CHAR pour extraire la date de l'échantillon de l'horodatage vers un format de date ISO-8601 et je l'ai utilisée dans les clauses GROUP BY et ORDER BY. (En outre inspiré, je le poste aussi ici au cas où il serait 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
Kieron Hardy

Si vous utilisez Oracle 9i +, vous pourriez pouvoir utiliser la fonction analytique 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

L'avertissement à NTILE est que vous pouvez uniquement spécifier le nombre de partitions, pas les points d'arrêt eux-mêmes. Vous devez donc spécifier un nombre approprié. IE: avec 100 lignes, NTILE(4) attribuera 25 lignes à chacun des quatre compartiments/partitions. Vous ne pouvez pas imbriquer des fonctions analytiques, vous devez donc les superposer à l'aide de sous-requêtes/factorisation de sous-requêtes pour obtenir la 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
OMG Ponies

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 de la flexibilité dans la définition des gammes
  • Je ne répète pas les plages dans les clauses select et group
  • mais si quelqu'un me dit, comment les commander par ordre de grandeur!
0
Ananth N