En SQL, comment pouvez-vous «grouper par» dans des plages?

181

Supposons que j'ai une table avec une colonne numérique (appelons-la "score").

Je voudrais générer un tableau des comptes, qui montre combien de fois les scores sont apparus dans chaque plage.

Par exemple:

gamme de score | nombre d'occurrences
-------------------------------------
   0-9 | 11
  10-19 | 14
  20-29 | 3
   ... | ...

Dans cet exemple, il y avait 11 lignes avec des scores compris entre 0 et 9, 14 lignes avec des scores compris entre 10 et 19 et 3 lignes avec des scores compris entre 20 et 29.

Existe-t-il un moyen simple de configurer cela? Que recommandez-vous?

Hugh
la source

Réponses:

143

Aucune des réponses aux votes les plus élevés n'est correcte sur SQL Server 2000. Ils utilisaient peut-être une version différente.

Voici les versions correctes des deux sur 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

ou

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
Ron Tuffin
la source
Puis-je également agréger une autre colonne (comme le nombre de groupes). disons que je veux agréger la colonne des bourses pour chaque plage de notes. J'ai essayé, mais je n'ai pas
réussi
Bonne réponse @Ron Tuffin, mais lorsque vous avez deux plages comme 10-20, 100-200, alors la commande ne fonctionne pas. vous auriez une commande comme 10-20, 100-200,20-30 etc. Un conseil pour la commande par?
Zo a
2
@ZoHas c'est un peu un hack mais ça marche: commande par len (t.range), t.range
Ron Tuffin
Meilleure réponse sur stackoverflow.com/questions/14730380/…
Thunder
1
Si vous rencontrez toujours des problèmes de syntaxe, vérifiez cette réponse: dba.stackexchange.com/questions/22491/…
Robert Hosking
33

Une approche alternative impliquerait de stocker les plages dans une table, au lieu de les incorporer dans la requête. Vous vous retrouveriez avec une table, appelez-la Ranges, qui ressemble à ceci:

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

Et une requête qui ressemble à ceci:

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

Cela signifie mettre en place une table, mais il serait facile à maintenir lorsque les plages souhaitées changent. Aucun changement de code nécessaire!

Walter Mitty
la source
J'ai posé une question sur la conception de la table des administrateurs de base de données pour les données à motifs utilisant des plages de seau variables qui n'ont pas obtenu de réponse, mais j'ai fini par concevoir un système contenant les plages que vous avez mentionnées. J'adore cette réponse.
ΩmegaMan
31

Je vois des réponses ici qui ne fonctionneront pas dans la syntaxe de SQL Server. J'utiliserais:

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: voir les commentaires

Ken Paul
la source
C'est peut-être à cause de la version de SQLServer que j'utilise, mais pour que votre exemple fonctionne (je teste les choses avant de les voter), j'ai dû déplacer le «score» après le «cas» vers après chaque «quand».
Ron Tuffin
3
Vous avez raison, et merci pour la correction. Apparemment, lorsque vous mettez la variable après le mot-clé «case», vous ne pouvez faire que des correspondances exactes, pas des expressions. J'apprends autant en répondant aux questions qu'en les posant. :-)
Ken Paul
23

Dans postgres (où ||est l'opérateur de concaténation de chaîne):

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

donne:

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

La réponse de James Curran était la plus concise à mon avis, mais le résultat n'était pas correct. Pour SQL Server, l'instruction la plus simple est la suivante:

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

Cela suppose une table temporaire #Scores que j'ai utilisée pour le tester, je viens de remplir 100 lignes avec un nombre aléatoire entre 0 et 99.

Timothy Walters
la source
1
Ah ... Il y a l'avantage de prendre le temps de créer la table. (J'ai utilisé une table existante avec trop peu de lignes sur une plage trop petite)
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
la source
Merci! J'ai essayé cela et l'idée de base fonctionne très bien, bien que la syntaxe que j'ai dû utiliser soit légèrement différente. Seul le premier mot-clé "case" est nécessaire, puis après la dernière condition, avant le "as range", vous avez besoin du mot-clé "end". A part ça, a très bien fonctionné - merci!
Hugh
5
select cast(score/10 as varchar) + '-' + cast(score/10+9 as varchar), 
       count(*)
from scores
group by score/10
James Curran
la source
J'aime ça, mais vous devez corriger les plages en dehors de la requête si vous allez l'afficher.
tvanfosson
Dans le cas où vous décidez de fixer votre réponse, vous devez changer votre score / 10 sur la première ligne pour être (score / 10) * 10 pour les deux sinon vous obtenez 3 - 12 au lieu de 30-39 etc. Selon mon message ci-dessous, vous pouvez ajouter une commande pour obtenir les résultats dans le bon ordre.
Timothy Walters
5

Cela vous permettra de ne pas avoir à spécifier de plages et devrait être indépendant du serveur SQL. Math FTW!

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

Je ferais cela un peu différemment pour qu'il évolue sans avoir à définir chaque cas:

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

Pas testé, mais vous avez l'idée ...

JoshNaro
la source
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
la source
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

Assurez-vous d'utiliser un mot autre que «range» si vous êtes dans MySQL, sinon vous obtiendrez une erreur pour exécuter l'exemple ci-dessus.

Danny Hui
la source
1

Étant donné que la colonne triée sur ( Range) est une chaîne, le tri chaîne / mot est utilisé à la place du tri numérique.

Tant que les chaînes ont des zéros pour compléter les longueurs des nombres, le tri doit toujours être sémantiquement correct:

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

Si la plage est mixte, ajoutez simplement un zéro supplémentaire:

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
la source
1

Essayer

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
la source
3
il serait utile que vous puissiez ajouter des explications sur la façon dont votre requête résout le problème.
devlin carnate
-1

Peut-être que vous demandez comment faire fonctionner de telles choses ...

Bien sûr, vous invoquerez une analyse complète de la table pour les requêtes et si la table contenant les scores à compter (agrégations) est grande, vous voudrez peut-être une solution plus performante, vous pouvez créer une table secondaire et utiliser des règles, telles que on insert- vous pourriez l'examiner.

Cependant, tous les moteurs SGBDR n'ont pas de règles!

Richard T
la source