Skip to main content

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

  1. Start simple - Begin with single table queries and gradually add joins
  2. Use DISTINCT carefully - Only when you need unique values
  3. Filter early - Add WHERE clauses before GROUP BY for better performance
  4. Test with LIMIT - Add LIMIT 10 when testing queries on large datasets
  5. Check for NULLs - Many fields can be NULL, use COALESCE or IS NULL checks
  6. 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.

On this page