Як у SQL можна згрупувати за діапазонами?


181

Припустимо, у мене є таблиця з числовим стовпцем (давайте назвати її "оцінка").

Я хотів би створити таблицю підрахунків, яка показує, скільки разів бали з’являлися в кожному діапазоні.

Наприклад:

діапазон балів | кількість подій
-------------------------------------
   0-9 | 11
  10-19 | 14
  20-29 | 3
   ... | ...

У цьому прикладі було 11 рядків з балами в діапазоні від 0 до 9, 14 рядів з оцінками в діапазоні від 10 до 19, і 3 ряди з оцінками в діапазоні 20-29.

Чи є простий спосіб налаштувати це? Що ви порадите?

Відповіді:


143

Жоден із найвищих відповідей не відповідає правильній версії SQLServer 2000. Можливо, вони використовували іншу версію.

Ось правильні версії обох у 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

або

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

Чи можу я також об'єднати інший стовпець (наприклад, кількість груп). скажімо, я збираю колонку стипендій для кожного балу. Я спробував, але не
вийшов

Приємна відповідь @ Рон Туффін, проте якщо у вас два діапазони, як 10-20, 100-200, замовлення не працює. Ви б замовили, як 10-20, 100-200,20-30 тощо. Будь-яка порада для замовлення?
Zo Has

2
@ZoHas це трохи хак, але це працює: замовити len (t.range), t.range
Рон Туффін

Краща відповідь на stackoverflow.com/questions/14730380/…
Грім

1
Якщо у вас все ще виникають проблеми з синтаксисом, перевірте цю відповідь: dba.stackexchange.com/questions/22491/…
Роберт Хоскінг

33

Альтернативний підхід передбачає збереження діапазонів у таблиці, а не вбудовувати їх у запит. Ви закінчите таблицю, назвіть її Діапазони, що виглядає приблизно так:

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

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

Дякую! Я спробував це, і основна ідея чудово працює, хоча синтаксис, який мені довелося використати, трохи інший. Потрібно лише перше ключове слово "регістр", а потім після останньої умови, перед "як діапазон" вам потрібне ключове слово "кінець". Крім цього, працював чудово - спасибі!
Х'ю

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-сервера. Math FTW!

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

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

Не перевірений, але ви зрозумієте ідею ...


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

Переконайтеся, що ви використовуєте слово, яке не є "діапазон", якщо ви перебуваєте в 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;

3
було б корисно, якщо ви можете додати пояснення щодо того, як ваш запит вирішує проблему.
девлін карнат

-1

Можливо, ви питаєте про те, щоб продовжувати такі речі ...

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

Не у всіх двигунах RDBMS є правила!

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.