2010-03-20 9 views
28

Wenn ich eine Tabelle wie folgt aus:Oracle: Wie man über eine Reihe "gruppiert"?

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

Ich kann „Gruppe“ eine Zählung jedes Alter zu erhalten.

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

Welche Abfrage kann ich verwenden, um nach Altersbereichen zu gruppieren?

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

Ich bin auf 10gR2, aber ich würde in 11g-spezifische Ansätze auch interessiert sein.

Antwort

50
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 
+0

Dies sollte die erste und einzige Antwort auf diese Frage sein. Könnte etwas mehr Formatierung verwenden. – jva

+2

Nein, CASE-Anweisungen verwenden Kurzschlussbewertung – Einstein

+0

Wie würde eine Kurzschlussbewertung ein Problem in dieser Abfrage verursachen? Da die Fälle geordnet sind und <= verwenden, wird immer die richtige Gruppe ausgewählt. Ist es nicht? – Adrian

23

Versuchen:

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

geschickte Verwendung von Boden/Division! – mpen

+1

Dieser Ansatz ist besser, wenn wir ein definiertes Muster haben und Gruppen über einen Ausdruck berechnet werden können. Es erfordert nicht, die Gruppen in Abfrage ausdrücklich zu erwähnen und wird daher in Lage sein, neue Gruppen bereitzustellen, ohne die Abfrage zu ändern. –

+1

Das funktioniert nicht, es führt zu ** Fehler ORA-00979: kein GROUP BY-Ausdruck * * weil 'ceil (age/10)' im Ausdruck GROUP BY fehlt. Aber die Richtung dieses Ansatzes ist besser, als @NitinMidha schrieb, also stimme ich diese Antwort ab. – Wintermute

1

eine age_range Tabelle hinzufügen und eine age_range_id Feld auf den Tisch und die Gruppe von diesem statt.

// die DDL entschuldigen, aber Sie sollten die Idee

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

// wieder die DML entschuldigen, aber Sie sollten die Idee

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; 

Sie können diese Idee verfeinern, wenn Sie mögen erhalten erhalten - Fügen Sie from_age to_age-Spalten in der age_range-Tabelle usw. hinzu - aber das überlasse ich Ihnen.

hoffte, das hilft :)

+0

Gemessen an den anderen Antworten sind Leistung und Flexibilität keine wichtigen Kriterien. Die EXPLAIN-Pläne für alle aufgelisteten dynamischen Abfragen wären entsetzlich und Sie müssten den Code ändern, wenn sich Ihre Altersgruppen ändern. Jeder zu ihrem eigenen Ich denke: P –

+0

1 voller Scan wird immer schneller sein als 2 vollständige Scans. Auch haben Leute, die nach Altersbereichsstatistiken fragen, wahrscheinlich die gleichen Bereiche für die letzten 20+ Jahre gehabt und haben nicht die Absicht, dies zu ändern. – jva

+1

Ich bin mir ziemlich sicher, dass die physische Spalte eine abgeleitete/berechnete durchführen wird. Tatsächlich ist es wahrscheinlich ein idealer Kandidat für einen Bitmap-Index. Ich würde immer noch lieber eine Nachschlagetabelle verwenden, als Werte in meinen Anwendungen fest zu codieren. Hinzufügen eines neuen Altersbereich sagen 14-16 Jahre und ich bin eine neue Zeile im Vergleich zu einem Änderungswunsch, Zeitaufwand Codierung und Testen der Änderungen und Freigabe in prod. –

3

Hier ist eine Lösung, die eine „Bereich“ Tabelle in einer Unterabfrage erstellt und verwendet dann diese Daten aus der Haupttabelle zu partitionieren:

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

Bei der Verwendung von Oracle 9i +, Sie könnte Lage sein, die NTILE analytic function zu verwenden:

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 

Der Vorbehalt zu NTILE ist, dass Sie nur die Anzahl der Partitionen, nicht die Breakpunkte selbst angeben können. Sie müssen also eine passende Nummer angeben. IE: Mit 100 Zeilen teilt NTILE(4) jedem der vier Buckets/Partitionen 25 Zeilen zu. Sie können analytische Funktionen nicht verschachteln, daher müssen Sie sie mithilfe von Unterabfragen/Unterabfragen factoring schichten, um die gewünschte Granularität zu erhalten. Ansonsten, verwenden Sie:

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 
2

