Support all-time, year, year+month, and custom from/to query params with filtered metrics and time-series charts. Co-authored-by: Cursor <cursoragent@cursor.com>
358 lines
14 KiB
SQL
358 lines
14 KiB
SQL
-- =====================
|
|
-- Analytics (date-filtered)
|
|
-- =====================
|
|
-- Shared optional params (nullable = all-time):
|
|
-- range_start, range_end (exclusive upper bound)
|
|
-- Required chart params:
|
|
-- series_start, series_end (inclusive dates)
|
|
-- Relative window anchor:
|
|
-- ref_date (inclusive date used for new_today/week/month)
|
|
|
|
-- =====================
|
|
-- User Analytics
|
|
-- =====================
|
|
|
|
-- name: AnalyticsUsersSummary :one
|
|
SELECT
|
|
COUNT(*)::bigint AS total,
|
|
COUNT(*) FILTER (WHERE u.created_at::date = sqlc.arg('ref_date')::date)::bigint AS new_today,
|
|
COUNT(*) FILTER (
|
|
WHERE u.created_at::date >= sqlc.arg('ref_date')::date - INTERVAL '6 days'
|
|
AND u.created_at::date <= sqlc.arg('ref_date')::date
|
|
)::bigint AS new_this_week,
|
|
COUNT(*) FILTER (
|
|
WHERE u.created_at::date >= sqlc.arg('ref_date')::date - INTERVAL '29 days'
|
|
AND u.created_at::date <= sqlc.arg('ref_date')::date
|
|
)::bigint AS new_this_month
|
|
FROM users u
|
|
WHERE (sqlc.narg('range_start')::timestamptz IS NULL OR u.created_at >= sqlc.narg('range_start')::timestamptz)
|
|
AND (sqlc.narg('range_end')::timestamptz IS NULL OR u.created_at < sqlc.narg('range_end')::timestamptz);
|
|
|
|
-- name: AnalyticsUsersByRole :many
|
|
SELECT
|
|
COALESCE(u.role, 'unknown') AS role,
|
|
COUNT(*)::bigint AS count
|
|
FROM users u
|
|
WHERE (sqlc.narg('range_start')::timestamptz IS NULL OR u.created_at >= sqlc.narg('range_start')::timestamptz)
|
|
AND (sqlc.narg('range_end')::timestamptz IS NULL OR u.created_at < sqlc.narg('range_end')::timestamptz)
|
|
GROUP BY u.role
|
|
ORDER BY count DESC;
|
|
|
|
-- name: AnalyticsUsersByStatus :many
|
|
SELECT
|
|
COALESCE(u.status, 'unknown') AS status,
|
|
COUNT(*)::bigint AS count
|
|
FROM users u
|
|
WHERE (sqlc.narg('range_start')::timestamptz IS NULL OR u.created_at >= sqlc.narg('range_start')::timestamptz)
|
|
AND (sqlc.narg('range_end')::timestamptz IS NULL OR u.created_at < sqlc.narg('range_end')::timestamptz)
|
|
GROUP BY u.status
|
|
ORDER BY count DESC;
|
|
|
|
-- name: AnalyticsUsersByAgeGroup :many
|
|
SELECT
|
|
COALESCE(u.age_group, 'unknown') AS age_group,
|
|
COUNT(*)::bigint AS count
|
|
FROM users u
|
|
WHERE (sqlc.narg('range_start')::timestamptz IS NULL OR u.created_at >= sqlc.narg('range_start')::timestamptz)
|
|
AND (sqlc.narg('range_end')::timestamptz IS NULL OR u.created_at < sqlc.narg('range_end')::timestamptz)
|
|
GROUP BY u.age_group
|
|
ORDER BY count DESC;
|
|
|
|
-- name: AnalyticsUsersByKnowledgeLevel :many
|
|
SELECT
|
|
COALESCE(u.knowledge_level, 'unknown') AS knowledge_level,
|
|
COUNT(*)::bigint AS count
|
|
FROM users u
|
|
WHERE (sqlc.narg('range_start')::timestamptz IS NULL OR u.created_at >= sqlc.narg('range_start')::timestamptz)
|
|
AND (sqlc.narg('range_end')::timestamptz IS NULL OR u.created_at < sqlc.narg('range_end')::timestamptz)
|
|
GROUP BY u.knowledge_level
|
|
ORDER BY count DESC;
|
|
|
|
-- name: AnalyticsUsersByRegion :many
|
|
SELECT
|
|
COALESCE(u.region, 'unknown') AS region,
|
|
COUNT(*)::bigint AS count
|
|
FROM users u
|
|
WHERE (sqlc.narg('range_start')::timestamptz IS NULL OR u.created_at >= sqlc.narg('range_start')::timestamptz)
|
|
AND (sqlc.narg('range_end')::timestamptz IS NULL OR u.created_at < sqlc.narg('range_end')::timestamptz)
|
|
GROUP BY u.region
|
|
ORDER BY count DESC;
|
|
|
|
-- name: AnalyticsUserRegistrationsLast30Days :many
|
|
SELECT
|
|
d.date,
|
|
COUNT(u.id)::bigint AS count
|
|
FROM generate_series(
|
|
sqlc.arg('series_start')::date,
|
|
sqlc.arg('series_end')::date,
|
|
INTERVAL '1 day'
|
|
) AS d(date)
|
|
LEFT JOIN users u ON u.created_at::date = d.date
|
|
AND (sqlc.narg('range_start')::timestamptz IS NULL OR u.created_at >= sqlc.narg('range_start')::timestamptz)
|
|
AND (sqlc.narg('range_end')::timestamptz IS NULL OR u.created_at < sqlc.narg('range_end')::timestamptz)
|
|
GROUP BY d.date
|
|
ORDER BY d.date;
|
|
|
|
-- =====================
|
|
-- Subscription Analytics
|
|
-- =====================
|
|
|
|
-- name: AnalyticsSubscriptionsSummary :one
|
|
SELECT
|
|
COUNT(*)::bigint AS total,
|
|
COUNT(*) FILTER (WHERE us.status = 'ACTIVE')::bigint AS active,
|
|
COUNT(*) FILTER (WHERE us.created_at::date = sqlc.arg('ref_date')::date)::bigint AS new_today,
|
|
COUNT(*) FILTER (
|
|
WHERE us.created_at::date >= sqlc.arg('ref_date')::date - INTERVAL '6 days'
|
|
AND us.created_at::date <= sqlc.arg('ref_date')::date
|
|
)::bigint AS new_this_week,
|
|
COUNT(*) FILTER (
|
|
WHERE us.created_at::date >= sqlc.arg('ref_date')::date - INTERVAL '29 days'
|
|
AND us.created_at::date <= sqlc.arg('ref_date')::date
|
|
)::bigint AS new_this_month
|
|
FROM user_subscriptions us
|
|
WHERE (sqlc.narg('range_start')::timestamptz IS NULL OR us.created_at >= sqlc.narg('range_start')::timestamptz)
|
|
AND (sqlc.narg('range_end')::timestamptz IS NULL OR us.created_at < sqlc.narg('range_end')::timestamptz);
|
|
|
|
-- name: AnalyticsSubscriptionsByStatus :many
|
|
SELECT
|
|
COALESCE(us.status, 'unknown') AS status,
|
|
COUNT(*)::bigint AS count
|
|
FROM user_subscriptions us
|
|
WHERE (sqlc.narg('range_start')::timestamptz IS NULL OR us.created_at >= sqlc.narg('range_start')::timestamptz)
|
|
AND (sqlc.narg('range_end')::timestamptz IS NULL OR us.created_at < sqlc.narg('range_end')::timestamptz)
|
|
GROUP BY us.status
|
|
ORDER BY count DESC;
|
|
|
|
-- name: AnalyticsRevenueByPlan :many
|
|
SELECT
|
|
sp.name AS plan_name,
|
|
sp.currency,
|
|
COUNT(p.id)::bigint AS total_payments,
|
|
COALESCE(SUM(p.amount), 0)::float8 AS total_revenue
|
|
FROM payments p
|
|
JOIN subscription_plans sp ON sp.id = p.plan_id
|
|
WHERE p.status = 'SUCCESS'
|
|
AND (sqlc.narg('range_start')::timestamptz IS NULL OR COALESCE(p.paid_at, p.created_at) >= sqlc.narg('range_start')::timestamptz)
|
|
AND (sqlc.narg('range_end')::timestamptz IS NULL OR COALESCE(p.paid_at, p.created_at) < sqlc.narg('range_end')::timestamptz)
|
|
GROUP BY sp.name, sp.currency
|
|
ORDER BY total_revenue DESC;
|
|
|
|
-- name: AnalyticsNewSubscriptionsLast30Days :many
|
|
SELECT
|
|
d.date,
|
|
COUNT(us.id)::bigint AS count
|
|
FROM generate_series(
|
|
sqlc.arg('series_start')::date,
|
|
sqlc.arg('series_end')::date,
|
|
INTERVAL '1 day'
|
|
) AS d(date)
|
|
LEFT JOIN user_subscriptions us ON us.created_at::date = d.date
|
|
AND (sqlc.narg('range_start')::timestamptz IS NULL OR us.created_at >= sqlc.narg('range_start')::timestamptz)
|
|
AND (sqlc.narg('range_end')::timestamptz IS NULL OR us.created_at < sqlc.narg('range_end')::timestamptz)
|
|
GROUP BY d.date
|
|
ORDER BY d.date;
|
|
|
|
-- =====================
|
|
-- Payment Analytics
|
|
-- =====================
|
|
|
|
-- name: AnalyticsPaymentsSummary :one
|
|
SELECT
|
|
COALESCE(SUM(p.amount) FILTER (WHERE p.status = 'SUCCESS'), 0)::float8 AS total_revenue,
|
|
COALESCE(AVG(p.amount) FILTER (WHERE p.status = 'SUCCESS'), 0)::float8 AS avg_value,
|
|
COUNT(*)::bigint AS total_payments,
|
|
COUNT(*) FILTER (WHERE p.status = 'SUCCESS')::bigint AS successful_payments
|
|
FROM payments p
|
|
WHERE (sqlc.narg('range_start')::timestamptz IS NULL OR COALESCE(p.paid_at, p.created_at) >= sqlc.narg('range_start')::timestamptz)
|
|
AND (sqlc.narg('range_end')::timestamptz IS NULL OR COALESCE(p.paid_at, p.created_at) < sqlc.narg('range_end')::timestamptz);
|
|
|
|
-- name: AnalyticsPaymentsByStatus :many
|
|
SELECT
|
|
COALESCE(p.status, 'unknown') AS status,
|
|
COUNT(*)::bigint AS count,
|
|
COALESCE(SUM(p.amount), 0)::float8 AS total_amount
|
|
FROM payments p
|
|
WHERE (sqlc.narg('range_start')::timestamptz IS NULL OR COALESCE(p.paid_at, p.created_at) >= sqlc.narg('range_start')::timestamptz)
|
|
AND (sqlc.narg('range_end')::timestamptz IS NULL OR COALESCE(p.paid_at, p.created_at) < sqlc.narg('range_end')::timestamptz)
|
|
GROUP BY p.status
|
|
ORDER BY count DESC;
|
|
|
|
-- name: AnalyticsPaymentsByMethod :many
|
|
SELECT
|
|
COALESCE(p.payment_method, 'unknown') AS payment_method,
|
|
COUNT(*)::bigint AS count,
|
|
COALESCE(SUM(p.amount), 0)::float8 AS total_amount
|
|
FROM payments p
|
|
WHERE p.status = 'SUCCESS'
|
|
AND (sqlc.narg('range_start')::timestamptz IS NULL OR COALESCE(p.paid_at, p.created_at) >= sqlc.narg('range_start')::timestamptz)
|
|
AND (sqlc.narg('range_end')::timestamptz IS NULL OR COALESCE(p.paid_at, p.created_at) < sqlc.narg('range_end')::timestamptz)
|
|
GROUP BY p.payment_method
|
|
ORDER BY count DESC;
|
|
|
|
-- name: AnalyticsRevenueLast30Days :many
|
|
SELECT
|
|
d.date,
|
|
COALESCE(SUM(p.amount), 0)::float8 AS total_revenue
|
|
FROM generate_series(
|
|
sqlc.arg('series_start')::date,
|
|
sqlc.arg('series_end')::date,
|
|
INTERVAL '1 day'
|
|
) AS d(date)
|
|
LEFT JOIN payments p ON COALESCE(p.paid_at, p.created_at)::date = d.date
|
|
AND p.status = 'SUCCESS'
|
|
AND (sqlc.narg('range_start')::timestamptz IS NULL OR COALESCE(p.paid_at, p.created_at) >= sqlc.narg('range_start')::timestamptz)
|
|
AND (sqlc.narg('range_end')::timestamptz IS NULL OR COALESCE(p.paid_at, p.created_at) < sqlc.narg('range_end')::timestamptz)
|
|
GROUP BY d.date
|
|
ORDER BY d.date;
|
|
|
|
-- =====================
|
|
-- Course Analytics
|
|
-- =====================
|
|
|
|
-- name: AnalyticsCourseCounts :one
|
|
SELECT
|
|
0::bigint AS total_categories,
|
|
0::bigint AS total_courses,
|
|
0::bigint AS total_sub_courses,
|
|
0::bigint AS total_videos;
|
|
|
|
-- =====================
|
|
-- Content Analytics
|
|
-- =====================
|
|
|
|
-- name: AnalyticsQuestionsCounts :one
|
|
SELECT
|
|
(
|
|
SELECT COUNT(*)::bigint
|
|
FROM questions q
|
|
WHERE (sqlc.narg('range_start')::timestamptz IS NULL OR q.created_at >= sqlc.narg('range_start')::timestamptz)
|
|
AND (sqlc.narg('range_end')::timestamptz IS NULL OR q.created_at < sqlc.narg('range_end')::timestamptz)
|
|
) AS total_questions,
|
|
(
|
|
SELECT COUNT(*)::bigint
|
|
FROM question_sets qs
|
|
WHERE (sqlc.narg('range_start')::timestamptz IS NULL OR qs.created_at >= sqlc.narg('range_start')::timestamptz)
|
|
AND (sqlc.narg('range_end')::timestamptz IS NULL OR qs.created_at < sqlc.narg('range_end')::timestamptz)
|
|
) AS total_question_sets;
|
|
|
|
-- name: AnalyticsQuestionsByType :many
|
|
SELECT
|
|
COALESCE(q.question_type, 'unknown') AS question_type,
|
|
COUNT(*)::bigint AS count
|
|
FROM questions q
|
|
WHERE (sqlc.narg('range_start')::timestamptz IS NULL OR q.created_at >= sqlc.narg('range_start')::timestamptz)
|
|
AND (sqlc.narg('range_end')::timestamptz IS NULL OR q.created_at < sqlc.narg('range_end')::timestamptz)
|
|
GROUP BY q.question_type
|
|
ORDER BY count DESC;
|
|
|
|
-- name: AnalyticsQuestionSetsByType :many
|
|
SELECT
|
|
COALESCE(qs.set_type, 'unknown') AS set_type,
|
|
COUNT(*)::bigint AS count
|
|
FROM question_sets qs
|
|
WHERE (sqlc.narg('range_start')::timestamptz IS NULL OR qs.created_at >= sqlc.narg('range_start')::timestamptz)
|
|
AND (sqlc.narg('range_end')::timestamptz IS NULL OR qs.created_at < sqlc.narg('range_end')::timestamptz)
|
|
GROUP BY qs.set_type
|
|
ORDER BY count DESC;
|
|
|
|
-- =====================
|
|
-- Notification Analytics
|
|
-- =====================
|
|
|
|
-- name: AnalyticsNotificationsSummary :one
|
|
SELECT
|
|
COUNT(*)::bigint AS total,
|
|
COUNT(*) FILTER (WHERE n.is_read = TRUE)::bigint AS read,
|
|
COUNT(*) FILTER (WHERE n.is_read = FALSE)::bigint AS unread
|
|
FROM notifications n
|
|
WHERE (sqlc.narg('range_start')::timestamptz IS NULL OR n.created_at >= sqlc.narg('range_start')::timestamptz)
|
|
AND (sqlc.narg('range_end')::timestamptz IS NULL OR n.created_at < sqlc.narg('range_end')::timestamptz);
|
|
|
|
-- name: AnalyticsNotificationsByChannel :many
|
|
SELECT
|
|
COALESCE(n.channel, 'unknown') AS channel,
|
|
COUNT(*)::bigint AS count
|
|
FROM notifications n
|
|
WHERE (sqlc.narg('range_start')::timestamptz IS NULL OR n.created_at >= sqlc.narg('range_start')::timestamptz)
|
|
AND (sqlc.narg('range_end')::timestamptz IS NULL OR n.created_at < sqlc.narg('range_end')::timestamptz)
|
|
GROUP BY n.channel
|
|
ORDER BY count DESC;
|
|
|
|
-- name: AnalyticsNotificationsByType :many
|
|
SELECT
|
|
COALESCE(n.type, 'unknown') AS type,
|
|
COUNT(*)::bigint AS count
|
|
FROM notifications n
|
|
WHERE (sqlc.narg('range_start')::timestamptz IS NULL OR n.created_at >= sqlc.narg('range_start')::timestamptz)
|
|
AND (sqlc.narg('range_end')::timestamptz IS NULL OR n.created_at < sqlc.narg('range_end')::timestamptz)
|
|
GROUP BY n.type
|
|
ORDER BY count DESC;
|
|
|
|
-- =====================
|
|
-- Issue Analytics
|
|
-- =====================
|
|
|
|
-- name: AnalyticsIssuesSummary :one
|
|
SELECT
|
|
COUNT(*)::bigint AS total,
|
|
COUNT(*) FILTER (WHERE ri.status = 'resolved')::bigint AS resolved,
|
|
CASE
|
|
WHEN COUNT(*) > 0 THEN (COUNT(*) FILTER (WHERE ri.status = 'resolved')::float8 / COUNT(*)::float8)
|
|
ELSE 0::float8
|
|
END AS resolution_rate
|
|
FROM reported_issues ri
|
|
WHERE (sqlc.narg('range_start')::timestamptz IS NULL OR ri.created_at >= sqlc.narg('range_start')::timestamptz)
|
|
AND (sqlc.narg('range_end')::timestamptz IS NULL OR ri.created_at < sqlc.narg('range_end')::timestamptz);
|
|
|
|
-- name: AnalyticsIssuesByStatus :many
|
|
SELECT
|
|
COALESCE(ri.status, 'unknown') AS status,
|
|
COUNT(*)::bigint AS count
|
|
FROM reported_issues ri
|
|
WHERE (sqlc.narg('range_start')::timestamptz IS NULL OR ri.created_at >= sqlc.narg('range_start')::timestamptz)
|
|
AND (sqlc.narg('range_end')::timestamptz IS NULL OR ri.created_at < sqlc.narg('range_end')::timestamptz)
|
|
GROUP BY ri.status
|
|
ORDER BY count DESC;
|
|
|
|
-- name: AnalyticsIssuesByType :many
|
|
SELECT
|
|
COALESCE(ri.issue_type, 'unknown') AS issue_type,
|
|
COUNT(*)::bigint AS count
|
|
FROM reported_issues ri
|
|
WHERE (sqlc.narg('range_start')::timestamptz IS NULL OR ri.created_at >= sqlc.narg('range_start')::timestamptz)
|
|
AND (sqlc.narg('range_end')::timestamptz IS NULL OR ri.created_at < sqlc.narg('range_end')::timestamptz)
|
|
GROUP BY ri.issue_type
|
|
ORDER BY count DESC;
|
|
|
|
-- =====================
|
|
-- Team Analytics
|
|
-- =====================
|
|
|
|
-- name: AnalyticsTeamSummary :one
|
|
SELECT
|
|
COUNT(*)::bigint AS total_members
|
|
FROM team_members tm
|
|
WHERE (sqlc.narg('range_start')::timestamptz IS NULL OR tm.created_at >= sqlc.narg('range_start')::timestamptz)
|
|
AND (sqlc.narg('range_end')::timestamptz IS NULL OR tm.created_at < sqlc.narg('range_end')::timestamptz);
|
|
|
|
-- name: AnalyticsTeamByRole :many
|
|
SELECT
|
|
COALESCE(tm.team_role, 'unknown') AS team_role,
|
|
COUNT(*)::bigint AS count
|
|
FROM team_members tm
|
|
WHERE (sqlc.narg('range_start')::timestamptz IS NULL OR tm.created_at >= sqlc.narg('range_start')::timestamptz)
|
|
AND (sqlc.narg('range_end')::timestamptz IS NULL OR tm.created_at < sqlc.narg('range_end')::timestamptz)
|
|
GROUP BY tm.team_role
|
|
ORDER BY count DESC;
|
|
|
|
-- name: AnalyticsTeamByStatus :many
|
|
SELECT
|
|
COALESCE(tm.status, 'unknown') AS status,
|
|
COUNT(*)::bigint AS count
|
|
FROM team_members tm
|
|
WHERE (sqlc.narg('range_start')::timestamptz IS NULL OR tm.created_at >= sqlc.narg('range_start')::timestamptz)
|
|
AND (sqlc.narg('range_end')::timestamptz IS NULL OR tm.created_at < sqlc.narg('range_end')::timestamptz)
|
|
GROUP BY tm.status
|
|
ORDER BY count DESC;
|