Course Query
To dynamically query information about courses, we have to construct a rather involved view on the fly. This is necessary because the information is spread across multiple different tables and there are dynamically many course tracks in which a course may be involved depending on the event.
We construct this view from three main components:
The general
event.courses
table. Here lies general course information like title, description and orga notes.The custom course related datafields. These are extracted from the JSON-column
fields
which is part of theevent.courses
table, ut we have to do on the fly to determine the appropriate fields for that event.A series of track tables, containing information for the course in relation to all course tracks of the event. This component will be discussed in further detail below.
The final view will be constructed as follows.
event.courses as course
LEFT OUTER JOIN (
SELECT
course_field_columns, id
FROM
event.courses
WHERE
event_id = X
) AS course_fields ON courses.id = course_fields.id
LEFT OUTER JOIN (
track_tableX
) AS trackX ON courses.id = trackX.base_id
course_field_columns
will contain a JSON-cast from the fields
column for every relevant course field. track_tableX
is an example for one of the dynamic tables that are all joined as described.
The following columns will be available in this view:
course.id
course.course_id
This is magically replaced by “{nr}. {shortname}” linking to the course.course.event_id
course.nr
course.title
course.description
course.shortname
course.instructors
course.min_size
course.max_size
course.notes
course_fields.xfield_{field_name}
This is available for every custom data field with course association.track{track_id}.is_offered
track{track_id}.takes_place
track{track_id}.attendees
track{track_id}.intructors
track{track_id}.num_choices{rank}
This is available for every available rank in that track.
Note that some additional columns are present but omitted here, since they are not really useful like ``track{track_id}.base_id``.
The Track Tables
For every course track we gather three kinds of information:
Course segment data. This comes from the
event.course_segments
table and contains information whether the course is offered in the specific track and whether it actually takes place.Attendee/Instructor counts. This comes from the
event.registration_tracks
table and contains information about the number of attendees and instructors of a course in the specific track.Course choices data. This comes from the
event.course_choices
table and contains a count of how many (active) registrations have chosen a course in the specific track and at a specific track.
The track table starts out with a base table created by simply selecting all the appropriate course ids, that is all ids of courses belonging to the event. The course id is selected as base_id
so we can later use it to join the track tables to the other components. This is necessary because there will be multiple columns called id
in a single track table and POSTGRES wouldn’t know which to use in the JOIN
otherwise.
All the components of a track table start out with the same base table.
(SELECT id FROM event.courses WHERE event_id = X) AS c
This ensures that we get some kind of data in that component for all courses even if a course would not be present in that component otherwise. For example if a course is not present in the course choices part, we want the count of choices be 0 instead of NULL.
The Course Segment Table
This part conatins two layers. In the inner layer we start with the usual base table and join that with the event.course_segments
table, selecting the is_active
column from there.
Afterwards we select the actual information we need from that joined table, while coalescing the is_active
column so we get a bool instead of NULL
SELECT
c.id, COALESCE(is_active, False) AS takes_place,
is_active IS NOT NULL AS is_offered
FROM
(SELECT id FROM event.courses WHERE event_id = X) AS c
LEFT OUTER JOIN (
SELECT
is_active, course_id
FROM
event.course_segments
WHERE track_id = X
) AS segment ON c.id = segment.course_id
The Attendees Table
This part contains two layers. In the inner layer, we start with the usual base table and join that with the event.registration_tracks
table by joining on c.id = rt.course_id.
In the outer layer we count the registration ids while grouping by course id. Doing it this way results in a count of 0
instead of NULL
for courses without attendees.
SELECT
c.id, COUNT(registration_id) AS attendees
FROM
(SELECT id FROM event.courses WHERE event_id = X) AS c
LEFT OUTER JOIN (
SELECT
registration_id, course_id
FROM
event.registration_tracks
WHERE track_id = X
) AS rt ON c.id = rt.course_id
GROUP BY
c.id
The Instructors Table
This works just like the attendees
part of the track table, but we join on c.id = rt.course_instructor instead.
SELECT
c.id, COUNT(registration_id) AS instructors
FROM
(SELECT id FROM event.courses WHERE event_id = X) AS c
LEFT OUTER JOIN (
SELECT
registration_id, course_instructor
FROM
event.registration_tracks
WHERE track_id = X
) AS rt ON c.id = rt.course_instructor
GROUP BY
c.id
The Course Choices Table
We have one of these tables for every possible rank in the specific track. So if a track allows up to 5 choices we have 5 of these tables.
This table contains three layers.
In the innermost layer we join event.course_choices
filtered by track and rank with event.registration_parts
(filtered by the part id corresponding with the specific track) via the registration id, so that we can get the registration status corresponsing to a course choice.
The middle layer starts with the usual base table, which we join with the innermost layer filtered by active registration stati.
In the outer layer we then count the registration ids while grouping by course id. See attendees
table for more information why we do that in this way.
SELECT
c.id, COUNT(status.registration_id) AS num_choicesX
FROM
(SELECT id FROM event.courses WHERE event_id = X) AS c
LEFT OUTER JOIN (
SELECT
choices.registration_id, choices.course_id
FROM
(
SELECT registration_id, course_id
FROM event.course_choices
WHERE rank = X AND track_id = X
) AS choices
LEFT OUTER JOIN (
SELECT
registration_id AS reg_id, status
FROM
event.registration_parts
WHERE
part_id = X
) AS reg_part
ON choices.registration_id = reg_part.reg_id
WHERE
status = ANY(X)
) AS status ON c.id = status.course_id
GROUP BY
c.id
The Complete View
The final view for course queries looks something like this:
event.courses AS course
LEFT OUTER JOIN (
SELECT
(fields->>'room')::varchar AS "xfield_room", id
FROM
event.courses
WHERE
event_id = 1
) AS course_fields ON course.id = course_fields.id
LEFT OUTER JOIN (
(
SELECT id AS base_id
FROM event.courses
WHERE event_id = 1
) AS base
LEFT OUTER JOIN (
SELECT
c.id, COALESCE(is_active, False) AS is_active
FROM
(SELECT id FROM event.courses WHERE event_id = 1) AS c
LEFT OUTER JOIN (
SELECT
is_active, course_id
FROM
event.course_segments
WHERE track_id = 1
) AS segment ON c.id = segment.course_id
) AS segment1 ON base_id = segment1.id
LEFT OUTER JOIN (
SELECT
c.id, COUNT(registration_id) AS attendees
FROM
(SELECT id FROM event.courses WHERE event_id = 1) AS c
LEFT OUTER JOIN (
SELECT
registration_id, course_id
FROM
event.registration_tracks
WHERE track_id = 1
) AS rt ON c.id = rt.course_id
GROUP BY
c.id
) AS attendees1 ON base_id = attendees1.id
LEFT OUTER JOIN (
SELECT
c.id, COUNT(status.registration_id) AS num_choices0
FROM
(SELECT id FROM event.courses WHERE event_id = 1) AS c
LEFT OUTER JOIN (
SELECT
choices.registration_id, choices.course_id
FROM
(
SELECT registration_id, course_id
FROM event.course_choices
WHERE rank = 0 AND track_id = 1
) AS choices
LEFT OUTER JOIN (
SELECT
registration_id AS reg_id, status
FROM
event.registration_parts
WHERE
part_id = 2
) AS reg_part
ON choices.registration_id = reg_part.reg_id
WHERE
status = ANY(ARRAY[1,2,3,4])
) AS status ON c.id = status.course_id
GROUP BY
c.id
) AS choices1_0 ON base_id = choices1_0.id LEFT OUTER JOIN (
SELECT
c.id, COUNT(status.registration_id) AS num_choices1
FROM
(SELECT id FROM event.courses WHERE event_id = 1) AS c
LEFT OUTER JOIN (
SELECT
choices.registration_id, choices.course_id
FROM
(
SELECT registration_id, course_id
FROM event.course_choices
WHERE rank = 1 AND track_id = 1
) AS choices
LEFT OUTER JOIN (
SELECT
registration_id AS reg_id, status
FROM
event.registration_parts
WHERE
part_id = 2
) AS reg_part
ON choices.registration_id = reg_part.reg_id
WHERE
status = ANY(ARRAY[1,2,3,4])
) AS status ON c.id = status.course_id
GROUP BY
c.id
) AS choices1_1 ON base_id = choices1_1.id LEFT OUTER JOIN (
SELECT
c.id, COUNT(status.registration_id) AS num_choices2
FROM
(SELECT id FROM event.courses WHERE event_id = 1) AS c
LEFT OUTER JOIN (
SELECT
choices.registration_id, choices.course_id
FROM
(
SELECT registration_id, course_id
FROM event.course_choices
WHERE rank = 2 AND track_id = 1
) AS choices
LEFT OUTER JOIN (
SELECT
registration_id AS reg_id, status
FROM
event.registration_parts
WHERE
part_id = 2
) AS reg_part
ON choices.registration_id = reg_part.reg_id
WHERE
status = ANY(ARRAY[1,2,3,4])
) AS status ON c.id = status.course_id
GROUP BY
c.id
) AS choices1_2 ON base_id = choices1_2.id LEFT OUTER JOIN (
SELECT
c.id, COUNT(status.registration_id) AS num_choices3
FROM
(SELECT id FROM event.courses WHERE event_id = 1) AS c
LEFT OUTER JOIN (
SELECT
choices.registration_id, choices.course_id
FROM
(
SELECT registration_id, course_id
FROM event.course_choices
WHERE rank = 3 AND track_id = 1
) AS choices
LEFT OUTER JOIN (
SELECT
registration_id AS reg_id, status
FROM
event.registration_parts
WHERE
part_id = 2
) AS reg_part
ON choices.registration_id = reg_part.reg_id
WHERE
status = ANY(ARRAY[1,2,3,4])
) AS status ON c.id = status.course_id
GROUP BY
c.id
) AS choices1_3 ON base_id = choices1_3.id
) AS track1 ON course.id = track1.base_id
LEFT OUTER JOIN (
(
SELECT id AS base_id
FROM event.courses
WHERE event_id = 1
) AS base
LEFT OUTER JOIN (
SELECT
c.id, COALESCE(is_active, False) AS is_active
FROM
(SELECT id FROM event.courses WHERE event_id = 1) AS c
LEFT OUTER JOIN (
SELECT
is_active, course_id
FROM
event.course_segments
WHERE track_id = 2
) AS segment ON c.id = segment.course_id
) AS segment2 ON base_id = segment2.id
LEFT OUTER JOIN (
SELECT
c.id, COUNT(registration_id) AS attendees
FROM
(SELECT id FROM event.courses WHERE event_id = 1) AS c
LEFT OUTER JOIN (
SELECT
registration_id, course_id
FROM
event.registration_tracks
WHERE track_id = 2
) AS rt ON c.id = rt.course_id
GROUP BY
c.id
) AS attendees2 ON base_id = attendees2.id
LEFT OUTER JOIN (
SELECT
c.id, COUNT(status.registration_id) AS num_choices0
FROM
(SELECT id FROM event.courses WHERE event_id = 1) AS c
LEFT OUTER JOIN (
SELECT
choices.registration_id, choices.course_id
FROM
(
SELECT registration_id, course_id
FROM event.course_choices
WHERE rank = 0 AND track_id = 2
) AS choices
LEFT OUTER JOIN (
SELECT
registration_id AS reg_id, status
FROM
event.registration_parts
WHERE
part_id = 2
) AS reg_part
ON choices.registration_id = reg_part.reg_id
WHERE
status = ANY(ARRAY[1,2,3,4])
) AS status ON c.id = status.course_id
GROUP BY
c.id
) AS choices2_0 ON base_id = choices2_0.id
) AS track2 ON course.id = track2.base_id
LEFT OUTER JOIN (
(
SELECT id AS base_id
FROM event.courses
WHERE event_id = 1
) AS base
LEFT OUTER JOIN (
SELECT
c.id, COALESCE(is_active, False) AS is_active
FROM
(SELECT id FROM event.courses WHERE event_id = 1) AS c
LEFT OUTER JOIN (
SELECT
is_active, course_id
FROM
event.course_segments
WHERE track_id = 3
) AS segment ON c.id = segment.course_id
) AS segment3 ON base_id = segment3.id
LEFT OUTER JOIN (
SELECT
c.id, COUNT(registration_id) AS attendees
FROM
(SELECT id FROM event.courses WHERE event_id = 1) AS c
LEFT OUTER JOIN (
SELECT
registration_id, course_id
FROM
event.registration_tracks
WHERE track_id = 3
) AS rt ON c.id = rt.course_id
GROUP BY
c.id
) AS attendees3 ON base_id = attendees3.id
LEFT OUTER JOIN (
SELECT
c.id, COUNT(status.registration_id) AS num_choices0
FROM
(SELECT id FROM event.courses WHERE event_id = 1) AS c
LEFT OUTER JOIN (
SELECT
choices.registration_id, choices.course_id
FROM
(
SELECT registration_id, course_id
FROM event.course_choices
WHERE rank = 0 AND track_id = 3
) AS choices
LEFT OUTER JOIN (
SELECT
registration_id AS reg_id, status
FROM
event.registration_parts
WHERE
part_id = 3
) AS reg_part
ON choices.registration_id = reg_part.reg_id
WHERE
status = ANY(ARRAY[1,2,3,4])
) AS status ON c.id = status.course_id
GROUP BY
c.id
) AS choices3_0 ON base_id = choices3_0.id LEFT OUTER JOIN (
SELECT
c.id, COUNT(status.registration_id) AS num_choices1
FROM
(SELECT id FROM event.courses WHERE event_id = 1) AS c
LEFT OUTER JOIN (
SELECT
choices.registration_id, choices.course_id
FROM
(
SELECT registration_id, course_id
FROM event.course_choices
WHERE rank = 1 AND track_id = 3
) AS choices
LEFT OUTER JOIN (
SELECT
registration_id AS reg_id, status
FROM
event.registration_parts
WHERE
part_id = 3
) AS reg_part
ON choices.registration_id = reg_part.reg_id
WHERE
status = ANY(ARRAY[1,2,3,4])
) AS status ON c.id = status.course_id
GROUP BY
c.id
) AS choices3_1 ON base_id = choices3_1.id LEFT OUTER JOIN (
SELECT
c.id, COUNT(status.registration_id) AS num_choices2
FROM
(SELECT id FROM event.courses WHERE event_id = 1) AS c
LEFT OUTER JOIN (
SELECT
choices.registration_id, choices.course_id
FROM
(
SELECT registration_id, course_id
FROM event.course_choices
WHERE rank = 2 AND track_id = 3
) AS choices
LEFT OUTER JOIN (
SELECT
registration_id AS reg_id, status
FROM
event.registration_parts
WHERE
part_id = 3
) AS reg_part
ON choices.registration_id = reg_part.reg_id
WHERE
status = ANY(ARRAY[1,2,3,4])
) AS status ON c.id = status.course_id
GROUP BY
c.id
) AS choices3_2 ON base_id = choices3_2.id
) AS track3 ON course.id = track3.base_id