【问题标题】:How to do 'grading' in pure (i.e. ANSI) SQL如何在纯(即 ANSI)SQL 中进行“分级”
【发布时间】:2010-06-23 14:58:03
【问题描述】:

我有一个看起来像这样的表:

CREATE TABLE student_results(id integer, name varchar(32), score float);

让我们做以下两个假设:

  1. 假设分数从 0 到最大 100。
  2. 假设我想以 10 的“步长”为学生评分

所以我想应用以下评分:

Score      Grade Awarded
0-10       GRADE9
10-20      GRADE8
20-30      GRADE7
30-40      GRADE6
40-50      GRADE5
50-60      GRADE4
60-70      GRADE3
70-80      GRADE2
80-90      GRADE1
99-100     GENIUS

我想编写一个接受以下输入参数的 SQL 查询:

lowest score:  0 in this example
highest score: 100 in this example
'step' size:   10 in this example

和以往一样,如果可能的话,我想使用 ANSI SQL 编写这样的查询。如果我必须选择一个数据库,那么按照偏好递减的顺序,它必须是:

  • PostgreSQL
  • MySQL

能否以上表为例,解释一下我如何能够编写一个执行这种评分的 SQL 查询?

[编辑]

Sample input data

1, 'homer', 10.5
2. 'santas little helper', 15.2
3, 'bart',  20.5
4, 'marge', 40.5
5. 'lisa', 100

我将有一个 SQL 函数grade_rank() - 对学生进行排名:

函数grade_rank()的参数是:

1st argument: LOWEST possible score value
2nd argument: HIGHEST possible score value
3rd argument: step size, which determines the levels/divisions between the ranks

select id, name, grade_rank(0,100, 10) grade from student_scores;

输出(基于上面的输入)应该是:

1, homer,               GRADE9
2. santas liitle helper GRADE9
3, bart,                GRADE8
4, marge,               GRADE6
5. lisa,                GENIUS

【问题讨论】:

  • 您能否发布一个此类查询的预期输出示例?
  • 您是否在问如何使用BETWEEN 而不是= 进行连接?
  • 您的原始评分列表仅处理整数,但您在编辑部分中的示例数据是指介于原始边界之间的实数。例如。 10.5 介于 GRADE9 和 GRADE8 之间。您应该编辑一个或另一个,以准确显示您希望它如何工作。我认为到目前为止提供的答案仅适用于整数值。
  • 让步数从 0-10、10-20 开始不是更有意义,这样您的所有步数都相等吗?现在你的第一步实际上是大小 11,你的最后一步是 9。
  • @ivar + mike:点了。我只是试图展示一般概念,而忽略了成绩之间存在差距的事实。我将编辑数据,使成绩之间没有差距。顺便说一句,分数是实数(不是整数)

标签: sql mysql postgresql


【解决方案1】:

这样你可以做得更一般,但等级会倒序,从1到N,即

  • 0-10 1级
  • 10-20年级2
  • 20-30年级3
  • 30-40 4级
  • ...

例如使用值 第 10 步 得分 43

这个算法

SELECT (((score-1)-((score-1) % step))/step)+1

将返回 5

您不必知道最高分。如果最高分是 100 分,没有人能超过 100 分,你只需要决定步数的大小。例如,如果您希望步长为 25。知道最高分数为 100,则将有 4 个年级。因此,通过将 step level 设置为 25 而不是 10,结果将是 2,即 2 级。

SELECT (((43-1)-((43-1) % 25))/25)+1

也许不是您所期望的正确,但可能足够通用以至于有用。这是该函数在 SQL 中的样子。

CREATE OR REPLACE FUNCTION grade_rank(IN score integer, IN step integer, OUT rank integer)
AS 'SELECT ((($1-1)-(($1-1) % $2))/$2)+1'
LANGUAGE 'SQL';

现在调用这个函数

select * from grade_rank(43,10)

返回 5。

这是 plpgsql 等价物:

CREATE OR REPLACE FUNCTION grade_rank(IN score integer, IN step integer)
  RETURNS integer AS