Ich musste Daten gruppieren, nach wie vielen Transaktionen in einer Stunde erschien. Ich tat dies, indem Sie die Stunde aus dem Zeitstempel zu extrahieren:

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 
; 

Giving Ausgang:

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

Wie Sie sehen dies eine schöne einfache Art und Weise gibt die Anzahl der Datensätze pro Stunde der Gruppierung.

1

Ich musste eine Anzahl von Proben bis zum Tag bekommen. Inspiriert von @Clarkey habe ich TO_CHAR verwendet, um das Datum des Samples aus dem Zeitstempel in ein ISO-8601-Datumsformat zu extrahieren und dieses in den GROUP BY- und ORDER BY-Klauseln zu verwenden. (Weiter inspiriert, poste ich es hier auch für den Fall, dass es für andere nützlich ist.)

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 
/
7

Was Sie suchen, ist im Grunde die Daten für eine histogram.

Sie hätten das Alter (oder Altersbereich) auf der X-Achse und den Zählwert n (oder die Häufigkeit) auf der Y-Achse.

In der einfachsten Form könnte man einfach die Anzahl der jede einzelne Alterswert zählen wie bereits beschrieben:

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

Wenn es jedoch zu viele unterschiedliche Werte für die x-Achse sind, um ein möchten Erstellen Sie Gruppen (oder Cluster oder Buckets). In Ihrem Fall gruppieren Sie mit einem konstanten Bereich von 10.

Wir können vermeiden, eine WHEN ... THEN Zeile für jeden Bereich zu schreiben - es könnte Hunderte geben, wenn es nicht über Alter wäre. Stattdessen ist der Ansatz von @MatthewFlaschen aus den von @NitinMidha genannten Gründen vorzuziehen.

Nun wollen wir die SQL bauen ...

Zuerst müssen wir das Alter in Entfernungs-Gruppen von 10 wie so aufgeteilt:

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

Dies kann durch div erreicht werden Iding die Alte Spalte 10 und dann das Boden des Ergebnisses der Berechnung:

FLOOR(age/10) 

„FLOOR auf die größte ganze Zahl zurückzugibt oder kleiner als n“ http://docs.oracle.com/cd/E11882_01/server.112/e26088/functions067.htm#SQLRF00643

Dann nehmen wir den ursprünglichen SQL und ersetzen Alte mit diesem Ausdruck:

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

das ist in Ordnung, aber wir können den Bereich nicht sehen, noch nicht. Stattdessen sehen wir nur die berechneten Bodenwerte, die 0, 1, 2 ... n sind.

Um die tatsächliche untere Grenze zu erhalten, müssen wir es wieder mit 10 multiplizieren, so dass wir 0, 10, 20 ... n erhalten:

FLOOR(age/10) * 10 

Wir brauchen auch die oberen jeden Bereich gebunden, die gebunden + 10 niedriger ist - 1 oder

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

Schließlich verketten wir beide in eine Zeichenfolge wie folgt:

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

Dies erstellt '0-9', '10-19', '20-29' usw.

Jetzt ist unsere SQL wie folgt aussieht:

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

schließlich einen Auftrag und schöne Spaltenaliasnamen gelten:

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) 

jedoch in komplexeren Szenarien, könnten diese Bereiche nicht in konstant gruppiert werden Stücke der Größe 10, müssen aber dynamisch gruppiert werden. Oracle hat erweiterte Histogrammfunktionen enthalten, siehe http://docs.oracle.com/cd/E16655_01/server.121/e15858/tgsql_histo.htm#TGSQL366

Dank an @MatthewFlaschen für seinen Ansatz; Ich habe nur die Details erklärt.

0

Mein Ansatz:

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 
  • I Flexibilität haben die Bereiche bei der Definition
  • ich nicht wiederholen, die Bereiche in ausgewählten und Gruppenklauseln
  • aber jemand mir bitte erklären, wie man bestellt sie nach Größe!
Verwandte Themen