Production-Ready Reports for Event Management
Copy these complete report templates directly into your BI tools or use them as-is for comprehensive event insights. Each report is optimized for performance and includes all necessary business logic.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 your reports.
✅ CORRECT: SELECT * FROM planning_center.registrations_signups
❌ INCORRECT: SELECT * FROM registrations_signups
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'
Executive Dashboard Reports
Monthly Event Summary Dashboard
-- Executive summary of all event activity for the current month
WITH monthly_events AS (
SELECT
s.signup_id,
s.name as event_name,
s.archived,
cat.name as category,
camp.name as campus,
loc.name as location,
st.starts_at as event_date,
st.ends_at as event_end,
s.open_at as registration_open,
s.close_at as registration_close
FROM planning_center.registrations_signups s
LEFT JOIN planning_center.registrations_signups_relationships sr_cat
ON sr_cat.signup_id = s.signup_id
AND sr_cat.relationship_type IN ('Category', 'category')
LEFT JOIN planning_center.registrations_categories cat
ON cat.category_id = sr_cat.relationship_id
LEFT JOIN planning_center.registrations_signups_relationships sr_camp
ON sr_camp.signup_id = s.signup_id
AND sr_camp.relationship_type IN ('Campus', 'campus')
LEFT JOIN planning_center.registrations_campuses camp
ON camp.campus_id = sr_camp.relationship_id
LEFT JOIN planning_center.registrations_signups_relationships sr_loc
ON sr_loc.signup_id = s.signup_id
AND sr_loc.relationship_type IN ('SignupLocation', 'signup_location')
LEFT JOIN planning_center.registrations_signup_locations loc
ON loc.signup_location_id = sr_loc.relationship_id
LEFT JOIN planning_center.registrations_signups_relationships sr_time
ON sr_time.signup_id = s.signup_id
AND sr_time.relationship_type IN ('SignupTime', 'signup_time')
LEFT JOIN planning_center.registrations_signup_times st
ON st.signup_time_id = sr_time.relationship_id
WHERE DATE_PART('month', st.starts_at) = DATE_PART('month', CURRENT_DATE)
AND DATE_PART('year', st.starts_at) = DATE_PART('year', CURRENT_DATE)
),
registration_metrics AS (
SELECT
me.signup_id,
COUNT(DISTINCT a.attendee_id) as total_registrations,
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,
COUNT(DISTINCT ec.emergency_contact_id) as emergency_contacts_provided
FROM monthly_events me
LEFT JOIN planning_center.registrations_registrations_relationships ar_sg
ON ar_sg.relationship_type IN ('Signup', 'signup')
AND ar_sg.relationship_id = me.signup_id
LEFT JOIN planning_center.registrations_attendees_relationships ar
ON ar.relationship_type IN ('Registration', 'registration')
AND ar.relationship_id = ar_sg.registration_id
LEFT JOIN planning_center.registrations_attendees a
ON a.attendee_id = ar.attendee_id
LEFT JOIN planning_center.registrations_attendees_relationships ar_ec
ON ar_ec.attendee_id = a.attendee_id
AND ar_ec.relationship_type IN ('EmergencyContact', 'emergency_contact')
LEFT JOIN planning_center.registrations_emergency_contacts ec
ON ec.emergency_contact_id = ar_ec.relationship_id
GROUP BY me.signup_id
)
SELECT
me.event_name,
me.event_date::date as date,
EXTRACT(DOW FROM me.event_date) as day_of_week,
COALESCE(me.category, 'Uncategorized') as category,
COALESCE(me.campus, 'All Campuses') as campus,
COALESCE(me.location, 'TBD') as location,
CASE
WHEN me.archived = true THEN 'Archived'
WHEN me.event_date < CURRENT_TIMESTAMP THEN 'Completed'
WHEN me.registration_close < CURRENT_TIMESTAMP THEN 'Registration Closed'
WHEN me.registration_open > CURRENT_TIMESTAMP THEN 'Not Yet Open'
ELSE 'Open for Registration'
END as status,
COALESCE(rm.total_registrations, 0) as total_registrations,
COALESCE(rm.active_registrations, 0) as confirmed,
COALESCE(rm.cancellations, 0) as canceled,
COALESCE(rm.waitlisted, 0) as waitlisted,
CASE
WHEN rm.total_registrations > 0
THEN ROUND(rm.cancellations * 100.0 / rm.total_registrations, 1)
ELSE 0
END as cancellation_rate,
CASE
WHEN rm.active_registrations > 0
THEN ROUND(rm.emergency_contacts_provided * 100.0 / rm.active_registrations, 1)
ELSE 0
END as emergency_contact_completion,
COALESCE(rm.emergency_contacts_provided, 0) as emergency_contacts_provided
FROM monthly_events me
LEFT JOIN registration_metrics rm ON rm.signup_id = me.signup_id
ORDER BY me.event_date;
Year-to-Date Performance Report
-- Comprehensive YTD metrics with comparisons to last year
WITH ytd_data AS (
SELECT
DATE_PART('year', st.starts_at) as year,
DATE_PART('month', st.starts_at) as month,
cat.name as category,
COUNT(DISTINCT s.signup_id) as event_count,
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
LEFT JOIN planning_center.registrations_signups_relationships sr_cat
ON sr_cat.signup_id = s.signup_id
AND sr_cat.relationship_type IN ('Category', 'category')
LEFT JOIN planning_center.registrations_categories cat
ON cat.category_id = sr_cat.relationship_id
LEFT JOIN planning_center.registrations_signups_relationships sr_time
ON sr_time.signup_id = s.signup_id
AND sr_time.relationship_type IN ('SignupTime', 'signup_time')
LEFT JOIN planning_center.registrations_signup_times st
ON st.signup_time_id = sr_time.relationship_id
LEFT JOIN planning_center.registrations_registrations_relationships ar_sg
ON ar_sg.relationship_type IN ('Signup', 'signup')
AND ar_sg.relationship_id = s.signup_id
LEFT JOIN planning_center.registrations_attendees_relationships ar
ON ar.relationship_type IN ('Registration', 'registration')
AND ar.relationship_id = ar_sg.registration_id
LEFT JOIN planning_center.registrations_attendees a
ON a.attendee_id = ar.attendee_id
WHERE s.archived = false
AND st.starts_at >= DATE_TRUNC('year', CURRENT_DATE - INTERVAL '1 year')
AND st.starts_at <= CURRENT_DATE
GROUP BY DATE_PART('year', st.starts_at), DATE_PART('month', st.starts_at), cat.name
),
comparison AS (
SELECT
COALESCE(category, 'Uncategorized') as category,
SUM(CASE WHEN year = DATE_PART('year', CURRENT_DATE) THEN event_count ELSE 0 END) as events_this_year,
SUM(CASE WHEN year = DATE_PART('year', CURRENT_DATE) - 1 THEN event_count ELSE 0 END) as events_last_year,
SUM(CASE WHEN year = DATE_PART('year', CURRENT_DATE) THEN active_registrations ELSE 0 END) as registrations_this_year,
SUM(CASE WHEN year = DATE_PART('year', CURRENT_DATE) - 1 THEN active_registrations ELSE 0 END) as registrations_last_year,
SUM(CASE WHEN year = DATE_PART('year', CURRENT_DATE) THEN cancellations ELSE 0 END) as cancellations_this_year,
SUM(CASE WHEN year = DATE_PART('year', CURRENT_DATE) - 1 THEN cancellations ELSE 0 END) as cancellations_last_year,
SUM(CASE WHEN year = DATE_PART('year', CURRENT_DATE) THEN waitlisted ELSE 0 END) as waitlisted_this_year,
SUM(CASE WHEN year = DATE_PART('year', CURRENT_DATE) - 1 THEN waitlisted ELSE 0 END) as waitlisted_last_year
FROM ytd_data
GROUP BY category
)
SELECT
category,
events_this_year,
events_last_year,
CASE
WHEN events_last_year > 0
THEN ROUND((events_this_year - events_last_year) * 100.0 / events_last_year, 1)
ELSE NULL
END as event_growth_pct,
registrations_this_year,
registrations_last_year,
CASE
WHEN registrations_last_year > 0
THEN ROUND((registrations_this_year - registrations_last_year) * 100.0 / registrations_last_year, 1)
ELSE NULL
END as registration_growth_pct,
ROUND(cancellations_this_year * 100.0 / NULLIF(registrations_this_year + cancellations_this_year, 0), 1) as cancellation_rate_this_year,
ROUND(cancellations_last_year * 100.0 / NULLIF(registrations_last_year + cancellations_last_year, 0), 1) as cancellation_rate_last_year,
waitlisted_this_year,
waitlisted_last_year
FROM comparison
ORDER BY registrations_this_year DESC;
Operational Reports
Event Roster with Emergency Contacts
-- Complete attendee roster for event operations
WITH event_roster AS (
SELECT
s.name as event_name,
st.starts_at as event_date,
loc.name as location_name,
loc.formatted_address as location_address,
p.person_id,
p.name as attendee_name,
p.first_name,
p.last_name,
CASE
WHEN a.waitlisted = true THEN 'Waitlisted'
WHEN a.canceled = true THEN 'Canceled'
WHEN a.active = true THEN 'Confirmed'
ELSE 'Unknown'
END as registration_status,
a.created_at as registered_date,
a.waitlisted_at,
ec.name as emergency_contact_name,
ec.phone_number as emergency_contact_phone,
ROW_NUMBER() OVER (
PARTITION BY s.signup_id
ORDER BY
CASE WHEN a.active = true THEN 1
WHEN a.waitlisted = true THEN 2
ELSE 3 END,
a.created_at
) as roster_number
FROM planning_center.registrations_signups s
-- Join to time
JOIN planning_center.registrations_signups_relationships sr_time
ON sr_time.signup_id = s.signup_id
AND sr_time.relationship_type IN ('SignupTime', 'signup_time')
JOIN planning_center.registrations_signup_times st
ON st.signup_time_id = sr_time.relationship_id
-- Join to location
LEFT JOIN planning_center.registrations_signups_relationships sr_loc
ON sr_loc.signup_id = s.signup_id
AND sr_loc.relationship_type IN ('SignupLocation', 'signup_location')
LEFT JOIN planning_center.registrations_signup_locations loc
ON loc.signup_location_id = sr_loc.relationship_id
-- Join to attendees
JOIN planning_center.registrations_registrations_relationships ar_signup_sg
ON ar_signup_sg.relationship_type IN ('Signup', 'signup')
AND ar_signup_sg.relationship_id = s.signup_id
JOIN planning_center.registrations_attendees_relationships ar_signup
ON ar_signup.relationship_type IN ('Registration', 'registration')
AND ar_signup.relationship_id = ar_signup_sg.registration_id
JOIN planning_center.registrations_attendees a
ON a.attendee_id = ar_signup.attendee_id
-- Join to person
JOIN planning_center.registrations_attendees_relationships ar_person
ON ar_person.attendee_id = a.attendee_id
AND ar_person.relationship_type IN ('Person', 'person')
JOIN planning_center.registrations_people p
ON p.person_id = ar_person.relationship_id
-- Join to emergency contact
LEFT JOIN planning_center.registrations_attendees_relationships ar_ec
ON ar_ec.attendee_id = a.attendee_id
AND ar_ec.relationship_type IN ('EmergencyContact', 'emergency_contact')
LEFT JOIN planning_center.registrations_emergency_contacts ec
ON ec.emergency_contact_id = ar_ec.relationship_id
WHERE s.archived = false
AND st.starts_at >= CURRENT_DATE
AND st.starts_at <= CURRENT_DATE + INTERVAL '30 days'
)
SELECT
event_name,
event_date::date as date,
TO_CHAR(event_date, 'HH:MI AM') as start_time,
location_name,
location_address,
roster_number,
attendee_name,
first_name,
last_name,
registration_status,
registered_date::date as registration_date,
CASE
WHEN registration_status = 'Waitlisted'
THEN DATE_PART('day', CURRENT_TIMESTAMP - waitlisted_at) || ' days'
ELSE NULL
END as days_on_waitlist,
emergency_contact_name,
emergency_contact_phone,
CASE
WHEN emergency_contact_name IS NULL
THEN 'Missing'
ELSE 'Provided'
END as emergency_contact_status
FROM event_roster
ORDER BY event_date, event_name, roster_number;
Waitlist Management Report
-- Active waitlists with contact information for follow-up
WITH waitlist_details AS (
SELECT
s.signup_id,
s.name as event_name,
st.starts_at as event_date,
cat.name as category,
p.person_id,
p.name as attendee_name,
a.waitlisted_at,
CURRENT_TIMESTAMP - a.waitlisted_at as time_waiting,
ROW_NUMBER() OVER (
PARTITION BY s.signup_id
ORDER BY a.waitlisted_at
) as waitlist_position,
COUNT(*) OVER (PARTITION BY s.signup_id) as total_waitlisted,
-- Get contact info from People module if available
pe.address as primary_email,
pn.number as primary_phone
FROM planning_center.registrations_attendees a
JOIN planning_center.registrations_attendees_relationships ar_signup
ON ar_signup.attendee_id = a.attendee_id
AND ar_signup.relationship_type IN ('Registration', 'registration')
JOIN planning_center.registrations_registrations_relationships ar_signup_sg
ON ar_signup_sg.registration_id = ar_signup.relationship_id
AND ar_signup_sg.relationship_type IN ('Signup', 'signup')
JOIN planning_center.registrations_signups s
ON s.signup_id = ar_signup_sg.relationship_id
LEFT JOIN planning_center.registrations_signups_relationships sr_cat
ON sr_cat.signup_id = s.signup_id
AND sr_cat.relationship_type IN ('Category', 'category')
LEFT JOIN planning_center.registrations_categories cat
ON cat.category_id = sr_cat.relationship_id
LEFT JOIN planning_center.registrations_signups_relationships sr_time
ON sr_time.signup_id = s.signup_id
AND sr_time.relationship_type IN ('SignupTime', 'signup_time')
LEFT JOIN planning_center.registrations_signup_times st
ON st.signup_time_id = sr_time.relationship_id
JOIN planning_center.registrations_attendees_relationships ar_person
ON ar_person.attendee_id = a.attendee_id
AND ar_person.relationship_type IN ('Person', 'person')
JOIN planning_center.registrations_people p
ON p.person_id = ar_person.relationship_id
LEFT JOIN planning_center.people_people pp
ON pp.person_id = p.person_id
LEFT JOIN planning_center.people_emails_relationships em_r
ON em_r.relationship_id = pp.person_id AND em_r.relationship_type IN ('Person', 'person')
LEFT JOIN planning_center.people_emails pe
ON pe.email_id = em_r.email_id AND pe.is_primary = true
LEFT JOIN planning_center.people_phone_numbers_relationships pn_r
ON pn_r.relationship_id = pp.person_id AND pn_r.relationship_type IN ('Person', 'person')
LEFT JOIN planning_center.people_phone_numbers pn
ON pn.phone_number_id = pn_r.phone_number_id AND pn.is_primary = true
WHERE a.waitlisted = true
AND s.archived = false
AND st.starts_at >= CURRENT_DATE
),
recent_activity AS (
SELECT
s.signup_id,
COUNT(CASE
WHEN a.canceled = true
AND a.updated_at >= CURRENT_DATE - INTERVAL '7 days'
THEN 1
END) as recent_cancellations
FROM planning_center.registrations_signups s
JOIN planning_center.registrations_registrations_relationships ar_sg
ON ar_sg.relationship_type IN ('Signup', 'signup')
AND ar_sg.relationship_id = s.signup_id
JOIN planning_center.registrations_attendees_relationships ar
ON ar.relationship_type IN ('Registration', 'registration')
AND ar.relationship_id = ar_sg.registration_id
JOIN planning_center.registrations_attendees a
ON a.attendee_id = ar.attendee_id
GROUP BY s.signup_id
)
SELECT
wd.event_name,
wd.event_date::date as event_date,
DATE_PART('day', wd.event_date - CURRENT_DATE) as days_until_event,
COALESCE(wd.category, 'Uncategorized') as category,
wd.waitlist_position,
wd.total_waitlisted,
wd.attendee_name,
COALESCE(wd.primary_email, 'No email on file') as email,
COALESCE(wd.primary_phone, 'No phone on file') as phone,
wd.waitlisted_at::date as waitlist_date,
EXTRACT(DAY FROM wd.time_waiting) as days_waiting,
COALESCE(ra.recent_cancellations, 0) as spots_opened_this_week,
CASE
WHEN wd.waitlist_position <= ra.recent_cancellations
THEN 'High - Likely to get spot'
WHEN wd.waitlist_position <= wd.total_waitlisted * 0.3
THEN 'Medium - Possible opening'
ELSE 'Low - Unlikely this week'
END as conversion_likelihood
FROM waitlist_details wd
LEFT JOIN recent_activity ra ON ra.signup_id = wd.signup_id
ORDER BY
wd.event_date,
wd.event_name,
wd.waitlist_position;
Category Performance Reports
The report below ranks categories by demand. For estimated revenue based
on each attendee’s listed selection price, see Estimated Revenue by Selection
Type.
Use the Giving module when you need received-payment reporting.
Category Performance Report
-- Category-level demand metrics for strategic planning
WITH category_metrics AS (
SELECT
COALESCE(cat.name, 'Uncategorized') as category,
DATE_PART('quarter', st.starts_at) as quarter,
DATE_PART('year', st.starts_at) as year,
COUNT(DISTINCT s.signup_id) as events,
COUNT(DISTINCT a.attendee_id) as unique_attendees,
COUNT(CASE WHEN a.active = true THEN 1 END) as 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
LEFT JOIN planning_center.registrations_signups_relationships sr_cat
ON sr_cat.signup_id = s.signup_id
AND sr_cat.relationship_type IN ('Category', 'category')
LEFT JOIN planning_center.registrations_categories cat
ON cat.category_id = sr_cat.relationship_id
LEFT JOIN planning_center.registrations_signups_relationships sr_time
ON sr_time.signup_id = s.signup_id
AND sr_time.relationship_type IN ('SignupTime', 'signup_time')
LEFT JOIN planning_center.registrations_signup_times st
ON st.signup_time_id = sr_time.relationship_id
LEFT JOIN planning_center.registrations_registrations_relationships ar_sg
ON ar_sg.relationship_type IN ('Signup', 'signup')
AND ar_sg.relationship_id = s.signup_id
LEFT JOIN planning_center.registrations_attendees_relationships ar
ON ar.relationship_type IN ('Registration', 'registration')
AND ar.relationship_id = ar_sg.registration_id
LEFT JOIN planning_center.registrations_attendees a
ON a.attendee_id = ar.attendee_id
WHERE s.archived = false
AND st.starts_at >= DATE_TRUNC('year', CURRENT_DATE)
GROUP BY cat.name, DATE_PART('quarter', st.starts_at), DATE_PART('year', st.starts_at)
),
category_rankings AS (
SELECT
category,
'Q' || quarter || ' ' || year as period,
quarter,
year,
events,
unique_attendees,
registrations,
ROUND(cancellations * 100.0 / NULLIF(registrations + cancellations, 0), 1) as cancellation_rate,
ROUND(waitlisted * 100.0 / NULLIF(registrations, 0), 1) as waitlist_rate,
ROUND(registrations::NUMERIC / NULLIF(events, 0), 1) as avg_registrations_per_event,
RANK() OVER (PARTITION BY quarter, year ORDER BY registrations DESC) as attendance_rank
FROM category_metrics
)
SELECT
category,
period,
events,
unique_attendees,
registrations,
avg_registrations_per_event,
cancellation_rate,
waitlist_rate,
attendance_rank,
CASE
WHEN attendance_rank <= 3 AND COALESCE(cancellation_rate, 0) <= 20 THEN 'Star Performer'
WHEN attendance_rank <= 3 THEN 'Strong Performer'
WHEN cancellation_rate > 20 THEN 'Needs Attention'
ELSE 'Standard'
END as performance_tier
FROM category_rankings
ORDER BY period DESC, attendance_rank;
Trend Analysis Reports
Registration Velocity Trends
-- Track registration patterns to optimize marketing timing
WITH daily_registrations AS (
SELECT
s.signup_id,
s.name as event_name,
st.starts_at as event_date,
DATE(a.created_at) as registration_date,
st.starts_at::date - a.created_at::date as days_before_event,
COUNT(*) as daily_registrations,
SUM(COUNT(*)) OVER (
PARTITION BY s.signup_id
ORDER BY DATE(a.created_at)
ROWS UNBOUNDED PRECEDING
) as cumulative_registrations
FROM planning_center.registrations_signups s
JOIN planning_center.registrations_signups_relationships sr_time
ON sr_time.signup_id = s.signup_id
AND sr_time.relationship_type IN ('SignupTime', 'signup_time')
JOIN planning_center.registrations_signup_times st
ON st.signup_time_id = sr_time.relationship_id
JOIN planning_center.registrations_registrations_relationships ar_sg
ON ar_sg.relationship_type IN ('Signup', 'signup')
AND ar_sg.relationship_id = s.signup_id
JOIN planning_center.registrations_attendees_relationships ar
ON ar.relationship_type IN ('Registration', 'registration')
AND ar.relationship_id = ar_sg.registration_id
JOIN planning_center.registrations_attendees a
ON a.attendee_id = ar.attendee_id
AND a.active = true
WHERE s.archived = false
AND st.starts_at >= CURRENT_DATE - INTERVAL '90 days'
AND st.starts_at <= CURRENT_DATE + INTERVAL '90 days'
GROUP BY s.signup_id, s.name, st.starts_at, DATE(a.created_at)
),
velocity_patterns AS (
SELECT
CASE
WHEN days_before_event >= 60 THEN '60+ days out'
WHEN days_before_event >= 30 THEN '30-59 days out'
WHEN days_before_event >= 14 THEN '14-29 days out'
WHEN days_before_event >= 7 THEN '7-13 days out'
WHEN days_before_event >= 1 THEN '1-6 days out'
WHEN days_before_event = 0 THEN 'Day of event'
ELSE 'After event start'
END as registration_window,
COUNT(DISTINCT event_name) as events,
SUM(daily_registrations) as total_registrations,
AVG(daily_registrations) as avg_daily_registrations,
PERCENTILE_CONT(0.5) WITHIN GROUP (ORDER BY daily_registrations) as median_daily_registrations,
MAX(daily_registrations) as peak_daily_registrations
FROM daily_registrations
WHERE days_before_event >= 0
GROUP BY registration_window
)
SELECT
registration_window,
events,
total_registrations,
ROUND(avg_daily_registrations::numeric, 1) as avg_daily,
ROUND(median_daily_registrations::numeric, 1) as median_daily,
peak_daily_registrations as peak_daily,
ROUND(total_registrations * 100.0 / SUM(total_registrations) OVER (), 1) as pct_of_total,
SUM(total_registrations) OVER (
ORDER BY
CASE registration_window
WHEN '60+ days out' THEN 1
WHEN '30-59 days out' THEN 2
WHEN '14-29 days out' THEN 3
WHEN '7-13 days out' THEN 4
WHEN '1-6 days out' THEN 5
WHEN 'Day of event' THEN 6
ELSE 7
END
) as cumulative_total,
ROUND(
SUM(total_registrations) OVER (
ORDER BY
CASE registration_window
WHEN '60+ days out' THEN 1
WHEN '30-59 days out' THEN 2
WHEN '14-29 days out' THEN 3
WHEN '7-13 days out' THEN 4
WHEN '1-6 days out' THEN 5
WHEN 'Day of event' THEN 6
ELSE 7
END
) * 100.0 / SUM(total_registrations) OVER (),
1
) as cumulative_pct
FROM velocity_patterns
ORDER BY
CASE registration_window
WHEN '60+ days out' THEN 1
WHEN '30-59 days out' THEN 2
WHEN '14-29 days out' THEN 3
WHEN '7-13 days out' THEN 4
WHEN '1-6 days out' THEN 5
WHEN 'Day of event' THEN 6
ELSE 7
END;
Export Templates
CSV Export for Mail Merge
-- Export format for email campaigns and mail merge
SELECT
p.first_name,
p.last_name,
p.name as full_name,
s.name as event_name,
st.starts_at::date as event_date,
TO_CHAR(st.starts_at, 'FMDay, FMMonth DD at HH:MI AM') as event_datetime_formatted,
loc.name as venue_name,
loc.formatted_address as venue_address,
CASE
WHEN a.waitlisted = true THEN 'You are on the waitlist'
WHEN a.active = true THEN 'Your registration is confirmed'
ELSE 'Registration status pending'
END as status_message,
s.new_registration_url as registration_link,
pe.address as email_address
FROM planning_center.registrations_attendees a
JOIN planning_center.registrations_attendees_relationships ar_signup
ON ar_signup.attendee_id = a.attendee_id
AND ar_signup.relationship_type IN ('Registration', 'registration')
JOIN planning_center.registrations_registrations_relationships ar_signup_sg
ON ar_signup_sg.registration_id = ar_signup.relationship_id
AND ar_signup_sg.relationship_type IN ('Signup', 'signup')
JOIN planning_center.registrations_signups s
ON s.signup_id = ar_signup_sg.relationship_id
JOIN planning_center.registrations_signups_relationships sr_time
ON sr_time.signup_id = s.signup_id
AND sr_time.relationship_type IN ('SignupTime', 'signup_time')
JOIN planning_center.registrations_signup_times st
ON st.signup_time_id = sr_time.relationship_id
LEFT JOIN planning_center.registrations_signups_relationships sr_loc
ON sr_loc.signup_id = s.signup_id
AND sr_loc.relationship_type IN ('SignupLocation', 'signup_location')
LEFT JOIN planning_center.registrations_signup_locations loc
ON loc.signup_location_id = sr_loc.relationship_id
JOIN planning_center.registrations_attendees_relationships ar_person
ON ar_person.attendee_id = a.attendee_id
AND ar_person.relationship_type IN ('Person', 'person')
JOIN planning_center.registrations_people p
ON p.person_id = ar_person.relationship_id
LEFT JOIN planning_center.people_people pp
ON pp.person_id = p.person_id
LEFT JOIN planning_center.people_emails_relationships em_r
ON em_r.relationship_id = pp.person_id AND em_r.relationship_type IN ('Person', 'person')
LEFT JOIN planning_center.people_emails pe
ON pe.email_id = em_r.email_id AND pe.is_primary = true
WHERE s.archived = false
AND st.starts_at >= CURRENT_DATE
AND st.starts_at <= CURRENT_DATE + INTERVAL '30 days'
AND pe.address IS NOT NULL
ORDER BY st.starts_at, s.name, p.last_name, p.first_name;
Performance Notes
- Read-only access - Your Parable connection grants
SELECTonly. Materialized views, indexes, and other DDL are not available; cache frequently accessed summaries in your BI tool or save them as Parable reports. - Date filters first - Narrow by event date or registration date before joining relationship tables.
- Batch Processing - Run heavy reports during off-peak hours
- Caching - Cache executive dashboard queries that don’t need the most current data
Customization Tips
- Replace date ranges to match your reporting periods
- Add campus or location filters for multi-site churches
- Modify category groupings to match your event structure
- Adjust financial calculations based on your fee structure
- Add custom fields from People module for deeper demographics