Basic Planning Center Calendar Queries
Start querying Planning Center Calendar data: list upcoming events, see which rooms are in use, and check which resources are booked and when.
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.
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.
Query Requirements
Schema Prefix
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
Row Level Security (RLS)
Row Level Security automatically handles:
- tenant_organization_id – isolates data to your organization
- system_status – returns active records by default
Do not duplicate these filters in your queries—RLS already enforces them and extra predicates can hide data or slow execution:
- ❌
WHERE tenant_organization_id = 1 - ❌
WHERE system_status = 'active'
Let RLS govern tenant and status filtering so you can focus on scheduling logic and resource constraints.
Table of Contents
- Viewing Events
- Event Instances and Scheduling
- Resources and Rooms
- Resource Bookings
- Date and Time Queries
- Tags and Categories
Viewing Events
List All Events
-- View all your church's events
SELECT
event_id,
name,
TRIM(REGEXP_REPLACE(REGEXP_REPLACE(description, '<[^>]*>', '', 'g'), '\s+', ' ', 'g')) as description,
approval_status,
visible_in_church_center,
created_at
FROM planning_center.calendar_events
ORDER BY created_at DESC
LIMIT 50;Active and Approved Events
-- Only show approved events visible to the congregation
SELECT
event_id,
name,
TRIM(REGEXP_REPLACE(REGEXP_REPLACE(description, '<[^>]*>', '', 'g'), '\s+', ' ', 'g')) as description,
featured, -- Featured events for promotion
image_url
FROM planning_center.calendar_events
WHERE approval_status = 'A' -- A = Approved
AND visible_in_church_center = true
ORDER BY name;Search Events by Name
-- Find events containing specific keywords
SELECT
event_id,
name,
TRIM(REGEXP_REPLACE(REGEXP_REPLACE(description, '<[^>]*>', '', 'g'), '\s+', ' ', 'g')) as description,
summary
FROM planning_center.calendar_events
WHERE LOWER(name) LIKE '%youth%'
OR LOWER(REGEXP_REPLACE(description, '<[^>]*>', '', 'g')) LIKE '%youth%'
ORDER BY name;Event Instances and Scheduling
Upcoming Event Instances
-- Get the next 20 scheduled events
SELECT
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_hours
FROM planning_center.calendar_event_instances ei
JOIN planning_center.calendar_event_instances_relationships eir
ON eir.event_instance_id = ei.event_instance_id AND eir.relationship_type = 'Event'
JOIN planning_center.calendar_events e ON e.event_id = eir.relationship_id
WHERE ei.starts_at >= CURRENT_TIMESTAMP
ORDER BY ei.starts_at
LIMIT 20;This Week's Schedule
-- All events for the current week
SELECT
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_time
FROM planning_center.calendar_event_instances ei
JOIN planning_center.calendar_event_instances_relationships eir
ON eir.event_instance_id = ei.event_instance_id AND eir.relationship_type = 'Event'
JOIN planning_center.calendar_events e ON e.event_id = eir.relationship_id
WHERE 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;Recurring Events
-- Find all recurring events and their patterns
SELECT
e.name,
ei.recurrence,
ei.recurrence_description,
COUNT(*) as instance_count,
MIN(ei.starts_at) as first_occurrence,
MAX(ei.starts_at) as last_occurrence
FROM planning_center.calendar_event_instances ei
JOIN planning_center.calendar_event_instances_relationships eir
ON eir.event_instance_id = ei.event_instance_id AND eir.relationship_type = 'Event'
JOIN planning_center.calendar_events e ON e.event_id = eir.relationship_id
WHERE ei.recurrence <> 'None'
GROUP BY e.event_id, e.name, ei.recurrence, ei.recurrence_description
ORDER BY instance_count DESC;Events by Day of Week
-- See which days are busiest
SELECT
TO_CHAR(starts_at, 'FMDay') as day_of_week,
EXTRACT(DOW FROM starts_at) as day_number,
COUNT(*) as event_count
FROM planning_center.calendar_event_instances
WHERE starts_at >= CURRENT_DATE - INTERVAL '90 days'
AND starts_at <= CURRENT_DATE + INTERVAL '30 days'
GROUP BY TO_CHAR(starts_at, 'FMDay'), EXTRACT(DOW FROM starts_at)
ORDER BY day_number;Resources and Rooms
List All Resources
-- View all bookable resources
SELECT
resource_id,
name,
kind, -- Room or Resource
description,
quantity,
home_location
FROM planning_center.calendar_resources
ORDER BY kind, name;Available Rooms
-- Find all rooms and their details
SELECT
resource_id,
name,
description,
home_location,
quantity as capacity,
path_name -- Location hierarchy
FROM planning_center.calendar_resources
WHERE kind = 'Room'
ORDER BY name;Non-Room Resources
-- List all non-room resources (equipment, etc.)
SELECT
resource_id,
name,
description,
quantity,
serial_number,
home_location
FROM planning_center.calendar_resources
WHERE kind = 'Resource'
ORDER BY name;Resources Expiring Soon
expires_at is optional. This query returns no rows when your organization
does not track expiration dates on resources.
-- 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 status
FROM planning_center.calendar_resources
WHERE expires_at IS NOT NULL
ORDER BY expires_at;Resource Bookings
Current Bookings
This is a point-in-time snapshot. An empty result means no resource booking spans the exact time when the query runs; future and past bookings are not included.
-- See what's booked right now
SELECT
r.name as resource_name,
r.kind as resource_type,
rb.starts_at,
rb.ends_at,
rb.quantity as quantity_booked
FROM planning_center.calendar_resource_bookings rb
JOIN planning_center.calendar_resource_bookings_relationships rbr
ON rbr.resource_booking_id = rb.resource_booking_id AND rbr.relationship_type = 'Resource'
JOIN planning_center.calendar_resources r ON r.resource_id = rbr.relationship_id
WHERE rb.starts_at <= CURRENT_TIMESTAMP
AND rb.ends_at >= CURRENT_TIMESTAMP
ORDER BY r.name;Room Schedule for Specific Date
-- Check room bookings for a specific date
SELECT
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_name
FROM planning_center.calendar_resource_bookings rb
JOIN planning_center.calendar_resource_bookings_relationships rbr_res
ON rbr_res.resource_booking_id = rb.resource_booking_id AND rbr_res.relationship_type = 'Resource'
JOIN planning_center.calendar_resources r ON r.resource_id = rbr_res.relationship_id
JOIN planning_center.calendar_resource_bookings_relationships rbr_ei
ON rbr_ei.resource_booking_id = rb.resource_booking_id AND rbr_ei.relationship_type = 'EventInstance'
JOIN planning_center.calendar_event_instances ei ON ei.event_instance_id = rbr_ei.relationship_id
JOIN planning_center.calendar_event_instances_relationships eir
ON eir.event_instance_id = ei.event_instance_id AND eir.relationship_type = 'Event'
JOIN planning_center.calendar_events e ON e.event_id = eir.relationship_id
WHERE r.kind = 'Room'
AND DATE(rb.starts_at) = CURRENT_DATE -- Change to any date you want
ORDER BY r.name, rb.starts_at;Find Available Resources
-- Find resources NOT booked during a specific time
SELECT
r.resource_id,
r.name,
r.kind,
r.quantity
FROM planning_center.calendar_resources r
WHERE r.kind = 'Room' -- Change to desired resource type
AND NOT EXISTS (
SELECT 1
FROM planning_center.calendar_resource_bookings rb
JOIN planning_center.calendar_resource_bookings_relationships rbr
ON rbr.resource_booking_id = rb.resource_booking_id AND rbr.relationship_type = 'Resource'
WHERE rbr.relationship_id = r.resource_id
AND rb.starts_at < (CURRENT_DATE + TIME '14:00') -- Your end time
AND rb.ends_at > (CURRENT_DATE + TIME '12:00') -- Your start time
)
ORDER BY r.name;Resource Utilization Summary
-- See how often each resource is used
SELECT
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_booking
FROM planning_center.calendar_resources r
LEFT JOIN planning_center.calendar_resource_bookings_relationships rbr
ON rbr.relationship_id = r.resource_id AND rbr.relationship_type = 'Resource'
LEFT JOIN planning_center.calendar_resource_bookings rb
ON rb.resource_booking_id = rbr.resource_booking_id
GROUP BY r.resource_id, r.name, r.kind
ORDER BY total_bookings DESC;Date and Time Queries
Today's Events
-- All events happening today
SELECT
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.location
FROM planning_center.calendar_event_instances ei
JOIN planning_center.calendar_event_instances_relationships eir
ON eir.event_instance_id = ei.event_instance_id AND eir.relationship_type = 'Event'
JOIN planning_center.calendar_events e ON e.event_id = eir.relationship_id
WHERE (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;This Month's Events
-- All events in the current month
SELECT
e.name,
ei.starts_at,
ei.location,
ei.all_day_event
FROM planning_center.calendar_event_instances ei
JOIN planning_center.calendar_event_instances_relationships eir
ON eir.event_instance_id = ei.event_instance_id AND eir.relationship_type = 'Event'
JOIN planning_center.calendar_events e ON e.event_id = eir.relationship_id
WHERE 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;Weekend Events
-- Events on Saturdays and Sundays
SELECT
e.name,
ei.starts_at,
TO_CHAR(ei.starts_at, 'FMDay') as day_name,
ei.location
FROM planning_center.calendar_event_instances ei
JOIN planning_center.calendar_event_instances_relationships eir
ON eir.event_instance_id = ei.event_instance_id AND eir.relationship_type = 'Event'
JOIN planning_center.calendar_events e ON e.event_id = eir.relationship_id
WHERE 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;Events by Time of Day
-- Categorize events by time of day
WITH categorized AS (
SELECT
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
FROM planning_center.calendar_event_instances
WHERE starts_at >= CURRENT_DATE - INTERVAL '30 days'
AND all_day_event = false
)
SELECT
time_of_day,
COUNT(*) as event_count
FROM categorized
GROUP BY time_of_day
ORDER BY
CASE time_of_day
WHEN 'Morning' THEN 1
WHEN 'Afternoon' THEN 2
WHEN 'Evening' THEN 3
END;Tags and Categories
Events with Tags
-- Find events by their tags (using relationship tables)
SELECT DISTINCT
e.name as event_name,
t.name as tag_name,
tg.name as tag_group
FROM planning_center.calendar_events e
JOIN 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_id
LEFT JOIN planning_center.calendar_tag_groups_relationships tgr
ON tgr.relationship_id = t.tag_id AND tgr.relationship_type = 'Tag'
LEFT JOIN planning_center.calendar_tag_groups tg
ON tg.tag_group_id = tgr.tag_group_id
ORDER BY e.name, tg.name, t.name;Popular Tags
-- See which tags are used most
SELECT
t.name as tag_name,
tg.name as tag_group,
COUNT(DISTINCT er.event_id) as event_count
FROM planning_center.calendar_tags t
LEFT JOIN planning_center.calendar_tag_groups_relationships tgr
ON tgr.relationship_id = t.tag_id AND tgr.relationship_type = 'Tag'
LEFT JOIN planning_center.calendar_tag_groups tg
ON tg.tag_group_id = tgr.tag_group_id
LEFT 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.name
ORDER BY event_count DESC;Tips for Writing Calendar Queries
1. Handle All-Day Events
-- All-day events span from start date to end date
SELECT event_instance_id, name, starts_at, ends_at
FROM planning_center.calendar_event_instances
WHERE all_day_event = true
AND DATE(starts_at) <= CURRENT_DATE
AND DATE(ends_at) >= CURRENT_DATE;2. Work with Timestamps
-- Extract useful parts from timestamps
SELECT
DATE(starts_at) as event_date,
starts_at::time as start_time,
TO_CHAR(starts_at, 'FMDay') as day_name,
EXTRACT(HOUR FROM starts_at) as hour_of_day
FROM planning_center.calendar_event_instances;3. Calculate Duration
-- Get event duration in hours
SELECT
event_instance_id,
name,
EXTRACT(EPOCH FROM (ends_at - starts_at)) / 3600 as hours
FROM planning_center.calendar_event_instances;4. Check Resource Availability
-- Ensure no overlapping bookings (join via relationship table)
SELECT r.resource_id, r.name
FROM planning_center.calendar_resources r
WHERE NOT EXISTS (
SELECT 1 FROM planning_center.calendar_resource_bookings rb
JOIN planning_center.calendar_resource_bookings_relationships rbr
ON rbr.resource_booking_id = rb.resource_booking_id AND rbr.relationship_type = 'Resource'
WHERE rbr.relationship_id = r.resource_id
AND rb.starts_at < (CURRENT_DATE + TIME '12:00')
AND rb.ends_at > (CURRENT_DATE + TIME '10:00')
);5. Format Times for Display
-- User-friendly time formats
SELECT
TO_CHAR(starts_at, 'HH12:MI AM') as time_12hr, -- 02:30 PM
TO_CHAR(starts_at, 'HH24:MI') as time_24hr, -- 14:30
TO_CHAR(starts_at, 'Mon DD, YYYY') as date_str -- Jan 15, 2024
FROM planning_center.calendar_event_instances;Common Issues & Solutions
Issue: Events appearing multiple times
Solution: You might be joining to event instances - use DISTINCT or GROUP BY.
Issue: Missing recurring events
Solution: Check the date range includes future instances, not just the master event.
Issue: Resources showing as available when booked
Solution: Ensure your time comparison includes both start AND end times for overlaps.
Issue: All-day events not showing
Solution: Remember to check both the date AND the all_day_event flag.
Next Steps
Ready for more complex queries? Check out:
- Advanced Queries - Conflict detection, optimization, and analytics
- Reporting Examples - Complete reports you can use immediately
Planning Center Calendar Data Model
Complete reference for Planning Center Calendar tables in Parable: events, event instances, resources, rooms, tags, and the relationships between them.
Advanced Planning Center Calendar Queries
Advanced Calendar SQL for churches: detect scheduling conflicts, measure room utilization over time, and optimize how resources are allocated.