$BODY$ 
DECLARE rank integer;
BEGIN
    SELECT (((score-1)-((score-1) % step))/step)+1 INTO rank;
    RETURN rank;
END;
$BODY$
  LANGUAGE 'plpgsql';

【讨论】:

    【解决方案2】:

    有几个选项:

    1) 创建一个包含成绩(最低、最高)的表并加入该表

    SELECT score, grades.grade
    FROM table 
         INNER JOIN grades ON table.score >= grades.min AND table.score <= grades.max
    

    2) 创建一个临时表(甚至从 DUAL 中选择)并加入它,例如在上面你可以编写子查询而不是成绩

    (SELECT 0 as MIN, 10 as max, 'GRADE9' as grade FROM DUAL
     UNION ALL
     SELECT 11 as MIN, 20 as max, 'GRADE8' as grade FROM DUAL
     UNION ALL
     ...
     SELECT 91 as min, 100 as max, 'GENIUS' as grade FROM DUAL
     ) AS grades
    

    3) 用一个案例

    SELECT score,
       CASE WHEN score = 0 THEN 'GRADE9'
            WHEN score >= 1 AND score <= 90 THEN 'GRADE' || (9 - (score-1) / 10)
            WHEN score >= 91 THEN 'GENIUS'
            ELSE 'ERROR'
       END grade
    FROM table
    

    (请注意,在上述查询中,您可以将 0、100 和 10 替换为最低、最高和步长以获取动态 sql)

    4) 创建user function(但这将获得特定于R​​DBMS)

    【讨论】:

      【解决方案3】:

      这样的?

      SELECT 
           [name],
           score,
           CASE 
                WHEN score > @max - @stepsize THEN 'GENIUS'
                ELSE CONCAT('GRADE',
                          CAST(
                               FLOOR((@max - score)/@stepsize - 
                                    CASE score 
                                         WHEN @min THEN 1
                                         ELSE 0
                                    END CASE
                               ) as char(3)
                          )
                     )
           END CASE
      FROM 
           student_results
      

      您可能需要稍微调整一下 - 我不太了解 min 部分(是否仅因为最后一个范围比其他范围大 1 号而使用它?)

      编辑

      将@step 重命名为@stepsize 以使每个 Ivar 更清楚(@step 可能被误解为步数)

      【讨论】:

      • +1 - 我相信你的 SQL 比我的更标准,也不错
      • -1 仅适用于当前的分级量表。如果 @min != 0、@step != 10 或 @max != 100... 将失败
      • @Ivar Bonsaksen - 你能提供一个组合,并记录哪个失败?我尝试了 0-110 STEP 11,效果很好。谢谢!
      • 对不起...我的错,我将@step 解释为步数,而不是步长。用作步长的是我的查询将失败。如果您编辑答案,我将撤消我的 -1。 (否则不允许)
      • 我在中途也遇到了同样的困惑 :-)。完成。
      【解决方案4】:

      这个怎么样? (原来我使用了@steps,而不是@step。如果您更愿意指定@step,@steps 可以计算为@steps = (@highest-@lowest)/@step

      SET @lowest = 0;
      SET @highest = 100;
      SET @steps = 10;
      
      SELECT
          name,
          CASE
              WHEN score >= (@highest-@steps) THEN 'GENIUS'
              ELSE 
                  CONCAT(
                      'GRADE',
                      @steps-FLOOR((score-@lowest)/((@highest-@lowest)/@steps))-1)
          END
      FROM
          student_results
      

      每当您通过下一步时,这都会给您一个新的成绩。

      0-9.999   => GRADE1
      10-19.999 => GRADE2
      etc.
      

      【讨论】:

      • 我认为在测试“GENIUS”时需要从@highest 中减去@step。
      • 我想你可能想稍微调整一下。 90 分不等于 GRADE1(或者我计算错误,这通常会发生 :-))
      猜你喜欢
      • 1970-01-01
      • 2021-01-11
      • 1970-01-01
      • 2012-10-01
      • 1970-01-01
      • 1970-01-01
      • 2019-11-08
      • 1970-01-01
      • 2021-12-24
      相关资源
      最近更新 更多