Start here to learn the fundamentals of querying your church’s calendar and facility data. Each example builds your confidence with SQL while solving real scheduling challenges.
Query Customization Required
These example queries demonstrate common patterns but may require adjustments to match your specific database schema and field names. Test thoroughly in your environment before use.
IMPORTANT: All tables in the Planning Center Calendar module live in the planning_center schema. Always prefix table names with planning_center. in your SQL.✅ CORRECT: SELECT * FROM planning_center.calendar_events
❌ INCORRECT: SELECT * FROM calendar_events
-- Only show approved events visible to the congregationSELECT event_id, name, TRIM(REGEXP_REPLACE(REGEXP_REPLACE(description, '<[^>]*>', '', 'g'), '\s+', ' ', 'g')) as description, featured, -- Featured events for promotion image_urlFROM planning_center.calendar_eventsWHERE approval_status = 'A' -- A = Approved AND visible_in_church_center = trueORDER BY name;
-- Get the next 20 scheduled eventsSELECT ei.event_instance_id, e.name as event_name, ei.starts_at, ei.ends_at, ei.location, ei.all_day_event, EXTRACT(EPOCH FROM (ei.ends_at - ei.starts_at))/3600 as duration_hoursFROM planning_center.calendar_event_instances eiJOIN planning_center.calendar_events e ON ei.event_id = e.event_idWHERE ei.starts_at >= CURRENT_TIMESTAMPORDER BY ei.starts_atLIMIT 20;
-- All events for the current weekSELECT e.name, ei.starts_at, ei.ends_at, ei.location, CASE WHEN ei.all_day_event THEN 'All Day' ELSE TO_CHAR(ei.starts_at, 'HH12:MI AM') END as start_timeFROM planning_center.calendar_event_instances eiJOIN planning_center.calendar_events e ON ei.event_id = e.event_idWHERE ei.starts_at >= DATE_TRUNC('week', CURRENT_DATE) AND ei.starts_at < DATE_TRUNC('week', CURRENT_DATE) + INTERVAL '1 week'ORDER BY ei.starts_at;
-- Find all recurring events and their patternsSELECT e.name, ei.recurrence, ei.recurrence_description, COUNT(*) as instance_count, MIN(ei.starts_at) as first_occurrence, MAX(ei.starts_at) as last_occurrenceFROM planning_center.calendar_event_instances eiJOIN planning_center.calendar_events e ON ei.event_id = e.event_idWHERE ei.recurrence IS NOT NULLGROUP BY e.event_id, e.name, ei.recurrence, ei.recurrence_descriptionORDER BY instance_count DESC;
-- See which days are busiestSELECT TO_CHAR(starts_at, 'Day') as day_of_week, EXTRACT(DOW FROM starts_at) as day_number, COUNT(*) as event_countFROM planning_center.calendar_event_instancesWHERE starts_at >= CURRENT_DATE - INTERVAL '90 days' AND starts_at <= CURRENT_DATE + INTERVAL '30 days'GROUP BY TO_CHAR(starts_at, 'Day'), EXTRACT(DOW FROM starts_at)ORDER BY day_number;
-- Find all rooms and their detailsSELECT resource_id, name, description, home_location, quantity as capacity, path_name -- Location hierarchyFROM planning_center.calendar_resourcesWHERE kind = 'Room'ORDER BY name;
-- Resources with expiration dates (like rentals or leases)SELECT name, kind, expires_at, CASE WHEN expires_at < CURRENT_DATE THEN 'Expired' WHEN expires_at < CURRENT_DATE + INTERVAL '30 days' THEN 'Expiring Soon' ELSE 'Active' END as statusFROM planning_center.calendar_resourcesWHERE expires_at IS NOT NULLORDER BY expires_at;
-- See what's booked right nowSELECT r.name as resource_name, r.kind as resource_type, rb.starts_at, rb.ends_at, rb.quantity as quantity_bookedFROM planning_center.calendar_resource_bookings rbJOIN planning_center.calendar_resources r ON rb.resource_id = r.resource_idWHERE rb.starts_at <= CURRENT_TIMESTAMP AND rb.ends_at >= CURRENT_TIMESTAMPORDER BY r.name;
-- Check room bookings for a specific dateSELECT r.name as room_name, rb.starts_at::time as start_time, rb.ends_at::time as end_time, ei.location, e.name as event_nameFROM planning_center.calendar_resource_bookings rbJOIN planning_center.calendar_resources r ON rb.resource_id = r.resource_idJOIN planning_center.calendar_event_instances ei ON rb.event_instance_id = ei.event_instance_idJOIN planning_center.calendar_events e ON ei.event_id = e.event_idWHERE r.kind = 'Room' AND DATE(rb.starts_at) = '2024-01-15' -- Change to your dateORDER BY r.name, rb.starts_at;
-- Find resources NOT booked during a specific timeSELECT r.resource_id, r.name, r.kind, r.quantityFROM planning_center.calendar_resources rWHERE r.kind = 'Room' -- Change to desired resource type AND NOT EXISTS ( SELECT 1 FROM planning_center.calendar_resource_bookings rb WHERE rb.resource_id = r.resource_id AND rb.starts_at < '2024-01-15 14:00:00'::timestamp -- Your end time AND rb.ends_at > '2024-01-15 12:00:00'::timestamp -- Your start time )ORDER BY r.name;
-- See how often each resource is usedSELECT r.name as resource_name, r.kind as resource_type, COUNT(rb.resource_booking_id) as total_bookings, SUM(EXTRACT(EPOCH FROM (rb.ends_at - rb.starts_at))/3600) as total_hours_booked, MIN(rb.starts_at) as first_booking, MAX(rb.ends_at) as last_bookingFROM planning_center.calendar_resources rLEFT JOIN planning_center.calendar_resource_bookings rb ON r.resource_id = rb.resource_idGROUP BY r.resource_id, r.name, r.kindORDER BY total_bookings DESC;
-- All events happening todaySELECT e.name, CASE WHEN ei.all_day_event THEN 'All Day' ELSE TO_CHAR(ei.starts_at, 'HH12:MI AM') END as start_time, ei.locationFROM planning_center.calendar_events eJOIN planning_center.calendar_event_instances ei ON e.event_id = ei.event_idWHERE (DATE(ei.starts_at) = CURRENT_DATE OR (ei.all_day_event = true AND DATE(ei.starts_at) <= CURRENT_DATE AND DATE(ei.ends_at) >= CURRENT_DATE))ORDER BY ei.all_day_event DESC, ei.starts_at;
-- All events in the current monthSELECT e.name, ei.starts_at, ei.location, ei.all_day_eventFROM planning_center.calendar_events eJOIN planning_center.calendar_event_instances ei ON e.event_id = ei.event_idWHERE ei.starts_at >= DATE_TRUNC('month', CURRENT_DATE) AND ei.starts_at < DATE_TRUNC('month', CURRENT_DATE) + INTERVAL '1 month'ORDER BY ei.starts_at;
-- Events on Saturdays and SundaysSELECT e.name, ei.starts_at, TO_CHAR(ei.starts_at, 'Day') as day_name, ei.locationFROM planning_center.calendar_events eJOIN planning_center.calendar_event_instances ei ON e.event_id = ei.event_idWHERE EXTRACT(DOW FROM ei.starts_at) IN (0, 6) -- 0 = Sunday, 6 = Saturday AND ei.starts_at >= CURRENT_DATE AND ei.starts_at < CURRENT_DATE + INTERVAL '30 days'ORDER BY ei.starts_at;
-- Categorize events by time of daySELECT CASE WHEN EXTRACT(HOUR FROM starts_at) < 12 THEN 'Morning' WHEN EXTRACT(HOUR FROM starts_at) < 17 THEN 'Afternoon' ELSE 'Evening' END as time_of_day, COUNT(*) as event_countFROM planning_center.calendar_event_instancesWHERE starts_at >= CURRENT_DATE - INTERVAL '30 days' AND all_day_event = falseGROUP BY time_of_dayORDER BY CASE time_of_day WHEN 'Morning' THEN 1 WHEN 'Afternoon' THEN 2 WHEN 'Evening' THEN 3 END;
-- Find events by their tags (using relationship tables)SELECT DISTINCT e.name as event_name, t.name as tag_name, tg.name as tag_groupFROM planning_center.calendar_events eJOIN planning_center.calendar_events_relationships er ON e.event_id = er.event_id AND er.relationship_type = 'Tag'JOIN planning_center.calendar_tags t ON er.relationship_id = t.tag_idLEFT JOIN planning_center.calendar_tag_groups tg ON t.tag_group_id = tg.tag_group_idORDER BY e.name, tg.name, t.name;
-- See which tags are used mostSELECT t.name as tag_name, tg.name as tag_group, COUNT(DISTINCT er.event_id) as event_countFROM planning_center.calendar_tags tLEFT JOIN planning_center.calendar_tag_groups tg ON t.tag_group_id = tg.tag_group_idLEFT JOIN planning_center.calendar_events_relationships er ON t.tag_id = er.relationship_id AND er.relationship_type = 'Tag'GROUP BY t.tag_id, t.name, tg.nameORDER BY event_count DESC;
-- Extract useful parts from timestampsSELECT DATE(starts_at) as event_date, starts_at::time as start_time, TO_CHAR(starts_at, 'Day') as day_name, EXTRACT(HOUR FROM starts_at) as hour_of_day
-- Ensure no overlapping bookingsWHERE NOT EXISTS ( SELECT 1 FROM calendar_resource_bookings rb WHERE rb.resource_id = r.resource_id AND rb.starts_at < [your_end_time] AND rb.ends_at > [your_start_time])
-- User-friendly time formatsTO_CHAR(starts_at, 'HH12:MI AM') as time_12hr, -- 02:30 PMTO_CHAR(starts_at, 'HH24:MI') as time_24hr, -- 14:30TO_CHAR(starts_at, 'Mon DD, YYYY') as date_str -- Jan 15, 2024