Advanced Registrations Queries
Complex Analytics for Strategic Event Management
Take your event analysis to the next level with CTEs, window functions, and cross-module integration. These queries provide deep insights for strategic planning and optimization.Query Requirements
Schema Prefix
IMPORTANT: All tables in the Planning Center Registrations module live in theplanning_center
schema. Always prefix table names with planning_center.
in advanced queries.
✅ CORRECT: SELECT * FROM planning_center.registrations_attendees
❌ INCORRECT: SELECT * FROM registrations_attendees
Row Level Security (RLS)
Row Level Security automatically filters results by:- tenant_organization_id – limits data to your organization
- system_status – active records returned by default
- ❌
WHERE tenant_organization_id = 1
- ❌
WHERE system_status = 'active'
Registration Trends and Patterns
Registration Velocity Analysis
Copy
-- Track registration speed and predict fill rates
WITH registration_velocity AS (
SELECT
s.signup_id,
s.name as event_name,
st.starts_at as event_date,
a.created_at as registration_date,
DATE_PART('day', st.starts_at - a.created_at) as days_before_event,
COUNT(*) OVER (
PARTITION BY s.signup_id
ORDER BY a.created_at
ROWS UNBOUNDED PRECEDING
) as cumulative_registrations,
ROW_NUMBER() OVER (
PARTITION BY s.signup_id
ORDER BY a.created_at
) as registration_order
FROM planning_center.registrations_signups s
JOIN planning_center.registrations_signup_relationships sr
ON sr.signup_id = s.signup_id
AND sr.relationship_type = 'signup_time'
JOIN planning_center.registrations_signup_times st
ON st.signup_time_id = sr.relationship_id
JOIN planning_center.registrations_attendee_relationships ar
ON ar.relationship_type = 'signup'
AND ar.relationship_id = s.signup_id
JOIN planning_center.registrations_attendees a
ON a.attendee_id = ar.attendee_id
AND a.active = true
WHERE s.archived = false
),
velocity_stats AS (
SELECT
event_name,
event_date,
MAX(cumulative_registrations) as total_registrations,
AVG(days_before_event) as avg_days_before,
PERCENTILE_CONT(0.5) WITHIN GROUP (ORDER BY days_before_event) as median_days_before,
COUNT(CASE WHEN days_before_event >= 30 THEN 1 END) as early_birds,
COUNT(CASE WHEN days_before_event < 7 THEN 1 END) as last_minute
FROM registration_velocity
GROUP BY event_name, event_date
)
SELECT
event_name,
event_date,
total_registrations,
ROUND(avg_days_before, 1) as avg_registration_lead_time,
ROUND(median_days_before, 1) as median_registration_lead_time,
ROUND(early_birds * 100.0 / total_registrations, 1) as early_bird_percentage,
ROUND(last_minute * 100.0 / total_registrations, 1) as last_minute_percentage
FROM velocity_stats
ORDER BY event_date DESC;
Year-over-Year Event Comparison
Copy
-- Compare event performance across years
WITH yearly_events AS (
SELECT
s.name as event_name,
DATE_PART('year', st.starts_at) as event_year,
DATE_PART('month', st.starts_at) as event_month,
COUNT(DISTINCT a.attendee_id) as total_attendees,
COUNT(CASE WHEN a.active = true THEN 1 END) as active_registrations,
COUNT(CASE WHEN a.canceled = true THEN 1 END) as cancellations,
COUNT(CASE WHEN a.waitlisted = true THEN 1 END) as waitlisted
FROM planning_center.registrations_signups s
JOIN planning_center.registrations_signup_relationships sr
ON sr.signup_id = s.signup_id
AND sr.relationship_type = 'signup_time'
JOIN planning_center.registrations_signup_times st
ON st.signup_time_id = sr.relationship_id
LEFT JOIN planning_center.registrations_attendee_relationships ar
ON ar.relationship_type = 'signup'
AND ar.relationship_id = s.signup_id
LEFT JOIN planning_center.registrations_attendees a
ON a.attendee_id = ar.attendee_id
GROUP BY s.name, DATE_PART('year', st.starts_at), DATE_PART('month', st.starts_at)
),
year_comparison AS (
SELECT
event_name,
event_month,
MAX(CASE WHEN event_year = DATE_PART('year', CURRENT_DATE) - 1
THEN total_attendees END) as last_year,
MAX(CASE WHEN event_year = DATE_PART('year', CURRENT_DATE)
THEN total_attendees END) as this_year,
MAX(CASE WHEN event_year = DATE_PART('year', CURRENT_DATE) - 1
THEN cancellations END) as cancellations_last_year,
MAX(CASE WHEN event_year = DATE_PART('year', CURRENT_DATE)
THEN cancellations END) as cancellations_this_year
FROM yearly_events
GROUP BY event_name, event_month
)
SELECT
event_name,
TO_CHAR(TO_DATE(event_month::text, 'MM'), 'Month') as month,
COALESCE(last_year, 0) as last_year_attendees,
COALESCE(this_year, 0) as this_year_attendees,
CASE
WHEN last_year > 0
THEN ROUND((this_year - last_year) * 100.0 / last_year, 1)
ELSE NULL
END as growth_percentage,
COALESCE(cancellations_last_year, 0) as cancellations_last_year,
COALESCE(cancellations_this_year, 0) as cancellations_this_year
FROM year_comparison
WHERE last_year IS NOT NULL OR this_year IS NOT NULL
ORDER BY event_month, event_name;
Waitlist Analytics
Waitlist Conversion Funnel
Copy
-- Analyze waitlist to registration conversions
WITH waitlist_timeline AS (
SELECT
a.attendee_id,
s.signup_id,
s.name as event_name,
a.waitlisted_at,
a.created_at as registration_date,
a.waitlisted,
a.active,
a.canceled,
LEAD(a.waitlisted) OVER (
PARTITION BY a.attendee_id
ORDER BY a.updated_at
) as next_waitlist_status,
LEAD(a.active) OVER (
PARTITION BY a.attendee_id
ORDER BY a.updated_at
) as next_active_status
FROM planning_center.registrations_attendees a
JOIN planning_center.registrations_attendee_relationships ar
ON ar.attendee_id = a.attendee_id
AND ar.relationship_type = 'signup'
JOIN planning_center.registrations_signups s
ON s.signup_id = ar.relationship_id
WHERE a.waitlisted_at IS NOT NULL
),
conversion_metrics AS (
SELECT
event_name,
COUNT(DISTINCT attendee_id) as total_waitlisted,
COUNT(DISTINCT CASE
WHEN active = true
THEN attendee_id
END) as converted_to_active,
COUNT(DISTINCT CASE
WHEN canceled = true
THEN attendee_id
END) as canceled_from_waitlist,
AVG(CASE
WHEN active = true
THEN EXTRACT(EPOCH FROM (registration_date - waitlisted_at))/3600
END) as avg_hours_to_conversion
FROM waitlist_timeline
GROUP BY event_name
)
SELECT
event_name,
total_waitlisted,
converted_to_active,
ROUND(converted_to_active * 100.0 / NULLIF(total_waitlisted, 0), 1) as conversion_rate,
canceled_from_waitlist,
ROUND(canceled_from_waitlist * 100.0 / NULLIF(total_waitlisted, 0), 1) as cancellation_rate,
ROUND(avg_hours_to_conversion / 24, 1) as avg_days_to_conversion
FROM conversion_metrics
WHERE total_waitlisted > 0
ORDER BY conversion_rate DESC;
Geographic Analysis
Registration Heatmap Data
Copy
-- Geographic distribution of registrations
WITH location_registrations AS (
SELECT
sl.latitude,
sl.longitude,
sl.name as location_name,
sl.formatted_address,
s.name as event_name,
COUNT(DISTINCT a.attendee_id) as registration_count
FROM planning_center.registrations_signup_locations sl
JOIN planning_center.registrations_signup_relationships sr
ON sr.relationship_id = sl.signup_location_id
AND sr.relationship_type = 'signup_location'
JOIN planning_center.registrations_signups s
ON s.signup_id = sr.signup_id
LEFT JOIN planning_center.registrations_attendee_relationships ar
ON ar.relationship_type = 'signup'
AND ar.relationship_id = s.signup_id
LEFT JOIN planning_center.registrations_attendees a
ON a.attendee_id = ar.attendee_id
AND a.active = true
WHERE sl.latitude IS NOT NULL
AND sl.longitude IS NOT NULL
AND s.archived = false
GROUP BY sl.latitude, sl.longitude, sl.name, sl.formatted_address, s.name
),
location_stats AS (
SELECT
latitude,
longitude,
location_name,
formatted_address,
COUNT(DISTINCT event_name) as events_at_location,
SUM(registration_count) as total_registrations,
AVG(registration_count) as avg_registrations_per_event,
STRING_AGG(event_name || ' (' || registration_count || ')', ', '
ORDER BY registration_count DESC) as event_details
FROM location_registrations
GROUP BY latitude, longitude, location_name, formatted_address
)
SELECT
location_name,
formatted_address,
latitude,
longitude,
events_at_location,
total_registrations,
ROUND(avg_registrations_per_event, 1) as avg_registrations,
event_details
FROM location_stats
ORDER BY total_registrations DESC;
Campus Performance Comparison
Copy
-- Comprehensive campus metrics
WITH campus_events AS (
SELECT
c.campus_id,
c.name as campus_name,
s.signup_id,
s.name as event_name,
st.starts_at as event_date,
COUNT(DISTINCT a.attendee_id) as total_attendees,
COUNT(CASE WHEN a.active = true THEN 1 END) as active_registrations,
COUNT(CASE WHEN a.waitlisted = true THEN 1 END) as waitlisted
FROM planning_center.registrations_campuses c
JOIN planning_center.registrations_signup_relationships sr_campus
ON sr_campus.relationship_id = c.campus_id
AND sr_campus.relationship_type = 'campus'
JOIN planning_center.registrations_signups s
ON s.signup_id = sr_campus.signup_id
LEFT JOIN planning_center.registrations_signup_relationships sr_time
ON sr_time.signup_id = s.signup_id
AND sr_time.relationship_type = 'signup_time'
LEFT JOIN planning_center.registrations_signup_times st
ON st.signup_time_id = sr_time.relationship_id
LEFT JOIN planning_center.registrations_attendee_relationships ar
ON ar.relationship_type = 'signup'
AND ar.relationship_id = s.signup_id
LEFT JOIN planning_center.registrations_attendees a
ON a.attendee_id = ar.attendee_id
WHERE s.archived = false
GROUP BY c.campus_id, c.name, s.signup_id, s.name, st.starts_at
),
campus_summary AS (
SELECT
campus_name,
COUNT(DISTINCT signup_id) as total_events,
SUM(total_attendees) as total_registrations,
AVG(total_attendees) as avg_attendees_per_event,
SUM(waitlisted) as total_waitlisted,
MIN(event_date) as first_event,
MAX(event_date) as last_event
FROM campus_events
GROUP BY campus_name
)
SELECT
campus_name,
total_events,
total_registrations,
ROUND(avg_attendees_per_event, 1) as avg_attendees,
total_waitlisted,
ROUND(total_waitlisted * 100.0 / NULLIF(total_registrations, 0), 1) as waitlist_percentage,
first_event::date as first_event_date,
last_event::date as last_event_date,
DATE_PART('day', last_event - first_event) as days_of_activity
FROM campus_summary
ORDER BY total_registrations DESC;
Financial Analysis
Revenue Analysis by Category
Copy
-- Detailed revenue breakdown
WITH event_revenue AS (
SELECT
cat.name as category_name,
s.name as event_name,
selt.name as selection_type,
selt.price_cents,
COUNT(a.attendee_id) as tickets_sold,
(selt.price_cents * COUNT(a.attendee_id)) as revenue_cents
FROM planning_center.registrations_signups s
-- Join to category
LEFT JOIN planning_center.registrations_signup_relationships sr_cat
ON sr_cat.signup_id = s.signup_id
AND sr_cat.relationship_type = 'category'
LEFT JOIN planning_center.registrations_categories cat
ON cat.category_id = sr_cat.relationship_id
-- Join to selection types
JOIN planning_center.registrations_selection_types selt
ON EXISTS (
SELECT 1 FROM planning_center.registrations_signup_relationships sr
WHERE sr.signup_id = s.signup_id
AND sr.relationship_type = 'selection_type'
AND sr.relationship_id = selt.selection_type_id
)
-- Join to attendees
LEFT JOIN planning_center.registrations_attendee_relationships ar
ON ar.relationship_type = 'signup'
AND ar.relationship_id = s.signup_id
LEFT JOIN planning_center.registrations_attendees a
ON a.attendee_id = ar.attendee_id
AND a.active = true
WHERE s.archived = false
GROUP BY cat.name, s.name, selt.name, selt.price_cents
),
category_summary AS (
SELECT
COALESCE(category_name, 'Uncategorized') as category,
COUNT(DISTINCT event_name) as events_count,
SUM(tickets_sold) as total_tickets,
SUM(revenue_cents) / 100.0 as total_revenue,
AVG(price_cents) / 100.0 as avg_ticket_price,
MIN(price_cents) / 100.0 as min_price,
MAX(price_cents) / 100.0 as max_price
FROM event_revenue
GROUP BY category_name
)
SELECT
category,
events_count,
total_tickets,
ROUND(total_revenue, 2) as total_revenue_dollars,
ROUND(avg_ticket_price, 2) as avg_ticket_price,
ROUND(total_revenue / NULLIF(total_tickets, 0), 2) as actual_avg_paid,
min_price,
max_price,
ROUND(total_revenue / NULLIF(SUM(total_revenue) OVER (), 0) * 100, 1) as revenue_percentage
FROM category_summary
ORDER BY total_revenue DESC;
Price Elasticity Analysis
Copy
-- Analyze registration patterns by price point
WITH price_analysis AS (
SELECT
selt.price_cents / 100.0 as price_dollars,
s.name as event_name,
COUNT(DISTINCT a.attendee_id) as registrations,
COUNT(CASE WHEN a.waitlisted = true THEN 1 END) as waitlisted,
AVG(DATE_PART('day', st.starts_at - a.created_at)) as avg_days_advance_registration
FROM planning_center.registrations_selection_types selt
JOIN planning_center.registrations_signup_relationships sr_sel
ON sr_sel.relationship_id = selt.selection_type_id
AND sr_sel.relationship_type = 'selection_type'
JOIN planning_center.registrations_signups s
ON s.signup_id = sr_sel.signup_id
LEFT JOIN planning_center.registrations_signup_relationships sr_time
ON sr_time.signup_id = s.signup_id
AND sr_time.relationship_type = 'signup_time'
LEFT JOIN planning_center.registrations_signup_times st
ON st.signup_time_id = sr_time.relationship_id
LEFT JOIN planning_center.registrations_attendee_relationships ar
ON ar.relationship_type = 'signup'
AND ar.relationship_id = s.signup_id
LEFT JOIN planning_center.registrations_attendees a
ON a.attendee_id = ar.attendee_id
WHERE s.archived = false
GROUP BY selt.price_cents, s.name
),
price_bands AS (
SELECT
CASE
WHEN price_dollars = 0 THEN 'Free'
WHEN price_dollars <= 25 THEN '$1-25'
WHEN price_dollars <= 50 THEN '$26-50'
WHEN price_dollars <= 100 THEN '$51-100'
ELSE '$100+'
END as price_band,
COUNT(DISTINCT event_name) as events,
SUM(registrations) as total_registrations,
AVG(registrations) as avg_registrations,
SUM(waitlisted) as total_waitlisted,
AVG(avg_days_advance_registration) as avg_advance_days
FROM price_analysis
GROUP BY
CASE
WHEN price_dollars = 0 THEN 'Free'
WHEN price_dollars <= 25 THEN '$1-25'
WHEN price_dollars <= 50 THEN '$26-50'
WHEN price_dollars <= 100 THEN '$51-100'
ELSE '$100+'
END
)
SELECT
price_band,
events,
total_registrations,
ROUND(avg_registrations, 1) as avg_registrations_per_event,
total_waitlisted,
ROUND(total_waitlisted * 100.0 / NULLIF(total_registrations, 0), 1) as waitlist_rate,
ROUND(avg_advance_days, 1) as avg_days_advance_registration
FROM price_bands
ORDER BY
CASE price_band
WHEN 'Free' THEN 1
WHEN '$1-25' THEN 2
WHEN '$26-50' THEN 3
WHEN '$51-100' THEN 4
ELSE 5
END;
Cross-Module Integration
Registrations with People Data
Copy
-- Combine registrations with People module demographics
WITH person_registrations AS (
SELECT
rp.person_id,
rp.name as registrant_name,
pp.birthdate,
pp.gender,
pp.membership,
pp.status as person_status,
COUNT(DISTINCT s.signup_id) as events_registered,
SUM(CASE WHEN a.active = true THEN 1 ELSE 0 END) as active_registrations,
SUM(CASE WHEN a.waitlisted = true THEN 1 ELSE 0 END) as waitlist_registrations
FROM planning_center.registrations_people rp
LEFT JOIN planning_center.people_people pp
ON pp.person_id = rp.person_id
LEFT JOIN planning_center.registrations_registration_relationships rr
ON rr.relationship_id = rp.person_id
AND rr.relationship_type = 'person'
LEFT JOIN planning_center.registrations_attendee_relationships ar
ON ar.relationship_id = rr.registration_id
AND ar.relationship_type = 'registration'
LEFT JOIN planning_center.registrations_attendees a
ON a.attendee_id = ar.attendee_id
LEFT JOIN planning_center.registrations_attendee_relationships ar_signup
ON ar_signup.attendee_id = a.attendee_id
AND ar_signup.relationship_type = 'signup'
LEFT JOIN planning_center.registrations_signups s
ON s.signup_id = ar_signup.relationship_id
GROUP BY rp.person_id, rp.name, pp.birthdate, pp.gender, pp.membership, pp.status
),
demographic_summary AS (
SELECT
CASE
WHEN birthdate IS NULL THEN 'Unknown'
WHEN DATE_PART('year', AGE(birthdate)) < 18 THEN 'Youth'
WHEN DATE_PART('year', AGE(birthdate)) < 30 THEN 'Young Adult'
WHEN DATE_PART('year', AGE(birthdate)) < 50 THEN 'Adult'
ELSE 'Senior'
END as age_group,
COALESCE(gender, 'Not Specified') as gender,
COALESCE(membership, 'Non-Member') as membership_status,
COUNT(DISTINCT person_id) as unique_registrants,
SUM(events_registered) as total_event_registrations,
AVG(events_registered) as avg_events_per_person,
SUM(active_registrations) as total_active,
SUM(waitlist_registrations) as total_waitlisted
FROM person_registrations
WHERE person_status = 'active'
GROUP BY age_group, gender, membership_status
)
SELECT
age_group,
gender,
membership_status,
unique_registrants,
total_event_registrations,
ROUND(avg_events_per_person, 1) as avg_events_per_person,
total_active,
total_waitlisted,
ROUND(total_waitlisted * 100.0 / NULLIF(total_active + total_waitlisted, 0), 1) as waitlist_percentage
FROM demographic_summary
ORDER BY unique_registrants DESC;
Registration Impact on Giving
Copy
-- Analyze giving patterns of event attendees
WITH event_attendees AS (
SELECT DISTINCT
rp.person_id,
rp.name,
s.name as event_name,
cat.name as event_category,
st.starts_at as event_date
FROM planning_center.registrations_people rp
JOIN planning_center.registrations_registration_relationships rr
ON rr.relationship_id = rp.person_id
AND rr.relationship_type = 'person'
JOIN planning_center.registrations_attendee_relationships ar
ON ar.relationship_id = rr.registration_id
AND ar.relationship_type = 'registration'
JOIN planning_center.registrations_attendees a
ON a.attendee_id = ar.attendee_id
AND a.active = true
JOIN planning_center.registrations_attendee_relationships ar_signup
ON ar_signup.attendee_id = a.attendee_id
AND ar_signup.relationship_type = 'signup'
JOIN planning_center.registrations_signups s
ON s.signup_id = ar_signup.relationship_id
LEFT JOIN planning_center.registrations_signup_relationships sr_cat
ON sr_cat.signup_id = s.signup_id
AND sr_cat.relationship_type = 'category'
LEFT JOIN planning_center.registrations_categories cat
ON cat.category_id = sr_cat.relationship_id
LEFT JOIN planning_center.registrations_signup_relationships sr_time
ON sr_time.signup_id = s.signup_id
AND sr_time.relationship_type = 'signup_time'
LEFT JOIN planning_center.registrations_signup_times st
ON st.signup_time_id = sr_time.relationship_id
),
giving_analysis AS (
SELECT
ea.event_category,
COUNT(DISTINCT ea.person_id) as attendee_count,
COUNT(DISTINCT gd.donor_id) as donors_count,
COUNT(DISTINCT CASE
WHEN gd.created_at > ea.event_date
THEN gd.donor_id
END) as new_donors_after_event,
SUM(CASE
WHEN gd.created_at > ea.event_date
THEN gd.amount_cents / 100.0
END) as giving_after_event
FROM event_attendees ea
LEFT JOIN planning_center.giving_donors gd
ON gd.person_id = ea.person_id
LEFT JOIN planning_center.giving_donations don
ON don.donor_id = gd.donor_id
GROUP BY ea.event_category
)
SELECT
COALESCE(event_category, 'Uncategorized') as category,
attendee_count,
donors_count,
ROUND(donors_count * 100.0 / attendee_count, 1) as donor_percentage,
new_donors_after_event,
ROUND(new_donors_after_event * 100.0 / attendee_count, 1) as new_donor_percentage,
COALESCE(ROUND(giving_after_event, 2), 0) as total_giving_after_events
FROM giving_analysis
WHERE attendee_count > 0
ORDER BY attendee_count DESC;
Performance Optimization Patterns
Indexed Subquery Pattern
Copy
-- Efficient pattern for large datasets using indexed subqueries
WITH indexed_signups AS (
SELECT
signup_id,
name,
archived
FROM planning_center.registrations_signups
WHERE archived = false
AND created_at >= CURRENT_DATE - INTERVAL '1 year'
),
indexed_attendees AS (
SELECT
ar.relationship_id as signup_id,
COUNT(*) as attendee_count,
COUNT(CASE WHEN a.active = true THEN 1 END) as active_count,
COUNT(CASE WHEN a.waitlisted = true THEN 1 END) as waitlist_count
FROM planning_center.registrations_attendees a
JOIN planning_center.registrations_attendee_relationships ar
ON ar.attendee_id = a.attendee_id
AND ar.relationship_type = 'signup'
WHERE a.created_at >= CURRENT_DATE - INTERVAL '1 year'
GROUP BY ar.relationship_id
)
SELECT
s.name,
COALESCE(a.attendee_count, 0) as total_attendees,
COALESCE(a.active_count, 0) as active_registrations,
COALESCE(a.waitlist_count, 0) as waitlisted,
CASE
WHEN a.waitlist_count > 0
THEN ROUND(a.waitlist_count * 100.0 / a.attendee_count, 1)
ELSE 0
END as waitlist_percentage
FROM indexed_signups s
LEFT JOIN indexed_attendees a
ON a.signup_id = s.signup_id
ORDER BY a.attendee_count DESC NULLS LAST;
Materialized View Pattern
Copy
-- Create a materialized view for frequently accessed metrics
-- (Run this once to create the view)
CREATE MATERIALIZED VIEW planning_center.registrations_event_summary AS
WITH event_metrics AS (
SELECT
s.signup_id,
s.name as event_name,
s.archived,
s.open_at,
s.close_at,
cat.name as category,
camp.name as campus,
loc.name as location,
tim.starts_at as event_date,
COUNT(DISTINCT a.attendee_id) as total_attendees,
COUNT(CASE WHEN a.active = true THEN 1 END) as active_registrations,
COUNT(CASE WHEN a.waitlisted = true THEN 1 END) as waitlisted,
COUNT(CASE WHEN a.canceled = true THEN 1 END) as canceled,
AVG(selt.price_cents) / 100.0 as avg_price
FROM planning_center.registrations_signups s
-- All the necessary joins...
LEFT JOIN planning_center.registrations_signup_relationships sr_cat
ON sr_cat.signup_id = s.signup_id AND sr_cat.relationship_type = 'category'
LEFT JOIN planning_center.registrations_categories cat
ON cat.category_id = sr_cat.relationship_id
LEFT JOIN planning_center.registrations_signup_relationships sr_camp
ON sr_camp.signup_id = s.signup_id AND sr_camp.relationship_type = 'campus'
LEFT JOIN planning_center.registrations_campuses camp
ON camp.campus_id = sr_camp.relationship_id
LEFT JOIN planning_center.registrations_signup_relationships sr_loc
ON sr_loc.signup_id = s.signup_id AND sr_loc.relationship_type = 'signup_location'
LEFT JOIN planning_center.registrations_signup_locations loc
ON loc.signup_location_id = sr_loc.relationship_id
LEFT JOIN planning_center.registrations_signup_relationships sr_tim
ON sr_tim.signup_id = s.signup_id AND sr_tim.relationship_type = 'signup_time'
LEFT JOIN planning_center.registrations_signup_times tim
ON tim.signup_time_id = sr_tim.relationship_id
LEFT JOIN planning_center.registrations_attendee_relationships ar
ON ar.relationship_type = 'signup' AND ar.relationship_id = s.signup_id
LEFT JOIN planning_center.registrations_attendees a
ON a.attendee_id = ar.attendee_id
LEFT JOIN planning_center.registrations_selection_types selt
ON EXISTS (
SELECT 1 FROM planning_center.registrations_signup_relationships sr
WHERE sr.signup_id = s.signup_id
AND sr.relationship_type = 'selection_type'
AND sr.relationship_id = selt.selection_type_id
)
GROUP BY s.signup_id, s.name, s.archived, s.open_at, s.close_at,
cat.name, camp.name, loc.name, tim.starts_at
)
SELECT * FROM event_metrics;
-- Refresh the materialized view periodically
REFRESH MATERIALIZED VIEW planning_center.registrations_event_summary;
-- Query the materialized view for fast results
SELECT * FROM planning_center.registrations_event_summary
WHERE archived = false
AND event_date >= CURRENT_DATE
ORDER BY event_date;
Tips for Advanced Queries
- Use CTEs liberally - They make complex queries readable and maintainable
- Index awareness - Structure WHERE clauses to use existing indexes
- Window functions - Great for running totals, rankings, and comparisons
- COALESCE for NULLs - Handle missing data gracefully
- Cross-module carefully - Join to other modules only when necessary
- Test with EXPLAIN - Analyze query plans for performance bottlenecks