Various query cleanups and optimizations based on chats with the team

This commit is contained in:
2026-02-04 22:24:53 -05:00
parent f9b57307de
commit 90c67a6797
7 changed files with 12892 additions and 17312 deletions

View File

@ -18,17 +18,28 @@ WITH config AS (
-- For features, we'll look at this as features used in the company's day-to-day, and not
-- evaluation attempts.
360 AS long_term,
-- We'll only look back a few years since we want to make fairish comparisons betwen users
-- We'll only look back a few years since we want to make fairish comparisons between users
-- exposed to "modern" ZenMaid tools.
CAST('2023-01-01' AS timestamp) AS date_cutoff
CAST('2023-01-01' AS timestamp) AS date_cutoff,
-- Not all users in our view will have had checklists available during the periods we're looking at,
-- so we want to flag each record as having them available or not.
CAST('2025-01-01' AS date) AS checklist_available_date
),
-- Then establish a standard core set of user data
users_with_churn_stats AS (
SELECT id, active, billing_state,
DATE_TRUNC('month', created_at) AS month_created,
created_at, free_trial_ends_at, updated_at,
updated_at::date - created_at::date AS lifespan
FROM users, config
churned_at, owner.max_owner_updated,
users.created_at::date + config.long_term AS content_cutoff,
GREATEST(owner.max_owner_updated, users.churned_at) AS last_update,
GREATEST(owner.max_owner_updated, users.churned_at)::date - created_at::date AS lifespan
FROM config, users
JOIN LATERAL (
SELECT user_id, max(updated_at) AS max_owner_updated
FROM employees
WHERE user_id = users.id
GROUP BY user_id
) owner ON users.id = owner.user_id
-- We'll only look at newish users.
WHERE created_at >= config.date_cutoff
),
@ -79,50 +90,10 @@ users_to_examine AS (
sms_counts AS (
-- Establish a summary of records with ages
WITH summary AS (
SELECT sent_texts.user_id, sent_texts.created_at::date - users.created_at::date AS created_age
SELECT sent_texts.user_id, sent_texts.created_at::date - users_to_examine.created_at::date AS created_age
FROM config, sent_texts
JOIN users ON sent_texts.user_id = users.id
WHERE sent_texts.created_at >= config.date_cutoff
AND users.created_at >= config.date_cutoff
AND sent_texts.created_at <= users.created_at::date + config.long_term
),
-- Group by week for line charts
weekly_counts AS (
SELECT user_id,
jsonb_object_agg(
'week_' || weeks_in,
weekly_count
) AS weekly_counts,
COUNT(*) AS total
FROM (SELECT user_id, FLOOR(created_age / 7) AS weeks_in, COUNT(*) as weekly_count
FROM summary
GROUP BY user_id, weeks_in) by_weeks
GROUP BY user_id
),
-- Group by era for bar charts
bucket_counts AS (
SELECT user_id,
SUM(CASE WHEN created_age < config.short_term THEN 1 ELSE 0 END) AS short_term,
SUM(CASE WHEN created_age >= config.short_term AND created_age < config.medium_term THEN 1 ELSE 0 END) AS medium_term,
SUM(CASE WHEN created_age >= config.medium_term AND created_age < config.long_term THEN 1 ELSE 0 END) AS long_term,
COUNT(*) AS total
FROM config, summary
GROUP BY user_id
)
-- Put it all together
SELECT bucket_counts.*, weekly_counts.weekly_counts
FROM bucket_counts JOIN weekly_counts ON weekly_counts.user_id = bucket_counts.user_id
),
-- Appointments by era
appointment_counts AS (
-- Establish a summary of records with ages
WITH summary AS (
SELECT appointments.user_id, appointments.created_at::date - users.created_at::date AS created_age
FROM config, appointments
JOIN users ON appointments.user_id = users.id
WHERE appointments.created_at >= config.date_cutoff
AND users.created_at >= config.date_cutoff
AND appointments.created_at <= users.created_at::date + config.long_term
JOIN users_to_examine ON sent_texts.user_id = users_to_examine.id
WHERE sent_texts.created_at <= users_to_examine.content_cutoff
),
-- Group by week for line charts
weekly_counts AS (
@ -158,20 +129,13 @@ SELECT users_to_examine.id,
users_to_examine.free_trial_ends_at,
users_to_examine.category,
users_to_examine.created_at,
users_to_examine.month_created,
users_to_examine.updated_at,
users_to_examine.lifespan,
COALESCE(sms_counts.short_term, 0) AS sms_short_term,
COALESCE(sms_counts.medium_term, 0) AS sms_medium_term,
COALESCE(sms_counts.long_term, 0) AS sms_long_term,
COALESCE(sms_counts.total, 0) AS sms,
COALESCE(sms_counts.weekly_counts, '{}'::jsonb) AS sms_weekly_counts,
COALESCE(appointment_counts.short_term, 0) AS appointments_short_term,
COALESCE(appointment_counts.medium_term, 0) AS appointments_medium_term,
COALESCE(appointment_counts.long_term, 0) AS appointments_long_term,
COALESCE(appointment_counts.total, 0) AS appointments,
COALESCE(appointment_counts.weekly_counts, '{}'::jsonb) AS appointments_weekly_counts
FROM users_to_examine
COALESCE(sms_counts.weekly_counts, '{}'::jsonb) AS sms_weekly_counts
FROM config, users_to_examine
LEFT JOIN sms_counts ON sms_counts.user_id = users_to_examine.id
LEFT JOIN appointment_counts ON appointment_counts.user_id = users_to_examine.id
;