Basic Planning Center Registrations Queries
Practical SQL for Planning Center Registrations: list signups by event, count attendees, and filter registrations by status or date.
Simple, Practical SQL for Event Management
Start here to learn the fundamentals of querying Planning Center Registrations data. These examples cover common scenarios you'll encounter in day-to-day event management.
Query Requirements
Schema Prefix
IMPORTANT: All tables in the Planning Center Registrations module live in the planning_center schema. Always prefix table names with planning_center. in your queries.
✅ 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 – returns active records by default
Skip manual filters for these columns—RLS already enforces them and redundant predicates can mask data or slow execution:
- ❌
WHERE tenant_organization_id = 1 - ❌
WHERE system_status = 'active'
Keep your focus on event-specific filters (archived flags, dates, statuses) while RLS handles tenancy and system status automatically.
Most examples on this page filter archived = false because they answer
"what is happening now" questions — upcoming events, open registration windows,
current waitlists. Completed events are usually archived, so drop that
predicate whenever you are reporting on past events. The
revenue report below does this
deliberately.
Finding Events
List All Active Events
-- Get all non-archived events
SELECT
signup_id,
name,
description,
open_at,
close_at,
created_at
FROM planning_center.registrations_signups
WHERE archived = false
ORDER BY created_at DESC;Find Upcoming Events
-- Events with future dates
SELECT
s.name as event_name,
st.starts_at as event_date,
st.ends_at,
st.all_day
FROM planning_center.registrations_signups s
JOIN planning_center.registrations_signups_relationships sr
ON sr.signup_id = s.signup_id
AND sr.relationship_type IN ('SignupTime', 'signup_time')
JOIN planning_center.registrations_signup_times st
ON st.signup_time_id = sr.relationship_id
WHERE s.archived = false
AND st.starts_at >= CURRENT_DATE
ORDER BY st.starts_at;Events by Category
-- Find events in specific categories
SELECT
s.name as event_name,
c.name as category_name
FROM planning_center.registrations_signups s
JOIN planning_center.registrations_signups_relationships sr
ON sr.signup_id = s.signup_id
AND sr.relationship_type IN ('Category', 'category')
JOIN planning_center.registrations_categories c
ON c.category_id = sr.relationship_id
WHERE s.archived = false
ORDER BY c.name, s.name;Registration Counts
Count Attendees per Event
-- Basic registration counts
SELECT
s.name as event_name,
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 canceled_registrations,
COUNT(CASE WHEN a.waitlisted = true THEN 1 END) as waitlisted
FROM planning_center.registrations_signups s
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
GROUP BY s.name
ORDER BY total_attendees DESC;Recent Registrations
-- Last 20 registrations across all events
SELECT
p.name as attendee_name,
s.name as event_name,
a.created_at as registration_date,
CASE
WHEN a.waitlisted = true THEN 'Waitlisted'
WHEN a.canceled = true THEN 'Canceled'
WHEN a.active = true THEN 'Active'
ELSE 'Unknown'
END as status
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_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
ORDER BY a.created_at DESC
LIMIT 20;Waitlist Management
View Waitlisted Attendees
-- All waitlisted attendees with their wait times
SELECT
p.name as attendee_name,
s.name as event_name,
a.waitlisted_at,
CURRENT_TIMESTAMP - a.waitlisted_at as time_on_waitlist
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_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
WHERE a.waitlisted = true
ORDER BY s.name, a.waitlisted_at;Events with Waitlists
-- Find events that have waitlists
SELECT
s.name as event_name,
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_signups s
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
GROUP BY s.name
HAVING COUNT(CASE WHEN a.waitlisted = true THEN 1 END) > 0
ORDER BY waitlist_count DESC;Location Information
Events by Location
-- List events with their venues
SELECT
s.name as event_name,
sl.name as location_name,
sl.formatted_address,
sl.latitude,
sl.longitude
FROM planning_center.registrations_signups s
JOIN planning_center.registrations_signups_relationships sr
ON sr.signup_id = s.signup_id
AND sr.relationship_type IN ('SignupLocation', 'signup_location')
JOIN planning_center.registrations_signup_locations sl
ON sl.signup_location_id = sr.relationship_id
WHERE s.archived = false
ORDER BY sl.name, s.name;Campus Events
-- Events by campus
SELECT
c.name as campus_name,
s.name as event_name,
st.starts_at as event_date
FROM planning_center.registrations_signups s
JOIN planning_center.registrations_signups_relationships sr_campus
ON sr_campus.signup_id = s.signup_id
AND sr_campus.relationship_type IN ('Campus', 'campus')
JOIN planning_center.registrations_campuses c
ON c.campus_id = sr_campus.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 s.archived = false
ORDER BY c.name, st.starts_at;Pricing and Selection Types
Registration totals include discounts and every attendee on the registration.
They report the amount collected and still owed directly from Planning Center
Registrations. The revenue example below includes archived signups — completed events are
usually archived, so filtering them out would hide most historical revenue.
Add AND s.archived = false to limit the report to non-archived (typically
current and upcoming) events.
Revenue Collected and Outstanding by Event
-- Compare billed, collected, and outstanding registration totals by event.
-- The relationship table has no natural-key uniqueness, so collapse duplicate
-- Signup rows first: each registration must be summed exactly once per event.
WITH registration_events AS (
SELECT DISTINCT registration_id, relationship_id AS signup_id
FROM planning_center.registrations_registrations_relationships
WHERE relationship_type IN ('Signup', 'signup')
)
SELECT
s.signup_id,
s.name as event_name,
COUNT(r.registration_id) as registrations,
COALESCE(SUM(r.total_cost_cents), 0) / 100.0 as total_cost,
COALESCE(SUM(r.total_paid_cents), 0) / 100.0 as total_collected,
COALESCE(SUM(r.total_due_cents), 0) / 100.0 as total_outstanding
FROM planning_center.registrations_registrations r
JOIN registration_events re
ON re.registration_id = r.registration_id
JOIN planning_center.registrations_signups s
ON s.signup_id = re.signup_id
GROUP BY s.signup_id, s.name
ORDER BY total_collected DESC, s.name;Registration Volume by Event
-- How many people actually registered for each event
SELECT
s.name as event_name,
COUNT(DISTINCT a.attendee_id) FILTER (WHERE a.active = true) as active_attendees,
COUNT(DISTINCT a.attendee_id) FILTER (WHERE a.waitlisted = true) as waitlisted,
COUNT(DISTINCT a.attendee_id) FILTER (WHERE a.canceled = true) as canceled
FROM planning_center.registrations_signups s
JOIN planning_center.registrations_registrations_relationships rr
ON rr.relationship_id = s.signup_id
AND rr.relationship_type IN ('Signup', 'signup')
JOIN planning_center.registrations_attendees_relationships ar
ON ar.relationship_id = rr.registration_id
AND ar.relationship_type IN ('Registration', 'registration')
JOIN planning_center.registrations_attendees a
ON a.attendee_id = ar.attendee_id
WHERE s.archived = false
GROUP BY s.name
ORDER BY active_attendees DESC;Emergency Contacts
List Emergency Contacts for Event
-- Get emergency contacts for active attendees
SELECT
p.name as attendee_name,
s.name as event_name,
ec.name as emergency_contact_name,
ec.phone_number as emergency_phone
FROM planning_center.registrations_attendees a
-- Link to signup
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
-- Link 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
-- Link 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 a.active = true
AND s.archived = false
ORDER BY s.name, p.name;Check Emergency Contact Coverage
-- Find attendees without emergency contacts
SELECT
s.name as event_name,
COUNT(DISTINCT a.attendee_id) as total_attendees,
COUNT(DISTINCT ar_ec.attendee_id) as with_emergency_contact,
COUNT(DISTINCT a.attendee_id) - COUNT(DISTINCT ar_ec.attendee_id) as missing_emergency_contact
FROM planning_center.registrations_signups s
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
AND a.active = true
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')
WHERE s.archived = false
GROUP BY s.name
ORDER BY missing_emergency_contact DESC;Date and Time Queries
Events This Month
-- All events happening this month
SELECT
s.name as event_name,
st.starts_at,
st.ends_at,
st.all_day
FROM planning_center.registrations_signups s
JOIN planning_center.registrations_signups_relationships sr
ON sr.signup_id = s.signup_id
AND sr.relationship_type IN ('SignupTime', 'signup_time')
JOIN planning_center.registrations_signup_times st
ON st.signup_time_id = sr.relationship_id
WHERE s.archived = false
AND DATE_PART('month', st.starts_at) = DATE_PART('month', CURRENT_DATE)
AND DATE_PART('year', st.starts_at) = DATE_PART('year', CURRENT_DATE)
ORDER BY st.starts_at;Registration Windows
-- Events with open/closed registration periods
SELECT
name as event_name,
open_at,
close_at,
CASE
WHEN CURRENT_TIMESTAMP < open_at THEN 'Not Yet Open'
WHEN CURRENT_TIMESTAMP BETWEEN open_at AND close_at THEN 'Open'
WHEN CURRENT_TIMESTAMP > close_at THEN 'Closed'
ELSE 'Always Open'
END as registration_status
FROM planning_center.registrations_signups
WHERE archived = false
ORDER BY
CASE
WHEN CURRENT_TIMESTAMP BETWEEN open_at AND close_at THEN 1
WHEN CURRENT_TIMESTAMP < open_at THEN 2
ELSE 3
END,
open_at;People and Attendees
Find Person's Registrations
-- All events a person is registered for
SELECT
p.name as person_name,
s.name as event_name,
a.created_at as registered_at,
CASE
WHEN a.waitlisted = true THEN 'Waitlisted'
WHEN a.canceled = true THEN 'Canceled'
WHEN a.active = true THEN 'Active'
ELSE 'Unknown'
END as status
FROM planning_center.registrations_people p
JOIN planning_center.registrations_attendees_relationships ar_person
ON ar_person.relationship_id = p.person_id
AND ar_person.relationship_type IN ('Person', 'person')
JOIN planning_center.registrations_attendees a
ON a.attendee_id = ar_person.attendee_id
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
WHERE p.name ILIKE '%smith%' -- Search by name
ORDER BY a.created_at DESC;Most Active Participants
-- People registered for the most events
SELECT
p.name as person_name,
COUNT(DISTINCT s.signup_id) as events_registered,
COUNT(CASE WHEN a.active = true THEN 1 END) as active_registrations
FROM planning_center.registrations_people p
JOIN planning_center.registrations_attendees_relationships ar_person
ON ar_person.relationship_id = p.person_id
AND ar_person.relationship_type IN ('Person', 'person')
JOIN planning_center.registrations_attendees a
ON a.attendee_id = ar_person.attendee_id
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
GROUP BY p.name
HAVING COUNT(DISTINCT s.signup_id) > 1
ORDER BY events_registered DESC;Tips for Basic Queries
- Start simple - Begin with single table queries and gradually add joins
- Use DISTINCT carefully - Only when you need unique values
- Filter early - Add WHERE clauses before GROUP BY for better performance
- Test with LIMIT - Add LIMIT 10 when testing queries on large datasets
- Check for NULLs - Many fields can be NULL, use COALESCE or IS NULL checks
- Understand relationships - Always join through the relationship tables
Next Steps
Ready for more complex analysis? Check out our Advanced Queries guide for CTEs, window functions, and cross-module integration.
Planning Center Registrations Data Model
Complete reference for the 11 Planning Center Registrations entity tables in Parable: events, attendees, waitlists, emergency contacts, and more.
Advanced Planning Center Registrations Queries
Advanced Registrations SQL using CTEs and window functions to analyze signup velocity, waitlist movement, and overall event performance.