-- ===================================================== -- Index Optimization -- -- Adds partial indexes for common query patterns, -- covers the advanced search filter combinations, -- and optimizes reporting queries. -- ===================================================== -- 1. Active members composite index (most common query pattern) -- Covers: listMembers, searchMembers, all reporting functions CREATE INDEX IF NOT EXISTS ix_members_active_account_status ON public.members(account_id, status, last_name, first_name) WHERE is_archived = false; -- 2. Entry date range queries (searchMembers with date filters) CREATE INDEX IF NOT EXISTS ix_members_entry_date ON public.members(account_id, entry_date) WHERE entry_date IS NOT NULL; -- 3. Dues category filter (searchMembers) CREATE INDEX IF NOT EXISTS ix_members_dues_category ON public.members(account_id, dues_category_id) WHERE dues_category_id IS NOT NULL; -- 4. Boolean flag filters (searchMembers flag queries) -- Partial indexes only store rows where the flag is true (very compact) CREATE INDEX IF NOT EXISTS ix_members_honorary ON public.members(account_id) WHERE is_honorary = true; CREATE INDEX IF NOT EXISTS ix_members_youth ON public.members(account_id) WHERE is_youth = true; CREATE INDEX IF NOT EXISTS ix_members_founding ON public.members(account_id) WHERE is_founding_member = true; CREATE INDEX IF NOT EXISTS ix_members_retiree ON public.members(account_id) WHERE is_retiree = true; -- 5. Active SEPA mandates lookup (finance integration) CREATE INDEX IF NOT EXISTS ix_sepa_mandates_active_lookup ON public.sepa_mandates(member_id, status) WHERE status = 'active' AND is_primary = true; -- 6. Communications per member (timeline queries) CREATE INDEX IF NOT EXISTS ix_member_comms_member_date ON public.member_communications(member_id, created_at DESC); -- 7. Audit log: action-type filtering (timeline with action filter) CREATE INDEX IF NOT EXISTS ix_member_audit_member_action ON public.member_audit_log(member_id, action, created_at DESC); -- 8. Tag assignments: member lookup (for search filter + detail view) CREATE INDEX IF NOT EXISTS ix_tag_assignments_member ON public.member_tag_assignments(member_id); -- 9. Reporting: active members for retention/duration CROSS JOIN -- Column order: account_id first (equality), then date columns (range scans) -- is_archived excluded from key since it's in WHERE clause CREATE INDEX IF NOT EXISTS ix_members_active_reporting ON public.members(account_id, entry_date, exit_date, status) WHERE is_archived = false; -- 10. Member merge log: primary member lookup CREATE INDEX IF NOT EXISTS ix_member_merges_primary ON public.member_merges(primary_member_id); -- 11. GDPR: candidates for anonymization (batch enforcement query) -- status excluded from key since enforcement query uses dynamic ANY(array) -- Covers: WHERE account_id = ? AND exit_date IS NOT NULL AND exit_date + interval <= current_date CREATE INDEX IF NOT EXISTS ix_members_gdpr_candidates ON public.members(account_id, exit_date) WHERE exit_date IS NOT NULL AND is_archived = false AND first_name != 'ANONYMISIERT'; -- 12. Portal invitations: account listing (listPortalInvitations query) CREATE INDEX IF NOT EXISTS ix_portal_invitations_account_date ON public.member_portal_invitations(account_id, created_at DESC); -- 13. Department assignments by department (searchMembers department filter subquery) CREATE INDEX IF NOT EXISTS ix_dept_assignments_department ON public.member_department_assignments(department_id, member_id);