【问题标题】:In SQL, how can you "group by" in ranges?在 SQL 中,如何在范围内“分组”?
【发布时间】:2010-09-18 22:39:42
【问题描述】:

假设我有一个带有数字列的表(我们称之为“分数”)。

我想生成一个计数表,显示每个范围内分数出现的次数。

例如:

分数范围 |出现次数 ------------------------------------- 0-9 | 11 10-19 | 14 20-29 | 3 ... | ...

在此示例中,有 11 行得分在 0 到 9 之间,14 行得分在 10 到 19 之间,3 行得分在 20 到 29 之间。

有没有简单的方法来设置它?你有什么推荐的?

【问题讨论】:

    标签: sql sql-server tsql


    【解决方案1】:

    在 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 等。订购时有什么提示吗?
    • @ZoHas 这有点小技巧,但这很有效:按 len(t.range),t.range 排序
    • 如果您仍然有语法问题,请查看此答案:dba.stackexchange.com/questions/22491/…
    【解决方案2】:

    另一种方法是将范围存储在表中,而不是将它们嵌入到查询中。您最终会得到一个表格,称为 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
    

    这确实意味着设置一个表格,但是当所需范围发生变化时,它会很容易维护。无需更改代码!

    【讨论】:

    【解决方案3】:

    我在这里看到的答案不适用于 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
    

    编辑:见 cmets

    【讨论】:

    • 这可能是因为我正在使用的 SQLServer 版本,但为了让你的示例正常工作(我在投票之前测试了它们)我不得不在“案例”之后移动“分数”在每个“时间”之后。
    • 您是对的,感谢您的更正。显然,当您将变量放在关键字“case”之后时,您只能进行完全匹配,而不能进行表达式。我从回答问题中学到的东西和从提问中学到的东西一样多。 :-)
    【解决方案4】:

    在 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
    

    【讨论】:

      【解决方案5】:

      James Curran 的回答在我看来是最简洁的,但输出不正确。对于 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 临时表来测试它,我只是用 0 到 99 之间的随机数填充了 100 行。

      【讨论】:

      • 啊...实际上花时间创建表是有好处的。 (我使用了一个现有表,其中行太少,范围太小)
      【解决方案6】:
      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
      

      【讨论】:

      • 谢谢!我试过这个,基本的想法很好用,尽管我必须使用的语法略有不同。只需要第一个“case”关键字,然后在最后一个条件之后,在“as range”之前,您需要关键字“end”。除此之外,效果很好-谢谢!
      【解决方案7】:
      select cast(score/10 as varchar) + '-' + cast(score/10+9 as varchar), 
             count(*)
      from scores
      group by score/10
      

      【讨论】:

      • 我喜欢这个,但是如果你要显示它,你必须修复查询之外的范围。
      • 如果您决定修复您的答案,您需要将第一行的 score/10 更改为 (score/10)*10,否则您将得到 3 - 12 而不是 30 -39 等。根据我在下面的帖子,您可以添加一个 order by 以按正确的顺序获取结果。
      【解决方案8】:

      这将使您不必指定范围,并且应该与 SQL 服务器无关。数学FTW!

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

      【讨论】:

        【解决方案9】:

        我会做一些不同的事情,以便它可以扩展而不必定义每个案例:

        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
        

        未经测试,但你明白了......

        【讨论】:

          【解决方案10】:
          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)
          

          【讨论】:

            【解决方案11】:
            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 中,请确保使用“范围”以外的单词,否则运行上述示例时会出错。

            【讨论】:

              【解决方案12】:

              因为被排序的列 (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
              

              【讨论】:

                【解决方案13】:

                试试

                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;
                

                【讨论】:

                • 如果您能添加一些关于您的查询如何解决问题的解释,将会很有帮助。
                【解决方案14】:
                select t.range as score, count(*) as Count 
                from (
                      select UserId,
                         case when isnull(score ,0) >= 0 and isnull(score ,0)< 5 then '0-5'
                                when isnull(score ,0) >= 5 and isnull(score ,0)< 10 then '5-10'
                                when isnull(score ,0) >= 10 and isnull(score ,0)< 15 then '10-15'
                                when isnull(score ,0) >= 15 and isnull(score ,0)< 20 then '15-20'               
                         else ' 20+' end as range
                         ,case when isnull(score ,0) >= 0 and isnull(score ,0)< 5 then 1
                                when isnull(score ,0) >= 5 and isnull(score ,0)< 10 then 2
                                when isnull(score ,0) >= 10 and isnull(score ,0)< 15 then 3
                                when isnull(score ,0) >= 15 and isnull(score ,0)< 20 then 4             
                         else 5  end as pd
                     from score table
                     ) t
                
                group by t.range,pd order by pd
                

                【讨论】:

                  【解决方案15】:

                  我来这里是因为我有类似的问题,但我发现简短的答案是错误的,而连续的“案例何时”的答案需要大量工作,并且在我的代码中看到任何重复的内容都会伤害我的眼睛。所以这里是解决方案

                  SELECT --MIN(score), MAX(score),
                      [score range] = CAST(ROUND(score-5,-1)AS VARCHAR) + ' - ' + CAST((ROUND(score-5,-1)+10)AS VARCHAR),
                      [number of occurrences] = COUNT(*)
                  FROM order
                  GROUP BY  CAST(ROUND(score-5,-1)AS VARCHAR) + ' - ' + CAST((ROUND(score-5,-1)+10)AS VARCHAR)
                  ORDER BY MIN(score)
                  
                  
                  

                  【讨论】:

                    【解决方案16】:

                    也许你在问是否要让这些事情继续下去......

                    当然,您将为查询调用全表扫描,如果包含需要计算(聚合)的分数的表很大,您可能需要一个性能更好的解决方案,您可以创建一个辅助表并使用规则,例如on insert - 你可以调查一下。

                    但并非所有 RDBMS 引擎都有规则!

                    【讨论】:

                      猜你喜欢
                      • 2011-08-05
                      • 1970-01-01
                      • 1970-01-01
                      • 2011-01-29
                      • 1970-01-01
                      • 1970-01-01
                      • 1970-01-01
                      • 1970-01-01
                      • 1970-01-01
                      相关资源
                      最近更新 更多