4.6

Show how to define the view student_grades(ID,GPA) giving the grade-point average of each student, based on the query in Exercise 3.2; recall that we use a relation grade_points(grade,points) to get the numeric points associated with a letter grade. Make sure your view definition correctly handles the case of null values for the grade attribute of the takes relation.


We should not add credits for courses with a null grade; further, to correctly handle the case where a student has not completed any course, we should make sure we don’t divide by zero, and should instead return a null value.

We break the query into a subquery that finds sum of credits and sum of credit-grade-points, taking null grades into account. The outer query divides the above to get the average, taking care of divide by zero.

CREATE VIEW student_grades(ID, GPA) AS 
    SELECT ID, credit_points / DECODE(credit_sum, 0, null, credit_sum)
    FROM ((SELECT ID,SUM(DECODE(grade, null, 0, credits)) AS credit_sum,
        SUM(DECODE(grade,null,0,credits*points)) AS credit_points
        FROM (takes NATURAL JOIN course) NATURAL LEFT OUTER JOIN grade_points
        GROUP BY ID)
        
        UNION
        
        SELECT ID,null,null
        FROM student
        WHERE ID NOT IN (SELECT ID FROM takes))
    

The view defined above takes care of null grades by considering the credit points to be 0 and not adding the corresponding credits in credit_sum.

The query above ensures that a student who has not taken any course with non-null credits, and has credit_sum = 0 gets a GPA of null. This avoids the division by zero, which would otherwise have resulted.

In systems that do not support decode, an alternative is the case construct. Using case, the solution would be written as follows:

CREATE VIEW student_grades(ID, GPA) AS 
    SELECT 
        ID, 
        credit_points / (
            CASE 
                WHEN credit_sum = 0 THEN NULL
                ELSE credit_sum
            END
        )
    FROM(
        (
            SELECT 
                ID,
                SUM ( 
                    CASE 
                        WHEN grade IS NULL THEN 0 
                        ELSE credits 
                    END
                ) AS credit_sum,
                SUM (
                    CASE 
                        WHEN grade IS NULL THEN 0 
                        ELSE credits * points 
                    END
                ) AS credit_points
            FROM (takes NATURAL JOIN course) NATURAL LEFT OUTER JOIN grade_points
            GROUP BY ID
        ) 
        
        UNION
        
        SELECT ID,null,null
        FROM student
        WHERE ID NOT IN (SELECT ID FROM takes)
    ) AS ss
    

An alternative way of writing the above query would be to use student natural left outer join gpa, in order to consider students who have not taken any course.