Jak w SQL można „grupować według” w zakresach?

181

Załóżmy, że mam tabelę z kolumną numeryczną (nazwijmy ją „wynikiem”).

Chciałbym wygenerować tabelę zliczeń, która pokazuje, ile razy wyniki pojawiły się w każdym zakresie.

Na przykład:

zakres punktacji | liczba wystąpień
-------------------------------------
   0–9 | 11
  10–19 | 14
  20–29 | 3)
   ... | ...

W tym przykładzie było 11 rzędów z wynikami w przedziale od 0 do 9, 14 rzędów z wynikami w przedziale od 10 do 19 oraz 3 rzędy z wynikami w przedziale 20-29.

Czy istnieje prosty sposób na skonfigurowanie tego? Co polecasz?

Hugh
źródło

Odpowiedzi:

143

Żadna z najwyżej ocenianych odpowiedzi nie jest poprawna w SQLServer 2000. Być może używali innej wersji.

Oto poprawne wersje obu z nich na 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

lub

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
Ron Tuffin
źródło
Czy mogę agregować również inną kolumnę (np. Liczbę grup). powiedz, że chcę zagregować kolumnę stypendialną dla każdego zakresu punktacji. Próbowałem, ale nie udało mi się to dobrze
Munish Goyal
Dobra odpowiedź @ Ron Tuffin, jednak jeśli masz dwa zakresy, takie jak 10-20, 100-200, kolejność nie działa. miałbyś zamówienie takie jak 10-20, 100-200,20-30 itd. Wszelkie wskazówki dotyczące zamówienia przez?
Zo ma
2
@ZoHas to trochę hack, ale to działa: zamów przez len (t.range), t.range
Ron Tuffin
Lepsza odpowiedź na stackoverflow.com/questions/14730380/…
Thunder
1
Jeśli nadal masz problemy ze składnią, sprawdź tę odpowiedź: dba.stackexchange.com/questions/22491/...
Robert Hosking
33

Alternatywne podejście polegałoby na przechowywaniu zakresów w tabeli zamiast osadzania ich w zapytaniu. W efekcie powstałby stół, który nazywa się Ranges, który wygląda następująco:

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

I zapytanie, które wygląda tak:

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

Oznacza to ustawienie tabeli, ale byłoby łatwe do utrzymania, gdy zmienią się pożądane zakresy. Żadne zmiany kodu nie są konieczne!

Walter Mitty
źródło
Zadałem pytanie na temat projektu tabeli administratorów baz danych dla wzorzystych danych przy użyciu zmiennych zakresów segmentów, które nie uzyskało odpowiedzi, ale ostatecznie zaprojektowałem system o podanych zakresach. Uwielbiam tę odpowiedź.
ΩmegaMan
31

Widzę tutaj odpowiedzi, które nie będą działać w składni SQL Server. Użyłbym:

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

EDYCJA: patrz komentarze

Ken Paul
źródło
Być może jest to spowodowane wersją SQLServer, której używam, ale żeby twój przykład działał (testuję rzeczy, zanim zagłosuję) musiałem przenieść „wynik” z „przypadka” na każdy „kiedy”.
Ron Tuffin
3
Masz rację i dziękuję za korektę. Najwyraźniej po wstawieniu zmiennej po słowie kluczowym „case” można wykonywać tylko dokładne dopasowania, a nie wyrażenia. Uczę się zarówno od odpowiadania na pytania, jak i od zadawania im pytań. :-)
Ken Paul
23

W postgres (gdzie ||jest operator konkatenacji ciągu):

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

daje:

 scorerange | count 
------------+-------
 0-9        |    11
 10-19      |    14
 20-29      |     3
 30-39      |     2
Mahawke
źródło
11

Moim zdaniem odpowiedź Jamesa Currana była najbardziej zwięzła, ale wyniki nie były prawidłowe. W przypadku SQL Server najprostsza instrukcja wygląda następująco:

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

Zakłada się, że tymczasową tabelę #Scores użyłem do jej przetestowania, właśnie zapełniłem 100 wierszy losową liczbą od 0 do 99.

Timothy Walters
źródło
1
Ach ... Zaletą jest fakt, że poświęcenie czasu na stworzenie stołu jest korzystne. (Użyłem istniejącej tabeli ze zbyt małą liczbą wierszy w zbyt małym zakresie)
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
tvanfosson
źródło
Dzięki! Próbowałem tego i podstawowy pomysł działa świetnie, chociaż składnia, której musiałem użyć, jest nieco inna. Potrzebne jest tylko pierwsze słowo kluczowe „case”, a następnie po ostatnim warunku, przed „as range” potrzebne jest słowo kluczowe „end”. Poza tym działało świetnie - dzięki!
Hugh
5
select cast(score/10 as varchar) + '-' + cast(score/10+9 as varchar), 
       count(*)
from scores
group by score/10
James Curran
źródło
Podoba mi się to, ale musisz poprawić zakresy poza zapytaniem, jeśli chcesz je wyświetlić.
tvanfosson
Jeśli zdecydujesz się naprawić odpowiedź, musisz zmienić swój wynik / 10 w pierwszym wierszu, aby wynosił (wynik / 10) * 10 dla obu z nich, w przeciwnym razie otrzymasz 3 - 12 zamiast 30-39 itd. Zgodnie z moim postem poniżej możesz dodać zamówienie, aby uzyskać wyniki we właściwej kolejności.
Timothy Walters
5

To pozwoli ci nie musieć określać zakresów i powinno być agnostyczne dla serwera SQL. Matematyka FTW!

SELECT CONCAT(range,'-',range+9), COUNT(range)
FROM (
  SELECT 
    score - (score % 10) as range
  FROM scores
)
trevorgrayson
źródło
3

Zrobiłbym to trochę inaczej, aby skalować bez konieczności definiowania każdego przypadku:

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

Nie testowano, ale masz pomysł ...

JoshNaro
źródło
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)
Aheho
źródło
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

Upewnij się, że używasz słowa innego niż „zakres”, jeśli jesteś w MySQL, w przeciwnym razie wystąpi błąd podczas uruchamiania powyższego przykładu.

Danny Hui
źródło
1

Ponieważ sortowana kolumna ( Range) jest ciągiem, zamiast sortowania numerycznego stosuje się sortowanie ciągów / słów.

Tak długo, jak ciągi mają zera zerujące długości liczb, sortowanie powinno być semantycznie poprawne:

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

Jeśli zakres jest mieszany, po prostu wstaw dodatkowe zero:

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
Kevin Hogg
źródło
1

Próbować

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;
Stubo
źródło
3
pomocne może być dodanie wyjaśnienia, w jaki sposób zapytanie rozwiązuje problem.
devlin carnate
-1

Być może pytasz o utrzymanie takich rzeczy ...

Oczywiście będziesz wywoływać pełne skanowanie tabeli dla zapytań, a jeśli tabela zawierająca wyniki, które muszą zostać zebrane (agregacje) jest duża, możesz chcieć rozwiązania o lepszej wydajności, możesz utworzyć tabelę pomocniczą i zastosować reguły, takie jak on insert- możesz na to spojrzeć.

Jednak nie wszystkie silniki RDBMS mają reguły!

Richard T.
źródło