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.
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
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);
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.
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;
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.
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 :)
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
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
/
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!