Micah Bales
Micah Bales

Reputation: 191

How to Query A Hierarchy of Tables in SQL

I have a table, meeting. Among these meetings there exists a hierarchy. Some of them are yearly meetings, most are just regular meetings.

All regular meetings will be associated with at least one join table, meeting_yearly_meeting. A meeting_yearly_meeting has two columns: meeting_id and yearly_meeting_id.

Here is what these two tables look like:

meeting:

id SERIAL PRIMARY KEY,
title VARCHAR(255),
mappable BOOLEAN,
phone VARCHAR(255),
email VARCHAR(255),
city VARCHAR(255),
address VARCHAR(255),
zip VARCHAR(255),
latitude NUMERIC,
longitude NUMERIC,
description VARCHAR(255),
worship_time TIME,
state VARCHAR(255),
website VARCHAR(255),
lgbt_affirming BOOLEAN,
created TIMESTAMP default current_timestamp,
updated TIMESTAMP default current_timestamp

meeting_yearly_meeting:

id SERIAL PRIMARY KEY,
meeting_id SMALLINT,
yearly_meeting_id SMALLINT,
created TIMESTAMP default current_timestamp,
updated TIMESTAMP default current_timestamp

So from my /meetings endpoint, I want to return a collection of all meetings - both regular and yearly meetings. I want to return the meetings with all their columns, as well as an additional column: yearly_meeting.

For meeting records that have one or more associated meeting_yearly_meeting records, yearly_meeting would be a comma-delimited list of the title of the meeting record that is designated as that meeting's yearly meeting. For those meetings that do not have any associated meeting_yearly_meeting records (and therefore are themselves yearly meetings), I want the yearly_meeting field to beNULL`.

On my way to pursuing this goal, I tried something like this:

SELECT t1.*, t2.meeting_yearly_meeting AS yearly_meeting
FROM (
  SELECT * FROM meeting
  FULL JOIN meeting_yearly_meeting ON meeting.id = meeting_yearly_meeting.yearly_meeting_id;
) as t1, 
(
  SELECT CASE WHEN (meeting_yearly_meeting.id IS NOT NULL)
    THEN (SELECT title FROM meeting WHERE meeting.id = meeting_yearly_meeting.yearly_meeting_id)
    ELSE NULL
    END
  FROM (
    SELECT meeting_yearly_meeting.* FROM meeting
    FULL JOIN meeting_yearly_meeting ON meeting.id = meeting_yearly_meeting.meeting_id
  ) as meeting_yearly_meeting;
) as t2;

But this throws a syntax error.

I appreciate any insight others might have. Please let me know if there is any additional context or clarification you need!

UPDATE:

Sample meeting data: https://gist.github.com/micahbales/4013399c3fd23a0caf108124dab827c8

Sample meeting_yearly_meeting data: https://gist.github.com/micahbales/fcbdeef282bd7bf1014606cee43bfb5e

Expected return value example: https://gist.github.com/micahbales/13d2aafdc5d43c4b948dc39c2df51569

Upvotes: 1

Views: 72

Answers (1)

sticky bit
sticky bit

Reputation: 37467

You can try to left join the yearly meetings and then use string_agg() to get your comma delimited list.

SELECT m1.id,
       m1.title,
       m1.mappable,
       m1.phone,
       m1.email,
       m1.city,
       m1.address,
       m1.zip,
       m1.latitude,
       m1.longitude,
       m1.description,
       m1.worship_time,
       m1.state,
       m1.website,
       m1.lgbt_affirming,
       m1.created,
       m1.updated,
       string_agg(m2.title, ', ') yearly_meeting
       FROM meeting m1
            LEFT JOIN meeting_yearly_meeting mym1
                      ON mym1.meeting_id = m1.id
            LEFT JOIN meeting m2
                      ON m2.id = mym1.yearly_meeting_id
       GROUP BY m1.id,
                m1.title,
                m1.mappable,
                m1.phone,
                m1.email,
                m1.city,
                m1.address,
                m1.zip,
                m1.latitude,
                m1.longitude,
                m1.description,
                m1.worship_time,
                m1.state,
                m1.website,
                m1.lgbt_affirming,
                m1.created,
                m1.updated;

Edit:

A more "compact" solution could be using a correlated subquery.

SELECT m1.*,
       (SELECT string_agg(m2.title, ', ')
               FROM meeting_yearly_meeting mym1
                    LEFT JOIN meeting m2
                              ON m2.id = mym1.yearly_meeting_id
               WHERE mym1.meeting_id = m1.id) yearly_meeting
       FROM meeting m1;

But note, though it's less code, it's not necessarily faster.

Upvotes: 1

Related Questions