Wie können Sie in SQL nach Bereichen gruppieren?


180

Angenommen, ich habe eine Tabelle mit einer numerischen Spalte (nennen wir sie "Punktzahl").

Ich möchte eine Zählungstabelle erstellen, die zeigt, wie oft Punkte in jedem Bereich angezeigt wurden.

Beispielsweise:

Punktebereich | Anzahl der Vorkommen
-------------------------------------
   0-9 | 11
  10-19 | 14
  20-29 | 3
   ... | ...

In diesem Beispiel gab es 11 Zeilen mit Bewertungen im Bereich von 0 bis 9, 14 Zeilen mit Bewertungen im Bereich von 10 bis 19 und 3 Zeilen mit Bewertungen im Bereich von 20 bis 29.

Gibt es eine einfache Möglichkeit, dies einzurichten? Was empfehlen Sie?

Antworten:


142

Keine der am höchsten bewerteten Antworten ist auf SQLServer 2000 korrekt. Möglicherweise wurde eine andere Version verwendet.

Hier sind die richtigen Versionen von beiden auf SQLServer 2000.

select t.range as [score range], count(*) as [number of occurences]
from (
  select case  
    when score between 0 and 9 then ' 0- 9'
    when score between 10 and 19 then '10-19'
    else '20-99' end as range
  from scores) t
group by t.range

oder

select t.range as [score range], count(*) as [number of occurences]
from (
      select user_id,
         case when score >= 0 and score< 10 then '0-9'
         when score >= 10 and score< 20 then '10-19'
         else '20-99' end as range
     from scores) t
group by t.range

Kann ich auch eine andere Spalte aggregieren (wie Gruppenzahlen)? Angenommen, ich möchte die Stipendienspalte für jeden Bewertungsbereich zusammenfassen. Ich habe es versucht, aber nicht richtig verstanden
Munish Goyal

Schöne Antwort @ Ron Tuffin, aber wenn Sie zwei Bereiche wie 10-20, 100-200 haben, dann funktioniert die Reihenfolge nicht. Sie hätten eine Bestellung wie 10-20, 100-200,20-30 usw. Gibt es einen Tipp für die Bestellung von?
Zo hat

2
@ ZoHas es ist ein bisschen ein Hack, aber das funktioniert: Bestellung von len (t.range), t.range
Ron Tuffin


1
Wenn Sie immer noch Syntaxprobleme haben, überprüfen Sie diese Antwort: dba.stackexchange.com/questions/22491/…
Robert Hosking

33

Ein alternativer Ansatz würde darin bestehen, die Bereiche in einer Tabelle zu speichern, anstatt sie in die Abfrage einzubetten. Sie würden am Ende einen Tisch haben, nennen Sie ihn Bereiche, der so aussieht:

LowerLimit   UpperLimit   Range 
0              9          '0-9'
10            19          '10-19'
20            29          '20-29'
30            39          '30-39'

Und eine Abfrage, die so aussieht:

Select
   Range as [Score Range],
   Count(*) as [Number of Occurences]
from
   Ranges r inner join Scores s on s.Score between r.LowerLimit and r.UpperLimit
group by Range

Dies bedeutet zwar das Einrichten einer Tabelle, ist jedoch leicht zu pflegen, wenn sich die gewünschten Bereiche ändern. Keine Codeänderungen notwendig!


Ich habe eine Frage zum Tabellenentwurf für Datenbankadministratoren für gemusterte Daten mit variablen Bucket-Bereichen gestellt, die keine Antwort erhalten hat, aber am Ende habe ich ein System mit den von Ihnen genannten Bereichen entworfen. Ich liebe diese Antwort.
ΩmegaMan

31

Ich sehe hier Antworten, die in der SQL Server-Syntax nicht funktionieren. Ich würde ... benutzen:

select t.range as [score range], count(*) as [number of occurences]
from (
  select case 
    when score between  0 and  9 then ' 0-9 '
    when score between 10 and 19 then '10-19'
    when score between 20 and 29 then '20-29'
    ...
    else '90-99' end as range
  from scores) t
group by t.range

BEARBEITEN: siehe Kommentare


Möglicherweise liegt es an der Version von SQLServer, die ich verwende, aber um Ihr Beispiel zum Laufen zu bringen (ich teste Dinge, bevor ich sie abstimme), musste ich "Punktzahl" von nach dem "Fall" nach jedem "Wann" verschieben.
Ron Tuffin

3
Du hast recht und danke für die Korrektur. Wenn Sie die Variable nach dem Schlüsselwort 'case' setzen, können Sie anscheinend nur exakte Übereinstimmungen durchführen, keine Ausdrücke. Ich lerne genauso viel aus der Beantwortung von Fragen wie aus der Beantwortung. :-)
Ken Paul

23

In postgres (wo ||ist der String-Verkettungsoperator):

select (score/10)*10 || '-' || (score/10)*10+9 as scorerange, count(*)
from scores
group by score/10
order by 1

gibt:

 scorerange | count 
------------+-------
 0-9        |    11
 10-19      |    14
 20-29      |     3
 30-39      |     2

11

James Currans Antwort war meiner Meinung nach die prägnanteste, aber die Ausgabe war nicht korrekt. Für SQL Server lautet die einfachste Anweisung wie folgt:

SELECT 
    [score range] = CAST((Score/10)*10 AS VARCHAR) + ' - ' + CAST((Score/10)*10+9 AS VARCHAR), 
    [number of occurrences] = COUNT(*)
FROM #Scores
GROUP BY Score/10
ORDER BY Score/10

