В SQL, как вы можете «сгруппировать» по диапазонам?

181

Предположим, у меня есть таблица с числовым столбцом (назовем ее «счет»).

Я хотел бы создать таблицу подсчетов, которая показывает, сколько раз результаты появлялись в каждом диапазоне.

Например:

диапазон очков | количество вхождений
-------------------------------------
   0-9 | 11
  10-19 | 14
  20-29 | 3
   ... | ...

В этом примере было 11 строк с оценками в диапазоне от 0 до 9, 14 строк с оценками в диапазоне от 10 до 19 и 3 строки с оценками в диапазоне 20-29.

Есть ли простой способ настроить это? Что вы порекомендуете?

Хью
источник

Ответы:

143

Ни один из ответов с наибольшим количеством голосов не является правильным на SQL Server 2000. Возможно, они использовали другую версию.

Вот правильные версии их обоих на 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

или

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, однако, когда у вас есть два диапазона, например, 10-20, 100-200, порядок не работает. у вас был бы заказ как 10-20, 100-200,20-30 и т. д. Любой совет для заказа?
Zo Has
2
@ ZoHas это немного хак, но это работает: заказ от len (t.range), t.range
Ron Tuffin
Лучший ответ на stackoverflow.com/questions/14730380/…
Гром
1
Если у вас все еще есть проблемы с синтаксисом, проверьте этот ответ: dba.stackexchange.com/questions/22491/…
Роберт Хоскинг
33

Альтернативный подход предполагает сохранение диапазонов в таблице, а не встраивание их в запрос. В итоге вы получите таблицу, назовите ее Ranges, которая выглядит так:

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

И запрос, который выглядит так:

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

Это означает настройку таблицы, но ее будет легко поддерживать при изменении желаемых диапазонов. Никаких изменений кода не требуется!

Уолтер Митти
источник
Я задал вопрос о дизайне таблицы администраторов баз данных для шаблонных данных с использованием диапазонов переменных переменных, который не получил ответа, но в итоге я разработал систему, которая имеет упомянутые вами диапазоны. Люблю этот ответ.
ΩmegaMan
31

Здесь я вижу ответы, которые не будут работать в синтаксисе SQL Server. Я хотел бы использовать:

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

РЕДАКТИРОВАТЬ: см. Комментарии

Кен Пол
источник
Возможно, это из-за версии SQLServer, которую я использую, но чтобы заставить ваш пример работать (я проверяю что-то перед тем, как голосовать), мне пришлось переместить «счет» от «дела» к каждому после «когда».
Рон Туффин
3
Вы правы, и спасибо за исправление. Очевидно, когда вы помещаете переменную после ключевого слова 'case', вы можете делать только точные совпадения, а не выражения. Я учусь столько же, отвечая на вопросы, сколько и задавая их. :-)
Кен Пол
23

В postgres (где ||находится оператор конкатенации строк):

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

дает:

 scorerange | count 
------------+-------
 0-9        |    11
 10-19      |    14
 20-29      |     3
 30-39      |     2
mhawke
источник
11

На мой взгляд, ответ Джеймса Керрана был самым кратким, но результат был неверным. Для SQL Server самое простое утверждение выглядит следующим образом:

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

Предполагается, что временная таблица #Scores, которую я использовал для ее тестирования, просто заполнила 100 строк случайным числом от 0 до 99.

Тимоти Уолтерс
источник
1
Ах ... Есть преимущество в том, что вы на самом деле тратите время на создание таблицы. (Я использовал существующую таблицу со слишком малым количеством строк в слишком маленьком диапазоне)
Джеймс Керран,
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
источник
Спасибо! Я попробовал это, и основная идея прекрасно работает, хотя синтаксис, который мне пришлось использовать, немного отличается. Требуется только первое ключевое слово "case", а затем после последнего условия перед "as range" вам нужно ключевое слово "end". Кроме того, работал отлично - спасибо!
Хью
5
select cast(score/10 as varchar) + '-' + cast(score/10+9 as varchar), 
       count(*)
from scores
group by score/10
Джеймс Керран
источник
Мне это нравится, но вы должны исправить диапазоны вне запроса, если вы собираетесь его отображать.
tvanfosson
В случае, если вы решили исправить свой ответ, вам нужно изменить ваш счет / 10 в первой строке, чтобы он был (счет / 10) * 10 для них обоих, в противном случае вы получите 3 - 12 вместо 30-39 и т. Д. Согласно моему посту ниже вы можете добавить заказ, чтобы получить результаты в правильном порядке.
Тимоти Уолтерс
5

Это позволит вам не указывать диапазоны и должно быть независимым от SQL-сервера. Математика FTW!

SELECT CONCAT(range,'-',range+9), COUNT(range)
FROM (
  SELECT 
    score - (score % 10) as range
  FROM scores
)
trevorgrayson
источник
3

Я бы сделал это немного по-другому, чтобы он масштабировался без определения каждого случая:

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

Не проверено, но вы поняли ...

JoshNaro
источник
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
источник
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

Убедитесь, что вы используете слово, отличное от 'range', если вы находитесь в MySQL, иначе вы получите ошибку при запуске приведенного выше примера.

Дэнни Хуэй
источник
1

Поскольку столбец, сортируемый по ( Range), является строкой, вместо числовой сортировки используется сортировка строк / слов.

До тех пор, пока строки имеют нули для заполнения числовых длин, сортировка должна быть семантически правильной:

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

Если диапазон смешанный, просто добавьте дополнительный ноль:

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

Пытаться

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
источник
3
было бы полезно добавить некоторые пояснения о том, как ваш запрос решает проблему.
Devlin Carnate
-1

Возможно, вы спрашиваете о том, чтобы такие вещи продолжались ...

Конечно, вы будете вызывать полное сканирование таблицы для запросов, и если таблица, содержащая оценки, которые необходимо подсчитать (агрегации), велика, вам может потребоваться более эффективное решение, вы можете создать дополнительную таблицу и использовать правила, такие как on insert- вы можете посмотреть на это.

Однако не все движки СУБД имеют правила!

Ричард Т
источник