Start with these foundational queries to explore your church database. Each example builds your SQL confidence while providing immediate ministry value.
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 People module live in the planning_center schema. Always prefix table names with planning_center. in every query.✅ CORRECT: SELECT * FROM planning_center.people_people
❌ INCORRECT: SELECT * FROM people_people
-- View active people in your databaseSELECT person_id, first_name, last_name, name, status, membership, created_atFROM planning_center.people_peopleWHERE status = 'active'ORDER BY last_name, first_nameLIMIT 100;
-- Find people by name (case-insensitive)SELECT person_id, name, nickname, status, membership, birthdateFROM planning_center.people_peopleWHERE status = 'active' AND ( LOWER(first_name) LIKE '%sarah%' OR LOWER(last_name) LIKE '%johnson%' OR LOWER(nickname) LIKE '%sarah%' )ORDER BY last_name, first_name;
-- People added in the last 30 daysSELECT person_id, name, status, membership, created_at, CURRENT_DATE - created_at::date as days_since_addedFROM planning_center.people_peopleWHERE created_at >= CURRENT_DATE - INTERVAL '30 days' AND status = 'active'ORDER BY created_at DESC;
-- Calculate ages and group peopleSELECT person_id, name, birthdate, EXTRACT(YEAR FROM AGE(CURRENT_DATE, birthdate)) as age, CASE WHEN child = true THEN 'Child' WHEN EXTRACT(YEAR FROM AGE(CURRENT_DATE, birthdate)) < 18 THEN 'Youth' WHEN EXTRACT(YEAR FROM AGE(CURRENT_DATE, birthdate)) < 30 THEN 'Young Adult' WHEN EXTRACT(YEAR FROM AGE(CURRENT_DATE, birthdate)) < 50 THEN 'Adult' WHEN EXTRACT(YEAR FROM AGE(CURRENT_DATE, birthdate)) < 65 THEN 'Middle Age' ELSE 'Senior' END as age_groupFROM planning_center.people_peopleWHERE birthdate IS NOT NULL AND status = 'active'ORDER BY birthdate;
-- Find all children and studentsSELECT person_id, name, birthdate, grade, graduation_year, school_type, CASE WHEN grade IS NOT NULL THEN 'Grade ' || grade::text WHEN graduation_year IS NOT NULL THEN 'Grad Year ' || graduation_year::text WHEN child = true THEN 'Child' ELSE 'Unknown' END as education_statusFROM planning_center.people_peopleWHERE (child = true OR grade IS NOT NULL OR graduation_year IS NOT NULL) AND status = 'active'ORDER BY grade, graduation_year, name;
-- Analyze gender distributionSELECT gender, COUNT(*) as count, ROUND(COUNT(*) * 100.0 / SUM(COUNT(*)) OVER (), 1) as percentageFROM planning_center.people_peopleWHERE status = 'active'GROUP BY genderORDER BY count DESC;
-- Birthdays in the next 30 daysSELECT person_id, name, birthdate, TO_CHAR(birthdate, 'Month DD') as birthday, EXTRACT(YEAR FROM AGE( DATE(EXTRACT(YEAR FROM CURRENT_DATE) || '-' || EXTRACT(MONTH FROM birthdate) || '-' || EXTRACT(DAY FROM birthdate)), birthdate )) as turning_age, -- Calculate days until birthday CASE WHEN DATE_PART('doy', birthdate) >= DATE_PART('doy', CURRENT_DATE) THEN DATE_PART('doy', birthdate) - DATE_PART('doy', CURRENT_DATE) ELSE 365 - DATE_PART('doy', CURRENT_DATE) + DATE_PART('doy', birthdate) END as days_untilFROM planning_center.people_peopleWHERE birthdate IS NOT NULL AND status = 'active' AND ( -- Birthday in current year (EXTRACT(MONTH FROM birthdate) = EXTRACT(MONTH FROM CURRENT_DATE) AND EXTRACT(DAY FROM birthdate) >= EXTRACT(DAY FROM CURRENT_DATE)) OR (EXTRACT(MONTH FROM birthdate) = EXTRACT(MONTH FROM CURRENT_DATE + INTERVAL '1 month')) )ORDER BY EXTRACT(MONTH FROM birthdate), EXTRACT(DAY FROM birthdate);
-- View all households with member countsSELECT household_id, name, member_count, primary_contact_name, created_atFROM planning_center.people_householdsORDER BY nameLIMIT 100;
-- Get all members of householdsSELECT h.household_id, h.name as household_name, h.member_count, p.name as member_name, p.birthdate, CASE WHEN p.child = true THEN 'Child' ELSE 'Adult' END as member_type, hm.pendingFROM planning_center.people_households hJOIN planning_center.people_household_memberships hm ON h.household_id = hm.household_idJOIN planning_center.people_people p ON hm.person_id = p.person_idWHERE p.status = 'active' AND hm.pending = falseORDER BY h.name, p.birthdate;
-- Find households with 5 or more membersSELECT household_id, name, member_count, primary_contact_nameFROM planning_center.people_householdsWHERE member_count >= 5ORDER BY member_count DESC;
-- Identify people living aloneSELECT h.household_id, h.name as household_name, p.name as person_name, p.birthdate, EXTRACT(YEAR FROM AGE(CURRENT_DATE, p.birthdate)) as ageFROM planning_center.people_households hJOIN planning_center.people_household_memberships hm ON h.household_id = hm.household_idJOIN planning_center.people_people p ON hm.person_id = p.person_idWHERE h.member_count = 1 AND p.status = 'active'ORDER BY p.birthdate;
-- Find primary email addressesSELECT p.person_id, p.name, e.address as email, e.location as email_type, e.is_primary, e.blockedFROM planning_center.people_people pJOIN planning_center.people_email_relationships er ON p.person_id = er.person_idJOIN planning_center.people_emails e ON er.email_id = e.email_idWHERE p.status = 'active' AND e.is_primary = true AND e.blocked = falseORDER BY p.last_name, p.first_nameLIMIT 100;
-- Get all contact methods for active peopleSELECT p.person_id, p.name, e.address as email, pn.number as phone, a.street, a.city, a.state, a.zipFROM planning_center.people_people pLEFT JOIN planning_center.people_email_relationships er ON p.person_id = er.person_idLEFT JOIN planning_center.people_emails e ON er.email_id = e.email_id AND e.is_primary = trueLEFT JOIN planning_center.people_phone_number_relationships pnr ON p.person_id = pnr.person_idLEFT JOIN planning_center.people_phone_numbers pn ON pnr.phone_number_id = pn.phone_number_id AND pn.is_primary = trueLEFT JOIN planning_center.people_addresses a ON p.person_id = a.person_id AND a.is_primary = trueWHERE p.status = 'active'ORDER BY p.last_name, p.first_nameLIMIT 50;
-- Find people without email or phoneSELECT p.person_id, p.name, p.created_at, CASE WHEN e.email_id IS NULL AND pn.phone_number_id IS NULL THEN 'No Contact Info' WHEN e.email_id IS NULL THEN 'No Email' WHEN pn.phone_number_id IS NULL THEN 'No Phone' ELSE 'Has Contact Info' END as contact_statusFROM planning_center.people_people pLEFT JOIN planning_center.people_email_relationships er ON p.person_id = er.person_idLEFT JOIN planning_center.people_emails e ON er.email_id = e.email_idLEFT JOIN planning_center.people_phone_number_relationships pnr ON p.person_id = pnr.person_idLEFT JOIN planning_center.people_phone_numbers pn ON pnr.phone_number_id = pn.phone_number_idWHERE p.status = 'active' AND p.child = false -- Adults only AND (e.email_id IS NULL OR pn.phone_number_id IS NULL)ORDER BY p.created_at DESC;
-- Count people by membership levelSELECT membership, COUNT(*) as count, ROUND(COUNT(*) * 100.0 / SUM(COUNT(*)) OVER (), 1) as percentageFROM planning_center.people_peopleWHERE status = 'active'GROUP BY membershipORDER BY count DESC;
-- People who became members recentlySELECT person_id, name, membership, created_at, updated_atFROM planning_center.people_peopleWHERE status = 'active' AND membership = 'Member' AND updated_at >= CURRENT_DATE - INTERVAL '90 days'ORDER BY updated_at DESC;
-- Find people with administrative permissionsSELECT person_id, name, site_administrator, people_permissions, can_create_forms, can_email_listsFROM planning_center.people_peopleWHERE status = 'active' AND ( site_administrator = true OR people_permissions IS NOT NULL OR can_create_forms = true OR can_email_lists = true )ORDER BY name;
-- Wedding anniversaries this monthSELECT person_id, name, anniversary, TO_CHAR(anniversary, 'Month DD') as anniversary_date, EXTRACT(YEAR FROM AGE(CURRENT_DATE, anniversary)) as years_marriedFROM planning_center.people_peopleWHERE anniversary IS NOT NULL AND status = 'active' AND EXTRACT(MONTH FROM anniversary) = EXTRACT(MONTH FROM CURRENT_DATE)ORDER BY EXTRACT(DAY FROM anniversary);
-- Track growth over the last yearSELECT TO_CHAR(created_at, 'YYYY-MM') as month, COUNT(*) as people_added, COUNT(*) FILTER (WHERE child = true) as children_added, COUNT(*) FILTER (WHERE child = false OR child IS NULL) as adults_addedFROM planning_center.people_peopleWHERE created_at >= CURRENT_DATE - INTERVAL '12 months' AND status = 'active'GROUP BY TO_CHAR(created_at, 'YYYY-MM')ORDER BY month DESC;
-- People who have been in database 5+ yearsSELECT person_id, name, membership, created_at, EXTRACT(YEAR FROM AGE(CURRENT_DATE, created_at)) as years_in_databaseFROM planning_center.people_peopleWHERE status = 'active' AND created_at <= CURRENT_DATE - INTERVAL '5 years'ORDER BY created_atLIMIT 100;
-- High-level congregation overviewSELECT COUNT(*) as total_people, COUNT(*) FILTER (WHERE status = 'active') as active_people, COUNT(*) FILTER (WHERE membership = 'Member') as members, COUNT(*) FILTER (WHERE child = true) as children, COUNT(*) FILTER (WHERE graduation_year IS NOT NULL) as students, COUNT(*) FILTER (WHERE gender = 'Male') as males, COUNT(*) FILTER (WHERE gender = 'Female') as females, ROUND(AVG(EXTRACT(YEAR FROM AGE(CURRENT_DATE, birthdate))) FILTER (WHERE birthdate IS NOT NULL), 1) as avg_ageFROM planning_center.people_people;
-- Breakdown by age categoriesWITH age_groups AS ( SELECT CASE WHEN birthdate IS NULL THEN 'Unknown' WHEN EXTRACT(YEAR FROM AGE(CURRENT_DATE, birthdate)) < 1 THEN 'Infant' WHEN EXTRACT(YEAR FROM AGE(CURRENT_DATE, birthdate)) < 5 THEN 'Preschool' WHEN EXTRACT(YEAR FROM AGE(CURRENT_DATE, birthdate)) < 12 THEN 'Elementary' WHEN EXTRACT(YEAR FROM AGE(CURRENT_DATE, birthdate)) < 18 THEN 'Youth' WHEN EXTRACT(YEAR FROM AGE(CURRENT_DATE, birthdate)) < 30 THEN 'Young Adult' WHEN EXTRACT(YEAR FROM AGE(CURRENT_DATE, birthdate)) < 50 THEN 'Adult' WHEN EXTRACT(YEAR FROM AGE(CURRENT_DATE, birthdate)) < 65 THEN 'Middle Age' ELSE 'Senior' END as age_group FROM planning_center.people_people WHERE status = 'active')SELECT age_group, COUNT(*) as count, ROUND(COUNT(*) * 100.0 / SUM(COUNT(*)) OVER (), 1) as percentageFROM age_groupsGROUP BY age_groupORDER BY CASE age_group WHEN 'Infant' THEN 1 WHEN 'Preschool' THEN 2 WHEN 'Elementary' THEN 3 WHEN 'Youth' THEN 4 WHEN 'Young Adult' THEN 5 WHEN 'Adult' THEN 6 WHEN 'Middle Age' THEN 7 WHEN 'Senior' THEN 8 ELSE 9 END;
-- LEFT JOIN when data might not existLEFT JOIN planning_center.people_emails e ON ...-- INNER JOIN when data must existJOIN planning_center.people_household_memberships hm ON ...