Dies setzt eine temporäre # Scores-Tabelle voraus, die ich zum Testen verwendet habe. Ich habe gerade 100 Zeilen mit einer Zufallszahl zwischen 0 und 99 gefüllt.


1
Ah ... Es hat den Vorteil, sich tatsächlich die Zeit zu nehmen, um die Tabelle zu erstellen. (Ich habe eine vorhandene Tabelle mit zu wenigen Zeilen über einen zu kleinen Bereich verwendet)
James Curran

5
create table scores (
   user_id int,
   score int
)

select t.range as [score range], count(*) as [number of occurences]
from (
      select user_id,
         case when score >= 0 and score < 10 then '0-9'
         case when score >= 10 and score < 20 then '10-19'
         ...
         else '90-99' as range
     from scores) t
group by t.range

Vielen Dank! Ich habe es versucht und die Grundidee funktioniert großartig, obwohl die Syntax, die ich verwenden musste, etwas anders ist. Es wird nur das erste Schlüsselwort "case" benötigt, und nach der letzten Bedingung benötigen Sie vor dem Bereich "as" das Schlüsselwort "end". Davon abgesehen hat es super funktioniert - danke!
Hugh

5
select cast(score/10 as varchar) + '-' + cast(score/10+9 as varchar), 
       count(*)
from scores
group by score/10

Ich mag das, aber Sie müssen die Bereiche außerhalb der Abfrage korrigieren, wenn Sie sie anzeigen möchten.
Tvanfosson

Wenn Sie sich entscheiden, Ihre Antwort zu korrigieren, müssen Sie Ihre Punktzahl / 10 in der ersten Zeile auf (Punktzahl / 10) * 10 für beide ändern, andernfalls erhalten Sie 3 - 12 anstelle von 30-39 usw. Gemäß meinem Beitrag Unten können Sie eine Bestellung hinzufügen, indem Sie die Ergebnisse in der richtigen Reihenfolge erhalten.
Timothy Walters

5

Auf diese Weise müssen Sie keine Bereiche angeben und sollten SQL Server-unabhängig sein. Mathe FTW!

SELECT CONCAT(range,'-',range+9), COUNT(range)
FROM (
  SELECT 
    score - (score % 10) as range
  FROM scores
)

3

Ich würde das etwas anders machen, damit es skaliert, ohne jeden Fall definieren zu müssen:

select t.range as [score range], count(*) as [number of occurences]
from (
  select FLOOR(score/10) as range
  from scores) t
group by t.range

Nicht getestet, aber Sie bekommen die Idee ...


2
declare @RangeWidth int

set @RangeWidth = 10

select
   Floor(Score/@RangeWidth) as LowerBound,
   Floor(Score/@RangeWidth)+@RangeWidth as UpperBound,
   Count(*)
From
   ScoreTable
group by
   Floor(Score/@RangeWidth)

1
select t.blah as [score range], count(*) as [number of occurences]
from (
  select case 
    when score between  0 and  9 then ' 0-9 '
    when score between 10 and 19 then '10-19'
    when score between 20 and 29 then '20-29'
    ...
    else '90-99' end as blah
  from scores) t
group by t.blah

Stellen Sie sicher, dass Sie ein anderes Wort als 'range' verwenden, wenn Sie sich in MySQL befinden. Andernfalls wird beim Ausführen des obigen Beispiels eine Fehlermeldung angezeigt.


1

Da es sich bei der nach ( Range) sortierten Spalte um eine Zeichenfolge handelt, wird anstelle der numerischen Sortierung die Zeichenfolgen- / Wortsortierung verwendet.

Solange die Zeichenfolgen Nullen haben, um die Zahlenlängen aufzufüllen, sollte die Sortierung immer noch semantisch korrekt sein:

SELECT t.range AS ScoreRange,
       COUNT(*) AS NumberOfOccurrences
  FROM (SELECT CASE
                    WHEN score BETWEEN 0 AND 9 THEN '00-09'
                    WHEN score BETWEEN 10 AND 19 THEN '10-19'
                    ELSE '20-99'
               END AS Range
          FROM Scores) t
 GROUP BY t.Range

Wenn der Bereich gemischt ist, füllen Sie einfach eine zusätzliche Null auf:

SELECT t.range AS ScoreRange,
       COUNT(*) AS NumberOfOccurrences
  FROM (SELECT CASE
                    WHEN score BETWEEN 0 AND 9 THEN '000-009'
                    WHEN score BETWEEN 10 AND 19 THEN '010-019'
                    WHEN score BETWEEN 20 AND 99 THEN '020-099'
                    ELSE '100-999'
               END AS Range
          FROM Scores) t
 GROUP BY t.Range

1

Versuchen

SELECT (str(range) + "-" + str(range + 9) ) AS [Score range], COUNT(score) AS [number of occurances]
FROM (SELECT  score,  int(score / 10 ) * 10  AS range  FROM scoredata )  
GROUP BY range;

3
Es wäre hilfreich, wenn Sie eine Erklärung hinzufügen könnten, wie Ihre Abfrage das Problem löst.
Devlin Carnate

-1

Vielleicht fragen Sie, ob Sie solche Dinge am Laufen halten sollen ...

Natürlich rufen Sie einen vollständigen Tabellenscan für die Abfragen auf. Wenn die Tabelle mit den zu bewertenden Bewertungen (Aggregationen) groß ist, möchten Sie möglicherweise eine Lösung mit besserer Leistung. Sie können eine sekundäre Tabelle erstellen und Regeln verwenden, z on insert - Sie könnten es untersuchen.

Allerdings haben nicht alle RDBMS-Engines Regeln!

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.