In SQL, come puoi "raggruppare per" negli intervalli?


181

Supponiamo che io abbia una tabella con una colonna numerica (chiamiamola "punteggio").

Vorrei generare una tabella di conteggi, che mostra quante volte i punteggi sono apparsi in ciascun intervallo.

Per esempio:

intervallo di punteggio | numero di occorrenze
-------------------------------------
   0-9 | 11
  10-19 | 14
  20-29 | 3
   ... | ...

In questo esempio c'erano 11 righe con punteggi nell'intervallo da 0 a 9, 14 righe con punteggi nell'intervallo da 10 a 19 e 3 righe con punteggi nell'intervallo 20-29.

C'è un modo semplice per configurarlo? Che cosa mi consiglia?

Risposte:


143

Nessuna delle risposte più votate è corretta su SQL Server 2000. Forse utilizzavano una versione diversa.

Ecco le versioni corrette di entrambi su SQL Server 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

o

select t.range as [score range], count(*) as [number of occurrences]
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

Posso aggregare anche un'altra colonna (come il conteggio dei gruppi). dico che voglio aggregare la colonna della borsa di studio per ogni intervallo di punteggio. Ci ho provato, ma non ho capito bene
Munish Goyal,

Bella risposta @ Ron Tuffin, tuttavia quando hai due intervalli come 10-20, 100-200, l'ordine non funziona. avresti ordine come 10-20, 100-200,20-30 ecc. Qualche suggerimento per l'ordine entro?
Zo ha il

2
@ZoHas è un po 'un trucco ma funziona: ordina per len (t.range), t.range
Ron Tuffin


1
Se hai ancora problemi di sintassi, controlla questa risposta: dba.stackexchange.com/questions/22491/…
Robert Hosking,

33

Un approccio alternativo implicherebbe l'archiviazione degli intervalli in una tabella, anziché incorporarli nella query. Si finirebbe con un tavolo, lo si chiama Ranges, che assomiglia a questo:

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

E una query simile a questa:

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

Ciò significa impostare una tabella, ma sarebbe facile da mantenere quando cambiano gli intervalli desiderati. Non sono necessarie modifiche al codice!


Ho fatto una domanda sulla progettazione della tabella degli amministratori di database per i dati modellati utilizzando intervalli di bucket variabili che non hanno ottenuto una risposta, ma ho finito per progettare un sistema con gli intervalli menzionati. Adoro questa risposta.
ΩmegaMan

31

Vedo qui delle risposte che non funzioneranno nella sintassi di SQL Server. Io userei:

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

EDIT: vedi commenti


È forse a causa della versione di SQL Server che sto usando, ma per far funzionare il tuo esempio (collaudo le cose prima di votarle) ho dovuto spostare il 'punteggio' da dopo il 'caso' a dopo ogni 'quando'.
Ron Tuffin,

3
Hai ragione e grazie per la correzione. Apparentemente quando metti la variabile dopo la parola chiave 'case', puoi fare solo corrispondenze esatte, non espressioni. Imparo tanto dal rispondere alle domande quanto dal porle. :-)
Ken Paul,

23

In postgres (dove si ||trova l'operatore di concatenazione delle stringhe):

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

dà:

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

11

La risposta di James Curran è stata la più concisa secondo me, ma l'output non è corretto. Per SQL Server l'istruzione più semplice è la seguente:

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

Questo presuppone una tabella temporanea #Scores che ho usato per testarlo, ho appena popolato 100 righe con un numero casuale compreso tra 0 e 99.


1
Ah ... C'è il vantaggio di impiegare del tempo per creare il tavolo. (Ho usato una tabella esistente con troppe righe su un intervallo troppo piccolo)
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

Grazie! Ho provato questo e l'idea di base funziona alla grande, sebbene la sintassi che ho dovuto usare sia leggermente diversa. È necessaria solo la prima parola chiave "case" e quindi dopo l'ultima condizione, prima dell'intervallo "as range" è necessaria la parola chiave "end". A parte questo, ha funzionato benissimo, grazie!
Hugh,

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

Mi piace questo, ma devi sistemare gli intervalli al di fuori della query se vuoi visualizzarlo.
tvanfosson,

Nel caso in cui decidessi di correggere la tua risposta, devi cambiare il tuo punteggio / 10 sulla prima riga in modo che sia (punteggio / 10) * 10 per entrambi, altrimenti otterrai 3 - 12 invece di 30-39 ecc. Secondo il mio post di seguito puoi aggiungere un ordine per ottenere i risultati nel giusto ordine.
Timothy Walters,

5

Ciò consentirà di non dover specificare intervalli e dovrebbe essere indipendente dal server SQL. Math FTW!

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

3

Lo farei in modo leggermente diverso in modo da ridimensionarlo senza dover definire tutti i casi:

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

Non testato, ma ti viene l'idea ...


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

Assicurati di usare una parola diversa da "range" se ti trovi in ​​MySQL, oppure visualizzerai un errore per l'esecuzione dell'esempio precedente.


1

Poiché la colonna ordinata su ( Range) è una stringa, viene utilizzato l'ordinamento stringa / parola anziché ordinamento numerico.

Fintanto che le stringhe hanno zeri per riempire le lunghezze dei numeri, l'ordinamento dovrebbe essere semanticamente corretto:

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

Se l'intervallo è misto, basta aggiungere un ulteriore 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

1

Provare

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
sarebbe utile se potessi aggiungere qualche spiegazione su come la tua query risolve il problema.
Devlin Carnate,

-1

Forse stai chiedendo di mantenere queste cose in corso ...

Ovviamente invocherai una scansione completa della tabella per le query e se la tabella contenente i punteggi che devono essere conteggiati (aggregazioni) è grande, potresti desiderare una soluzione con prestazioni migliori, puoi creare una tabella secondaria e utilizzare regole, come on insert- potresti guardarci dentro.

Tuttavia, non tutti i motori RDBMS hanno regole!

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.