No SQL, como você pode "agrupar por" em intervalos?


181

Suponha que eu tenha uma tabela com uma coluna numérica (vamos chamá-la de "pontuação").

Eu gostaria de gerar uma tabela de contagens, que mostra quantas vezes as pontuações apareceram em cada intervalo.

Por exemplo:

faixa de pontuação | número de ocorrências
-------------------------------------
   0-9 11
  10-19 14
  20-29 3
   ... ...

Neste exemplo, havia 11 linhas com pontuações no intervalo de 0 a 9, 14 linhas com pontuações no intervalo de 10 a 19 e 3 linhas com pontuações no intervalo de 20 a 29.

Existe uma maneira fácil de configurar isso? O que você recomenda?

Respostas:


143

Nenhuma das respostas mais votadas está correta no SQL Server 2000. Talvez eles estivessem usando uma versão diferente.

Aqui estão as versões corretas de ambos no 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

Posso agregar outra coluna também (como contagens de grupos). digamos que eu agregue a coluna de bolsas para cada faixa de pontuação. Eu tentei, mas não fazê-lo direito
Munish Goyal

Boa resposta @ Ron Tuffin, no entanto, quando você tem dois intervalos como 10-20, 100-200, o pedido não funciona. você teria pedido como 10-20, 100-200,20-30 etc. Alguma dica para o pedido?
precisa

2
@ZoHas é um pouco de um truque, mas isso funciona: ordem por Len (t.range), t.range
Ron Tuffin


1
Se você ainda tiver problemas de sintaxe, verifique esta resposta: dba.stackexchange.com/questions/22491/…
Robert Hosking

33

Uma abordagem alternativa envolveria armazenar os intervalos em uma tabela, em vez de incorporá-los na consulta. Você terminaria com uma tabela, chamada de Ranges, que fica assim:

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

E uma consulta que se parece com isso:

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

Isso significa configurar uma tabela, mas seria fácil manter quando os intervalos desejados mudarem. Não é necessário alterar o código!


Fiz uma pergunta no Design da tabela de administradores de banco de dados para dados padronizados usando intervalos de intervalo variáveis que não obtiveram resposta, mas acabei criando um sistema com os intervalos mencionados. Ame esta resposta.
ΩmegaMan

31

Vejo respostas aqui que não funcionarão na sintaxe do SQL Server. Eu usaria:

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: veja comentários


Possivelmente, devido à versão do SQLServer que estou usando, mas para que seu exemplo funcione (eu testei as coisas antes de votá-las), tive que mover 'score' de depois do 'case' para depois de cada 'when'.
Ron Tuffin 24/10/08

3
Você está certo e obrigado pela correção. Aparentemente, quando você coloca a variável após a palavra-chave 'case', você pode fazer apenas correspondências exatas, não expressões. Eu aprendo tanto respondendo perguntas quanto perguntando a elas. :-)
Ken Paul

23

No postgres (onde ||está o operador de concatenação de cadeias):

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

A resposta de James Curran foi a mais concisa na minha opinião, mas a saída não estava correta. Para o SQL Server, a instrução mais simples é a seguinte:

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

Isso pressupõe uma tabela temporária #Scores que eu usei para testá-la, apenas preenchi 100 linhas com número aleatório entre 0 e 99.


1
Ah ... Há a vantagem de realmente reservar um tempo para criar a tabela. (Eu usei uma tabela existente com muito poucas linhas alcance um longo muito pequeno)
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

Obrigado! Eu tentei isso e a idéia básica funciona muito bem, embora a sintaxe que eu tive que usar seja um pouco diferente. Somente a primeira palavra-chave "case" é necessária e, depois da última condição, antes do "as range", você precisa da palavra-chave "end". Fora isso, funcionou muito bem - obrigado!
Hugh

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

Eu gosto disso, mas você precisa corrigir os intervalos fora da consulta para exibi-la.
tvanfosson 24/10/08

Caso decida corrigir sua resposta, você precisa alterar sua pontuação / 10 na primeira linha para ser (pontuação / 10) * 10 para os dois, caso contrário, você recebe 3 - 12 em vez de 30-39 etc. abaixo, você pode adicionar um pedido para obter os resultados na ordem certa.
Timothy Walters

5

Isso permitirá que você não precise especificar intervalos e deve ser independente do servidor SQL. Math FTW!

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

3

Eu faria isso de forma um pouco diferente para que ele seja dimensionado sem ter que definir todos os casos:

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

Não testado, mas você entendeu ...


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

Certifique-se de usar uma palavra diferente de 'range' se você estiver no MySQL, ou você receberá um erro ao executar o exemplo acima.


1

Como a coluna que está sendo classificada em ( Range) é uma sequência, a classificação por sequência / palavra é usada em vez da ordenação numérica.

Desde que as strings tenham zeros para preencher os comprimentos dos números, a classificação ainda deve estar semanticamente correta:

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 o intervalo for misto, basta preencher um zero extra:

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

Experimentar

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
seria útil se você pudesse adicionar alguma explicação sobre como sua consulta resolve o problema.
Devlin Carnate

-1

Talvez você esteja perguntando sobre como manter essas coisas acontecendo ...

Obviamente, você invocará uma varredura completa da tabela para as consultas e, se a tabela que contém as pontuações que precisam ser computadas (agregações) for grande, você poderá querer uma solução com melhor desempenho, poderá criar uma tabela secundária e usar regras, como on insert- você pode investigar.

Mas nem todos os mecanismos RDBMS têm regras!

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.