views:

108

answers:

4

I have a table that looks something like this:

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

Lets make the following two assumptions:

  1. assume that the score goes from 0 to a maximum of 100.
  2. assume that I want to grade students in 'step sizes' of 10

so I want to apply the following grading:

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

I would like to write an SQL query that takes in the following input arguments:

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

As ever, if possible, I would like to write such a query using ANSI SQL. If I have to choose a database, then in order of DECREASING preference, it would have to be:

  • PostgreSQL
  • MySQL

Could someone please explain how I may be able to write an SQL query that does this kind of grading, using the above table as an example?

[Edit]

Sample input data

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

I will have an SQL function grade_rank() - that ranks the student:

The arguments for function grade_rank() are :

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;

the output (based on the input above) should be:

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

something like this?

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

you might have to tweak it a bit - i didn't quite understand the min part (is it used only because the last range is 1 size bigger than the other ranges?)

Edit

Renamed @step to @stepsize for clarity per Ivar (@step could be misinterpreted as step count)

potatopeelings
+1 - I believe your SQL is more standard then mine, also nice case
Unreason
-1 Will only work for current grading scale. Will fail if @min != 0, @step != 10 or @max != 100...
Ivar Bonsaksen
@Ivar Bonsaksen - could you provide a combination, and record which fails? I tried 0-110 STEP 11 and it worked fine. Thanks!
potatopeelings
Sorry... my bad, I interpreted @step as number of steps, rather than step size. Used as step size it is my query that will fail. If you edit your answer I will undo my -1. (Not allowed otherwise)
Ivar Bonsaksen
i pretty much ran into the same confusion part way :-). Done.
potatopeelings
+1  A: 

There are a few options:

1) create a table with grades (min, max) and join on that table

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

2) create a temporary table (or even select from DUAL) and join on it, for example in the above instead of grades you can write subquery

(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) use a case

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

(notice that in the above query you could substitute 0, 100 and 10 with lowest, highest and step to get dynamic sql)

4) create user function (but this will get RDBMS-specific)

Unreason
+1  A: 

How about this? (Turns out I've used @steps, as in number of steps, instead of @step. If you rather specify @step, @steps could be calculated as @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

This will give you a new grade whenever you pass the next step.

0-9.999   => GRADE1
10-19.999 => GRADE2
etc.
Ivar Bonsaksen
I think you need to subtract @step from @highest when testing for 'GENIUS'.
Mike
@Ivar - i think you might want to tweak it a bit. A score of 90 doesn't give GRADE1 (or I'm calculating wrong, which usually happens :-))
potatopeelings
+1  A: 

In this way you can do it more general but the grades will be in reverse order, starting from 1 up to N, ie

  • 0-10 Grade1
  • 10-20 Grade2
  • 20-30 Grade3
  • 30-40 Grade4
  • ...

For example using the values step 10 score 43

This algorithm

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

will return 5

You don't have to know the maximum score. If the max score is 100 no one will be able to perform higher than 100, you just have to decide the size of the steps. For example if you want a step size of 25. Knowing that the maximum score is 100 there will be 4 grade levels. So by setting step level to 25 instead of 10 the result will be 2, ie grade 2.

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

Perhaps not right on spot what you expected but maybe generic enough to be useful. Here is how the function would look like in 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';

Now calling this function

select * from grade_rank(43,10)

returns 5.

And this the plpgsql equivalent:

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';
John P