Lodgement Query
To dynamically query information about lodgements, 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 event parts in which a lodgement may be involved depending on the event.
We construct this view from the following main components:
The general
event.lodgements
table. Here lies general lodgement information like title, capacity and orga notes.The general
event.lodgement_groups
table. Every lodgement may belong to one lodgement group.The custom lodgement related datafields. These are extracted from the JSON-column
fields
which is part of theevent.lodgements
table, but we have to do this on the fly to determine the appropriate fields for that event.A series of part tables, containing information for the lodgement in relation to all event parts of the event. This component will be discussed in further detail below.
The final view will be constructed as follows (slightly simplified).
(
SELECT
id, id as lodgement_id, event_id,
title, regular_capacity, camping_mat_capacity, notes, group_id
FROM
event.lodgements
) AS lodgement
LEFT OUTER JOIN (
SELECT
id, title, regular_capacity, camping_mat_capacity
FROM
event.lodgement_groups
) AS lodgement_group ON lodgement.group_id = lodgement_group.id
LEFT OUTER JOIN (
SELECT
*course_field_columns*, id
FROM
event.courses
WHERE
event_id = X
) AS lodgement_field ON lodgement.id = lodgement_fields.id
LEFT OUTER JOIN (
part_tableX
) AS partX ON lodgement.id = partX.base_id
lodgement_field_columns
will contain a JSON-cast from the fields
column for every relevant lodgement field.
part_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:
lodgement.id
lodgement.lodgement_id
This is magically replaced by “{title}” linking to the lodgement.lodgement.event_id
lodgement.title
lodgement.regular_capacity
lodgement.notes
lodgement.group_id
lodgement.camping_mat_capacity
lodgement_group.title
lodgement_group.regular_capacity
lodgement_group.camping_mat_capacity
lodgement_fields.xfield_{field_name}
This is available for every custom data field with course association.part{part_id}.regular_inhabitants
part{part_id}.camping_mat_inhabitants
part[part_id}.total_inhabitants
part{part_id}.group_regular_inhabitants
part{part_id}.group_camping_mat_inhabitants
part[part_id}.group_total_inhabitants
Note that some additional columns are present but omitted here, since they are not really useful like ``part{part_id}.base_id``.
Implementation Details
In this section we discuss some more involved details of the implementation. These need not be considered when using the query view, rather you need to keep this in mind when chaning how the view is constructed.
Temporarily casting the group_id
A lodgement can either be linked to a lodgement group via the event.lodgements.group_id
foreign key to the
event.lodgement_groups.id
column, but it can also not belong to any lodgement group. For practical reasons we want
to group all lodgements that don’t belong to any group together, but POSTGRES won’t allow joining tables using
NULL = NULL
. Due to this we need to temporarily cast the nullable event.lodgements.group_id
column to a
non-nullable tmp_id
column by replacing NULL
-entries with -1
, like this:
SELECT
-- replace NULL ids with temp value so we can join.
id, COALESCE(group_id, -1) AS tmp_group_id
FROM
event.lodgements
We also need to create an artificial row for the event.ldogement_groups
table to join to, like this:
(
SELECT
id AS tmp_id, title
FROM
event.lodgement_groups
)
UNION
(
SELECT
-1, ''
)
Casting nested sums to bigint
Summing rows of integer datatypes with POSTGRES converts the result to the next bigger numerical datatype. Summing
integer
results in bigint
. Summing bigint
however results in numeric
, which is also used to store
fixed-point numbers and is thus converted to decimal.Decimal
by psycopg2
. To avoid this we cast the sums for
group inhabitants to bigint, since we do not expect to need lodgement groups with more than 10^19 inhabitants.
Note that these sums are also normalized to zero in case a NULL
-value occurs, because there are no
inhabitants/lodgements for a lodgement (group).
The Part Tables
For every event part we gather the following data points:
Regular inhabitants
Reserve inhabitants
Total inhabitants
We gather these both for each lodgement individually and summed for each lodgement group.
The part table starts out with a base table created by selecting all the appropriate lodgement ids, aswell as the
corresponding lodgement group ids. The lodgement id is selected as base_id
so we can later use it to join the
part tables to the other components. This is necessary because there will be multiple columns called id
in
a single part table and POSTGRES wouldn’t know which to use in the JOIN
otherwise:
(
SELECT
id as base_id, COALESCE(group_id, -1) AS tmp_group_id
FROM
event.lodgements
WHERE
event_id = X
) AS base
The part table consists of two components, both of which use the inhabitants_view. The first is just the inhabitants_view itself:
SELECT
id, tmp_group_id,
COALESCE(rp_regular.inhabitants, 0) AS regular_inhabitants,
COALESCE(rp_camping_mat.inhabitants, 0) AS camping_mat_inhabitants,
COALESCE(rp_total.inhabitants, 0) AS total_inhabitants
FROM
(
SELECT id, COALESCE(group_id, -1) as tmp_group_id
FROM event.lodgements
WHERE event_id = X
) AS l
LEFT OUTER JOIN (
*regular_inhabitants_counter*
) AS rp_regular ON l.id = rp_regular.lodgement_id
LEFT OUTER JOIN (
*camping_mat_inahbitants_counter*
) AS rp_camping_mat ON l.id = rp_camping_mat.lodgement_id
LEFT OUTER JOIN (
*total_inhabitants_counter*
) AS rp_total ON l.id = rp_total.lodgement_id
The second component is the group_inhabitants_view:
SELECT
tmp_group_id,
COALESCE(SUM(regular_inhabitants)::bigint, 0) AS group_regular_inhabitants,
COALESCE(SUM(camping_mat_inhabitants)::bigint, 0) AS group_camping_mat_inhabitants,
COALESCE(SUM(total_inhabitants)::bigint, 0) AS group_total_inhabitants
FROM (
*inhabitants_view*
) AS inhabitants_viewX
GROUP BY
tmp_group_id
The inhabitants counter
The inhabitants counter is a simple query where all inhabitants (with a specific camping_mat status are counted:
SELECT
lodgement_id, COUNT(registration_id) AS inhabitants
FROM
event.registration_parts
WHERE
part_id = X
*camping_mat_condition*
GROUP BY
lodgement_id
Where camping_mat condition is either “is_camping_mat = True”, “is_camping_mat = False” or nothing, for regular, camping_mat, total inhabitants respectively.
The Complete View
(
SELECT
id, id as lodgement_id, event_id,
title, regular_capacity, camping_mat_capacity, notes, group_id
FROM
event.lodgements
) AS lodgement
LEFT OUTER JOIN (
SELECT
-- replace NULL ids with temp value so we can join.
id, COALESCE(group_id, -1) AS tmp_group_id
FROM
event.lodgements
WHERE
event_id = 1
) AS tmp_group ON lodgement.id = tmp_group.id
LEFT OUTER JOIN (
SELECT
(fields->>'contamination')::varchar AS "xfield_contamination",
id
FROM
event.lodgements
WHERE
event_id = 1
) AS lodgement_fields ON lodgement.id = lodgement_fields.id
LEFT OUTER JOIN (
SELECT
tmp_id, title, regular_capacity, camping_mat_capacity
FROM (
(
(
SELECT
id AS tmp_id, title
FROM
event.lodgement_groups
WHERE
event_id = 1
)
UNION
(
SELECT
-1, ''
)
) AS group_base
LEFT OUTER JOIN (
SELECT
COALESCE(group_id, -1) as tmp_group_id,
SUM(regular_capacity) as regular_capacity,
SUM(camping_mat_capacity) as camping_mat_capacity
FROM
event.lodgements
WHERE
event_id = 1
GROUP BY
tmp_group_id
) AS group_totals ON group_base.tmp_id = group_totals.tmp_group_id
)
) AS lodgement_group ON tmp_group.tmp_group_id = lodgement_group.tmp_id
LEFT OUTER JOIN (
(
SELECT
id as base_id, COALESCE(group_id, -1) AS tmp_group_id
FROM
event.lodgements
WHERE
event_id = 1
) AS base
LEFT OUTER JOIN (
SELECT
id, tmp_group_id,
COALESCE(rp_regular.inhabitants, 0) AS regular_inhabitants,
COALESCE(rp_camping_mat.inhabitants, 0) AS camping_mat_inhabitants,
COALESCE(rp_total.inhabitants, 0) AS total_inhabitants
FROM
(
SELECT id, COALESCE(group_id, -1) as tmp_group_id
FROM event.lodgements
WHERE event_id = 1
) AS l
LEFT OUTER JOIN (
SELECT
lodgement_id, COUNT(registration_id) AS inhabitants
FROM
event.registration_parts
WHERE
part_id = 1
AND is_camping_mat = False
GROUP BY
lodgement_id
) AS rp_regular ON l.id = rp_regular.lodgement_id
LEFT OUTER JOIN (
SELECT
lodgement_id, COUNT(registration_id) AS inhabitants
FROM
event.registration_parts
WHERE
part_id = 1
AND is_camping_mat = True
GROUP BY
lodgement_id
) AS rp_camping_mat ON l.id = rp_camping_mat.lodgement_id
LEFT OUTER JOIN (
SELECT
lodgement_id, COUNT(registration_id) AS inhabitants
FROM
event.registration_parts
WHERE
part_id = 1
GROUP BY
lodgement_id
) AS rp_total ON l.id = rp_total.lodgement_id
) AS inhabitants_view1 ON base.base_id = inhabitants_view1.id
LEFT OUTER JOIN (
SELECT
tmp_group_id,
COALESCE(SUM(regular_inhabitants)::bigint, 0) AS group_regular_inhabitants,
COALESCE(SUM(camping_mat_inhabitants)::bigint, 0) AS group_camping_mat_inhabitants,
COALESCE(SUM(total_inhabitants)::bigint, 0) AS group_total_inhabitants
FROM (
SELECT
id, tmp_group_id,
COALESCE(rp_regular.inhabitants, 0) AS regular_inhabitants,
COALESCE(rp_camping_mat.inhabitants, 0) AS camping_mat_inhabitants,
COALESCE(rp_total.inhabitants, 0) AS total_inhabitants
FROM
(
SELECT id, COALESCE(group_id, -1) as tmp_group_id
FROM event.lodgements
WHERE event_id = 1
) AS l
LEFT OUTER JOIN (
SELECT
lodgement_id, COUNT(registration_id) AS inhabitants
FROM
event.registration_parts
WHERE
part_id = 1
AND is_camping_mat = False
GROUP BY
lodgement_id
) AS rp_regular ON l.id = rp_regular.lodgement_id
LEFT OUTER JOIN (
SELECT
lodgement_id, COUNT(registration_id) AS inhabitants
FROM
event.registration_parts
WHERE
part_id = 1
AND is_camping_mat = True
GROUP BY
lodgement_id
) AS rp_camping_mat ON l.id = rp_camping_mat.lodgement_id
LEFT OUTER JOIN (
SELECT
lodgement_id, COUNT(registration_id) AS inhabitants
FROM
event.registration_parts
WHERE
part_id = 1
GROUP BY
lodgement_id
) AS rp_total ON l.id = rp_total.lodgement_id
) AS inhabitants_view1
GROUP BY
tmp_group_id
) AS group_inhabitants_view1 ON base.tmp_group_id = group_inhabitants_view1.tmp_group_id
) AS part1 ON lodgement.id = part1.base_id
LEFT OUTER JOIN (
(
SELECT
id as base_id, COALESCE(group_id, -1) AS tmp_group_id
FROM
event.lodgements
WHERE
event_id = 1
) AS base
LEFT OUTER JOIN (
SELECT
id, tmp_group_id,
COALESCE(rp_regular.inhabitants, 0) AS regular_inhabitants,
COALESCE(rp_camping_mat.inhabitants, 0) AS camping_mat_inhabitants,
COALESCE(rp_total.inhabitants, 0) AS total_inhabitants
FROM
(
SELECT id, COALESCE(group_id, -1) as tmp_group_id
FROM event.lodgements
WHERE event_id = 1
) AS l
LEFT OUTER JOIN (
SELECT
lodgement_id, COUNT(registration_id) AS inhabitants
FROM
event.registration_parts
WHERE
part_id = 2
AND is_camping_mat = False
GROUP BY
lodgement_id
) AS rp_regular ON l.id = rp_regular.lodgement_id
LEFT OUTER JOIN (
SELECT
lodgement_id, COUNT(registration_id) AS inhabitants
FROM
event.registration_parts
WHERE
part_id = 2
AND is_camping_mat = True
GROUP BY
lodgement_id
) AS rp_camping_mat ON l.id = rp_camping_mat.lodgement_id
LEFT OUTER JOIN (
SELECT
lodgement_id, COUNT(registration_id) AS inhabitants
FROM
event.registration_parts
WHERE
part_id = 2
GROUP BY
lodgement_id
) AS rp_total ON l.id = rp_total.lodgement_id
) AS inhabitants_view2 ON base.base_id = inhabitants_view2.id
LEFT OUTER JOIN (
SELECT
tmp_group_id,
COALESCE(SUM(regular_inhabitants)::bigint, 0) AS group_regular_inhabitants,
COALESCE(SUM(camping_mat_inhabitants)::bigint, 0) AS group_camping_mat_inhabitants,
COALESCE(SUM(total_inhabitants)::bigint, 0) AS group_total_inhabitants
FROM (
SELECT
id, tmp_group_id,
COALESCE(rp_regular.inhabitants, 0) AS regular_inhabitants,
COALESCE(rp_camping_mat.inhabitants, 0) AS camping_mat_inhabitants,
COALESCE(rp_total.inhabitants, 0) AS total_inhabitants
FROM
(
SELECT id, COALESCE(group_id, -1) as tmp_group_id
FROM event.lodgements
WHERE event_id = 1
) AS l
LEFT OUTER JOIN (
SELECT
lodgement_id, COUNT(registration_id) AS inhabitants
FROM
event.registration_parts
WHERE
part_id = 2
AND is_camping_mat = False
GROUP BY
lodgement_id
) AS rp_regular ON l.id = rp_regular.lodgement_id
LEFT OUTER JOIN (
SELECT
lodgement_id, COUNT(registration_id) AS inhabitants
FROM
event.registration_parts
WHERE
part_id = 2
AND is_camping_mat = True
GROUP BY
lodgement_id
) AS rp_camping_mat ON l.id = rp_camping_mat.lodgement_id
LEFT OUTER JOIN (
SELECT
lodgement_id, COUNT(registration_id) AS inhabitants
FROM
event.registration_parts
WHERE
part_id = 2
GROUP BY
lodgement_id
) AS rp_total ON l.id = rp_total.lodgement_id
) AS inhabitants_view2
GROUP BY
tmp_group_id
) AS group_inhabitants_view2 ON base.tmp_group_id = group_inhabitants_view2.tmp_group_id
) AS part2 ON lodgement.id = part2.base_id
LEFT OUTER JOIN (
(
SELECT
id as base_id, COALESCE(group_id, -1) AS tmp_group_id
FROM
event.lodgements
WHERE
event_id = 1
) AS base
LEFT OUTER JOIN (
SELECT
id, tmp_group_id,
COALESCE(rp_regular.inhabitants, 0) AS regular_inhabitants,
COALESCE(rp_camping_mat.inhabitants, 0) AS camping_mat_inhabitants,
COALESCE(rp_total.inhabitants, 0) AS total_inhabitants
FROM
(
SELECT id, COALESCE(group_id, -1) as tmp_group_id
FROM event.lodgements
WHERE event_id = 1
) AS l
LEFT OUTER JOIN (
SELECT
lodgement_id, COUNT(registration_id) AS inhabitants
FROM
event.registration_parts
WHERE
part_id = 3
AND is_camping_mat = False
GROUP BY
lodgement_id
) AS rp_regular ON l.id = rp_regular.lodgement_id
LEFT OUTER JOIN (
SELECT
lodgement_id, COUNT(registration_id) AS inhabitants
FROM
event.registration_parts
WHERE
part_id = 3
AND is_camping_mat = True
GROUP BY
lodgement_id
) AS rp_camping_mat ON l.id = rp_camping_mat.lodgement_id
LEFT OUTER JOIN (
SELECT
lodgement_id, COUNT(registration_id) AS inhabitants
FROM
event.registration_parts
WHERE
part_id = 3
GROUP BY
lodgement_id
) AS rp_total ON l.id = rp_total.lodgement_id
) AS inhabitants_view3 ON base.base_id = inhabitants_view3.id
LEFT OUTER JOIN (
SELECT
tmp_group_id,
COALESCE(SUM(regular_inhabitants)::bigint, 0) AS group_regular_inhabitants,
COALESCE(SUM(camping_mat_inhabitants)::bigint, 0) AS group_camping_mat_inhabitants,
COALESCE(SUM(total_inhabitants)::bigint, 0) AS group_total_inhabitants
FROM (
SELECT
id, tmp_group_id,
COALESCE(rp_regular.inhabitants, 0) AS regular_inhabitants,
COALESCE(rp_camping_mat.inhabitants, 0) AS camping_mat_inhabitants,
COALESCE(rp_total.inhabitants, 0) AS total_inhabitants
FROM
(
SELECT id, COALESCE(group_id, -1) as tmp_group_id
FROM event.lodgements
WHERE event_id = 1
) AS l
LEFT OUTER JOIN (
SELECT
lodgement_id, COUNT(registration_id) AS inhabitants
FROM
event.registration_parts
WHERE
part_id = 3
AND is_camping_mat = False
GROUP BY
lodgement_id
) AS rp_regular ON l.id = rp_regular.lodgement_id
LEFT OUTER JOIN (
SELECT
lodgement_id, COUNT(registration_id) AS inhabitants
FROM
event.registration_parts
WHERE
part_id = 3
AND is_camping_mat = True
GROUP BY
lodgement_id
) AS rp_camping_mat ON l.id = rp_camping_mat.lodgement_id
LEFT OUTER JOIN (
SELECT
lodgement_id, COUNT(registration_id) AS inhabitants
FROM
event.registration_parts
WHERE
part_id = 3
GROUP BY
lodgement_id
) AS rp_total ON l.id = rp_total.lodgement_id
) AS inhabitants_view3
GROUP BY
tmp_group_id
) AS group_inhabitants_view3 ON base.tmp_group_id = group_inhabitants_view3.tmp_group_id
) AS part3 ON lodgement.id = part3.base_id