5.21
Modify the recursive query in Figure 5.16 to define a relation
prereq_depth(course_id,prereq_id,depth)
where the attribute depth indicates how many levels of intermediate prerequisites there are between the course and the prerequisite. Direct prerequisites have a depth of 0. Note that a prerequisite course may have multiple depths and thus may appear more than once.
Note that the below query is going to give us an infinite table, if there is a cycle in the prereq
relation. For example suppose course A
has course B
as a prerequisite and course B
has course A
as a prerequisite (i.e. the tuples (A,B)
and (B,A)
both appear inside of prereq
). In the query of Figure 5.16 since there was no attribute varying (like we have depth
here), fixed point will be reached even if there are such cycles. (Read more in section 5.4.2).
WITH RECURSIVE prereq_depth(course_id,prereq_id,depth) AS (
SELECT course_id,prereq_id,0 AS depth
FROM prereq
UNION
SELECT prereq_depth.course_id, prereq.prereq_id, prereq_depth.depth + 1
FROM prereq_depth, prereq
WHERE prereq_depth.prereq_id = prereq.course_id
)SELECT * FROM prereq_depth;
The following query tries to solve this problem.
CREATE TABLE temp_prereq (
VARCHAR(8),
course_id VARCHAR(8),
prereq_id PRIMARY KEY (course_id,prereq_id)
);
-- make_sure_it_is_not_duplicate returns true if it is the first time
-- that (course_id,prereq_id) is passed into the function. false otherwise.
CREATE OR REPLACE FUNCTION make_sure_it_is_not_duplicate(course_id VARCHAR(8), prereq_id VARCHAR(8))
BOOLEAN AS
RETURNS
$$BEGIN
IF ((course_id,prereq_id) IN (SELECT * FROM temp_prereq)) THEN
RETURN 'f'; -- it is a duplicate
ELSE
INSERT INTO temp_prereq VALUES (course_id, prereq_id);
RETURN 't'; -- not a duplicate
END IF;
END;
$$
LANGUAGE plpgsql;
WITH RECURSIVE prereq_depth(course_id,prereq_id,depth) AS (
SELECT course_id,prereq_id,0 AS depth
FROM prereq
WHERE make_sure_it_is_not_duplicate(course_id,prereq_id) AND
<> prereq_id -- the case where the course is a prerequisite of itself.
course_id UNION
SELECT prereq_depth.course_id, prereq.prereq_id, prereq_depth.depth + 1
FROM prereq_depth, prereq
WHERE prereq_depth.prereq_id = prereq.course_id
AND make_sure_it_is_not_duplicate(prereq_depth.course_id, prereq.prereq_id)
)SELECT * FROM prereq_depth;
-- we don't need temp_prereq anymore. It has served its purpose.
DROP TABLE temp_prereq;