300 Data Challenges Thực Chiến

Data Challenge 231125: Lagging & Leading Metrics & Coding Challenges

Với mô hình Challenge-based Learning, bạn không chỉ được học Business Domain, mà còn được giải các Challenges Thực Tế, rèn Tư Duy Phân Tích và tạo Portfolio ngay trong các Khóa Học Phân Tích Dữ Liệu Thực Chiến. Inbox mình Tại Đây nếu bạn muốn đăng ký nhé.

Bối Cảnh

Báo cáo Quý 3 vừa gửi tới Ban Giám Đốc sáng nay cho thấy những con số đáng báo động:

  • Doanh thu (Revenue): $500k (Không tăng trưởng so với Quý 2).
  • Tỷ lệ rời bỏ (Churn Rate): Tăng vọt từ 3% lên 5%.
  • Khách hàng lớn (Enterprise): 2 khách hàng lớn nhất vừa hủy hợp đồng tuần trước.

Vấn đề: Khi khách hàng hủy hợp đồng thì mọi chuyện đã rồi. Chúng ta cần phải biết họ có dấu hiệu sắp rời bỏ từ 1 tháng trước để giữ họ lại.

Hiện tại, bạn đang quản lý sản phẩm dựa trên những gì đã xảy ra. Bạn thiếu hoàn toàn khả năng dự báo.

Nhiệm vụ: Xây dựng ngay hệ thống chỉ số cảnh báo sớm để đội ngũ Customer Success có thể can thiệp giữ chân khách hàng trước khi họ quyết định rời đi. Mục tiêu: Giảm Churn Rate xuống dưới 3% trong Quý 4.

Kiến Thức Cốt Lõi

Để giải quyết bài toán, bạn cần phân biệt rõ hai nhóm chỉ số dựa trên tính thời điểm và khả năng tác động:

Lagging Metrics: Chỉ Số Kết Quả

Là những con số báo cáo kết quả đã xảy ra.

  • Đặc điểm: Chính xác tuyệt đối nhưng luôn trễ. Khi bạn nhìn thấy số liệu, sự việc đã kết thúc và không thể thay đổi được nữa.
  • Mục đích: Dùng để đánh giá hiệu quả cuối cùng (thành công hay thất bại).
  • Ví dụ: Doanh thu quý trước, Tổng số khách hàng đã rời bỏ (Churned Users).
Leading Metrics: Chỉ Số Dẫn Dắt

Là những chỉ số đo lường hành vi hiện tại có khả năng dự báo kết quả tương lai.

  • Đặc điểm: Mang tính dự báo và có thể can thiệp ngay lập tức. Nếu bạn tác động làm tăng chỉ số này hôm nay, kết quả tương lai (Lagging) sẽ thay đổi theo.
  • Mục đích: Dùng để điều hướng hành động hàng ngày của team.
  • Ví dụ: Số lượng người dùng đang hoạt động (Active Users), Tỷ lệ hoàn thành các bước cài đặt (Onboarding Completion).
Mối Quan Hệ
  • Bạn không thể trực tiếp thay đổi Lagging Metrics. Bạn chỉ có thể tác động vào Leading Metrics để gián tiếp thay đổi kết quả cuối cùng.
  • Ví dụ: Muốn giảm cân (Lagging), bạn phải kiểm soát lượng Calo nạp vào hàng ngày (Leading).

Case Study TungSync

Phân Tích Dữ Liệu Người Dùng

Bạn trích xuất dữ liệu của khách hàng dùng thử (Trial Users) trong 3 tháng qua và chia làm 2 nhóm:

  • Nhóm Thành công (Converted): Đã mua gói trả phí sau 14 ngày.
  • Nhóm Thất bại (Churned): Không mua gói và rời bỏ.

Insight: Khi so sánh hành vi của 2 nhóm trong 3 ngày đầu tiên, sự khác biệt nằm ở tính năng “Mời thành viên” (Invite Members) và “Tạo dự án” (Create Project).

  • Nhóm Thành công: 90% đã tạo ít nhất 3 dự án và mời đủ 5 thành viên.
  • Nhóm Thất bại: 80% chỉ tạo 1 dự án và hoạt động một mình.

Kết luận: Hành vi “3 Dự án + 5 Thành viên” chính là tín hiệu báo trước sự thành công.

Xác Định Chỉ Số
Loại MetricTên Chỉ SốÝ NghĩaKPI Mục Tiêu
Lagging (Mục tiêu cuối)Monthly Recurring Revenue (MRR)Tiền về tài khoản vào cuối tháng.Tăng trưởng 10% MoM
LaggingConversion RateTỷ lệ user chuyển từ Trial sang Paid sau 14 ngày.15%
Leading (Dự báo)Activation RateTỷ lệ user hoàn thành “3 Dự án + 5 Invite” trong 3 ngày đầu.30% (Đây là con số cần tập trung tối ưu)
Leading (Dự báo)Daily Active Users (DAU)Số lượng thành viên online mỗi ngày. Nếu giảm -> Cảnh báo rủi ro Churn.Ổn định hoặc tăng

Kế Hoạch Thực Thi

Bạn không thể ngồi chờ kết quả xảy ra. Hãy hành động dựa trên Leading Metrics theo quy trình 3 bước:

Bước 1: Xây dựng Dashboard theo dõi hàng ngày

  • Thiết lập biểu đồ theo dõi Activation Rate (Tỷ lệ hoàn thành 3 Dự án + 5 Invite) theo thời gian thực.
  • Mục tiêu: Phát hiện xu hướng giảm ngay khi nó vừa bắt đầu.

Bước 2: Thiết lập cảnh báo tự động

  • Thay vì kiểm tra thủ công, hãy cài đặt hệ thống tự động phát hiện user có nguy cơ rủi ro:
    • Trigger 1: User đăng ký được 24h nhưng chưa tạo Dự án nào.
    • Trigger 2: User đã tạo dự án nhưng chưa mời thành viên sau 48h.

Bước 3: Can thiệp ngay lập tức

  • Tác động trực tiếp vào hành vi người dùng để thay đổi kết quả.
    • Với Trigger 1: Gửi email hướng dẫn “Cách tạo dự án đầu tiên trong 30 giây”.
    • Với Trigger 2: Hiển thị thông báo trong ứng dụng (In-app popup): “Mời đồng nghiệp để cùng làm việc hiệu quả hơn”.

Kết quả kỳ vọng: Nếu bạn đẩy Activation Rate tăng từ 20% lên 30%, doanh thu cuối tháng (Lagging Metric) sẽ tăng trưởng tương ứng.

Bài Học

  • Doanh thu và Churn Rate là kết quả quá khứ. Bạn không thể sửa đổi quá khứ.
  • Tìm ra hành động cụ thể của người dùng (như “Tạo 3 dự án”) có mối liên hệ chặt chẽ với việc họ sẽ trả tiền. Đó là Leading Metric.
  • Mọi nỗ lực phát triển sản phẩm phải nhằm tăng chỉ số Leading. Khi Leading tăng, Doanh thu (Lagging) sẽ tự động tăng theo.

Tư Duy Code

Nguyên Tắc #1: Aggregate Trước Khi JOIN

Aggregate dữ liệu ở mỗi bảng TRƯỚC khi JOIN với bảng khác để giảm số lượng rows cần xử lý.

Cách KHÔNG TỐT

-- Đếm số projects sau khi JOIN với tất cả users
SELECT 
    u.user_id,
    COUNT(DISTINCT p.project_id) AS project_count
FROM users u
LEFT JOIN projects p ON u.user_id = p.user_id
GROUP BY u.user_id

-- 1 user × 100 projects = 100 rows
-- 10,000 users × 100 projects = 1,000,000 rows cần GROUP BY
-- Tốn memory để aggregate 1M rows về 10K users

Cách TỐT HƠN

-- Aggregate projects TRƯỚC, rồi mới JOIN
WITH Project_Counts AS (
    SELECT 
        user_id,
        COUNT(DISTINCT project_id) AS project_count
    FROM projects
    GROUP BY user_id
    -- Kết quả: 10,000 rows (mỗi user 1 dòng)
)
SELECT 
    u.user_id,
    COALESCE(pc.project_count, 0) AS project_count
FROM users u
LEFT JOIN Project_Counts pc ON u.user_id = pc.user_id
-- Chỉ JOIN 10K users × 1 row = 10K rows
-- Rows cần xử lý: 1,000,000 → 10,000 = 100x reduction
-- Query time: ~10x faster
Nguyên Tắc #2: Tránh Cartesian Explosion

Tránh JOIN nhiều bảng one-to-many liên tiếp vì sẽ tạo ra cartesian product.

Phân tích chi tiết cách KHÔNG TỐTcách TỐT HƠN: Nội dung Premium thuộc khóa học SQL Data Analyst & Data Analyst BootCamp.

Nguyên Tắc #3: Pre-Calculate Metrics

Tính tất cả metrics trong 1 CTE thay vì scan lại nhiều lần trong UNION ALL hoặc subqueries.

Phân tích chi tiết cách KHÔNG TỐTcách TỐT HƠN: Nội dung Premium thuộc khóa học SQL Data Analyst & Data Analyst BootCamp.

Nguyên Tắc #4: Tránh Repeated Scans

Materialize CTE hoặc dùng temp table nếu CTE được reference nhiều lần.

Phân tích chi tiết cách KHÔNG TỐTcách TỐT HƠN: Nội dung Premium thuộc khóa học SQL Data Analyst & Data Analyst BootCamp.

Nguyên Tắc #5: Giảm Function Calls

Pre-calculate function results thay vì gọi function nhiều lần cho cùng 1 row.

Phân tích chi tiết cách KHÔNG TỐTcách TỐT HƠN: Nội dung Premium thuộc khóa học SQL Data Analyst & Data Analyst BootCamp.

Nguyên Tắc #6: UNION ALL vs Separate Aggregations

Cân nhắc giữa UNION ALL và separate aggregations dựa trên kích thước dữ liệu.

Phân tích chi tiết cách KHÔNG TỐTcách TỐT HƠN: Nội dung Premium thuộc khóa học SQL Data Analyst & Data Analyst BootCamp.

Nguyên Tắc #7: Index Strategy

Index phải match với query pattern: JOIN columns, WHERE conditions, và ORDER BY.

Phân tích chi tiết cách KHÔNG TỐTcách TỐT HƠN: Nội dung Premium thuộc khóa học SQL Data Analyst & Data Analyst BootCamp.

SQL Coding Challenge 01

TungSync đang đối mặt với bài toán Churn Rate tăng cao. Team Product đưa ra giả thuyết về Leading Metric (Activation):

  • Người dùng tạo ít nhất 3 Dự án và mời đủ 5 Thành viên trong vòng 72 giờ đầu tiên sẽ có tỷ lệ mua hàng (Conversion) cao vượt trội.

Bạn cần chứng minh giả thuyết này. Viết câu truy vấn SQL Server để thực hiện các yêu cầu sau:

  • Xác định từng user có đạt trạng thái “Activated” hay không dựa trên hành vi trong 72h đầu sau khi đăng ký.
  • Tính toán Cohort: Gom nhóm user theo Tháng Đăng Ký (Signup Month).
  • Tính toán các chỉ số sau cho từng tháng:
    • Total Signups: Tổng số lượng đăng ký.
    • Activation Rate: Tỷ lệ % user đạt trạng thái Activated.
    • Activated Conversion Rate: Tỷ lệ chuyển đổi sang trả phí của nhóm Activated.
    • Non-Activated Conversion Rate: Tỷ lệ chuyển đổi sang trả phí của nhóm KHÔNG Activated.
    • Impact Score: (Activated Conversion Rate / Non-Activated Conversion Rate). Chỉ số này cho biết nhóm Activated chuyển đổi tốt gấp bao nhiêu lần nhóm thường.

Output mong muốn

Signup_MonthTotal_UsersActivated_UsersActivation_RateConv_Rate_ActivatedConv_Rate_NonActivatedImpact_Score_X
2024-01467347.3%29.4%12.7%2.3
  • Activation Rate = 7.3%
    • Chỉ có 7.3% user đạt mốc “3 Dự án + 5 Invite” trong 72h đầu.
  • Conv_Rate_Activated = 29.4%
    • Trong 34 user đạt Activated, có 29.4% (10 người) mua gói Pro.
    • Gần 1/3 user Activated chuyển đổi thành công.
  • Conv_Rate_NonActivated = 12.7%
    • Trong 433 user KHÔNG đạt Activated, chỉ có 12.7% (55 người) mua gói.
    • Thấp hơn đáng kể so với nhóm Activated.
  • Impact Score = 2.3x
    • User Activated có khả năng chuyển đổi cao gấp 2.3 lần user thường.
    • Kết luận: Giả thuyết ĐÚNG. “3 Dự án + 5 Invite” thực sự là Leading Metric dự báo Conversion.
-- BƯỚC 1: Tính số Projects trong 72h cho MỖI USER
WITH Project_Metrics AS (
    SELECT 
        p.user_id,
        -- Đếm số project được tạo trong 72h đầu
        COUNT(DISTINCT CASE 
            -- So sánh thời gian tạo project với signup_date + 72h
            WHEN p.created_at <= DATEADD(HOUR, 72, u.signup_date) 
            THEN p.project_id   -- Nếu trong 72h → đếm
        END) AS projects_72h    -- NULL sẽ bị bỏ qua bởi COUNT
        
    FROM projects p
    -- INNER JOIN vì chỉ quan tâm users có tạo project
    INNER JOIN users u ON p.user_id = u.user_id
    GROUP BY p.user_id
    -- Kết quả: Mỗi user_id xuất hiện 1 lần với số projects_72h
),

-- BƯỚC 2: Tính số Invites trong 72h cho MỖI USER
Invite_Metrics AS (
    SELECT 
        p.user_id,  -- Lấy user_id từ projects (vì members không có trực tiếp)
        -- Đếm số invite gửi trong 72h đầu
        COUNT(DISTINCT CASE 
            WHEN m.invited_at <= DATEADD(HOUR, 72, u.signup_date) 
            THEN m.invite_id 
        END) AS invites_72h
        
    FROM project_members m
    -- JOIN với projects để lấy user_id
    INNER JOIN projects p ON m.project_id = p.project_id
    -- JOIN với users để lấy signup_date (dùng cho DATEADD)
    INNER JOIN users u ON p.user_id = u.user_id
    GROUP BY p.user_id
    -- Kết quả: Mỗi user_id xuất hiện 1 lần với số invites_72h
),

-- BƯỚC 3: Kết hợp tất cả metrics cho từng user
User_Metrics AS (
    SELECT 
        u.user_id,
        -- Format tháng đăng ký (YYYY-MM)
        FORMAT(u.signup_date, 'yyyy-MM') AS signup_month,
        
        -- COALESCE: Nếu user không có project → trả về 0 thay vì NULL
        COALESCE(pm.projects_72h, 0) AS projects_72h,
        COALESCE(im.invites_72h, 0) AS invites_72h,
        
        -- Kiểm tra user có mua gói không
        CASE WHEN s.sub_id IS NOT NULL THEN 1 ELSE 0 END AS is_converted,
        
        -- GÁN NHÃN ACTIVATED
        CASE 
            -- Nếu đủ 3 project VÀ 5 invite trong 72h → Activated
            WHEN COALESCE(pm.projects_72h, 0) >= 3 
             AND COALESCE(im.invites_72h, 0) >= 5 
            THEN 1 
            ELSE 0 
        END AS is_activated
        
    FROM users u
    -- LEFT JOIN vì cần GIỮ TẤT CẢ users (kể cả không có project/invite)
    LEFT JOIN Project_Metrics pm ON u.user_id = pm.user_id
    LEFT JOIN Invite_Metrics im ON u.user_id = im.user_id
    LEFT JOIN subscriptions s ON u.user_id = s.user_id
    -- Lúc này mỗi user chỉ JOIN với 1 row từ Project_Metrics và 1 row từ Invite_Metrics
)

-- BƯỚC 4: Tính toán tổng hợp theo tháng
SELECT 
    signup_month,
    
    -- TỔNG SỐ USER
    COUNT(*) AS Total_Users,
    
    -- SỐ USER ĐẠT ACTIVATED
    SUM(is_activated) AS Activated_Users,
    
    -- ACTIVATION RATE (%)
    FORMAT(1.0 * SUM(is_activated) / COUNT(*), 'P1') AS Activation_Rate,
    
    -- CONVERSION RATE CỦA NHÓM ACTIVATED
    -- Tử số: Số user vừa activated vừa converted
    -- Mẫu số: Tổng số user activated
    FORMAT(1.0 * SUM(CASE WHEN is_activated = 1 AND is_converted = 1 THEN 1 ELSE 0 END) 
           / NULLIF(SUM(is_activated), 0), 'P1') AS Conv_Rate_Activated,
    
    -- CONVERSION RATE CỦA NHÓM NON-ACTIVATED
    -- Tử số: Số user KHÔNG activated nhưng vẫn converted
    -- Mẫu số: Tổng số user KHÔNG activated
    FORMAT(1.0 * SUM(CASE WHEN is_activated = 0 AND is_converted = 1 THEN 1 ELSE 0 END) 
           / NULLIF(SUM(CASE WHEN is_activated = 0 THEN 1 ELSE 0 END), 0), 'P1') AS Conv_Rate_NonActivated,
    
    -- IMPACT SCORE (Conv Rate Activated / Conv Rate Non-Activated)
    CAST(
        -- Tử số: Conv Rate Activated
        (1.0 * SUM(CASE WHEN is_activated = 1 AND is_converted = 1 THEN 1 ELSE 0 END) / NULLIF(SUM(is_activated), 0))
        /
        -- Mẫu số: Conv Rate Non-Activated
        NULLIF((1.0 * SUM(CASE WHEN is_activated = 0 AND is_converted = 1 THEN 1 ELSE 0 END) 
                / NULLIF(SUM(CASE WHEN is_activated = 0 THEN 1 ELSE 0 END), 0)), 0)
    AS DECIMAL(10, 1)) AS Impact_Score_X
    
FROM User_Metrics
GROUP BY signup_month
ORDER BY signup_month;

-- RECOMMENDED INDEXES
-- CREATE INDEX IX_projects_user_created 
--     ON projects(user_id, created_at) 
--     INCLUDE (project_id);
-- 
-- CREATE INDEX IX_project_members_project_invited 
--     ON project_members(project_id, invited_at) 
--     INCLUDE (invite_id);
-- 
-- CREATE INDEX IX_subscriptions_user 
--     ON subscriptions(user_id) 
--     INCLUDE (sub_id);

SQL Coding Challenge 02

  • Khách hàng bỏ cuộc vì không biết tạo dự án đầu tiên?
  • Hay họ tạo được dự án nhưng không tạo thêm dự án thứ 3?
  • Hay họ gặp khó khăn khi mời thành viên?

Bạn cần xây dựng một Time-to-Value Funnel để đo lường chính xác tỷ lệ rớt (Drop-off) và thời gian trung bình (Avg Time) giữa các bước quan trọng.

Viết câu truy vấn SQL Server để xây dựng phễu chuyển đổi gồm 4 bước sau cho tất cả user đăng ký trong tháng 1/2024:

  • Step 1: Signup (Thời điểm đăng ký).
  • Step 2: First Project (Thời điểm tạo Project đầu tiên).
  • Step 3: 3rd Project (Thời điểm tạo Project thứ 3).
  • Step 4: Full Activation (Thời điểm đạt cả 2 điều kiện: 3 Projects và 5 Invites).
    • Lưu ý: Thời gian Step 4 = MAX(thời gian Project thứ 3, thời gian Invite thứ 5) – lấy sự kiện muộn hơn

Lưu ý: Chỉ tính các sự kiện xảy ra trong vòng 7 ngày đầu tiên của user. Nếu sự kiện xảy ra sau 7 ngày, coi như user chưa đạt bước đó.

Output mong muốn

Step_NameStep_OrderUsers_ReachedDrop_off_RateAvg_Hours_From_Prev
1. Signup14670.0%0.0
2. First Project241311.6%15.9
3. 3rd Project36883.5%17.4
4. Full Activation45420.6%15.5
  • Step 1 → Step 2: Signup First Project
    • Drop-off: 11.6% – Avg Time: 14.6 giờ
    • Khá Tốt. Gần 90% user tạo được project đầu tiên trong ngày đầu.
  • Step 2 → Step 3: First Project 3rd Project
    • Drop-off: 83.5%Avg Time: 17.4 giờ
    • ĐÂY LÀ BOTTLENECK #1. User không thấy giá trị của việc tạo nhiều project.
  • Step 3 → Step 4: 3rd Project Full Activation
    • Drop-off: 20.6% (14 user có 3 project nhưng chưa đủ 5 invites) 0 Avg Time: 15.5 giờ
    • Drop-off chấp nhận được (79% user hoàn thành cả 2 điều kiện)
    • User mời thành viên trong vòng ~16 giờ sau khi tạo đủ 3 project

Chiến lược

  • PRIORITY 1: Phá vỡ bottleneck Step 2→3 (drop-off 83.5%)
    • Target: Giảm drop-off xuống 50% trong 1 tháng
    • Nếu thành công, số user đạt Full Activation sẽ tăng gấp 3.8 lần
  • PRIORITY 2: Tối ưu Step 3→4 (drop-off 20.6%)
    • Target: Giảm drop-off xuống 10%
    • Nếu thành công, tăng thêm ~21 user activated mỗi cohort
-- BƯỚC 1: Tìm thời gian tạo Project thứ 1 và thứ 3 cho từng user
WITH Project_Milestones AS (
    SELECT 
        user_id,
        -- Lấy thời gian tạo project ĐẦU TIÊN (rn = 1)
        MIN(CASE WHEN rn = 1 THEN created_at END) AS first_project_time,
        -- Lấy thời gian tạo project THỨ 3 (rn = 3)
        MIN(CASE WHEN rn = 3 THEN created_at END) AS third_project_time
        -- Dùng MIN vì sau GROUP BY, mỗi user chỉ có 1 giá trị cho rn=1 và rn=3
        -- MIN sẽ lấy giá trị đó (bỏ qua NULL)
        
    FROM (
        -- Subquery: Đánh số thứ tự cho từng project của user
        SELECT 
            user_id, 
            created_at, 
            -- ROW_NUMBER(): Đánh số 1, 2, 3... cho project theo thời gian
            ROW_NUMBER() OVER (
                PARTITION BY user_id    -- Đánh số RIÊNG cho từng user (reset về 1)
                ORDER BY created_at     -- Sắp xếp theo thời gian (sớm nhất = 1)
            ) AS rn
            -- Ví dụ: User A có 5 projects → rn = 1, 2, 3, 4, 5
        FROM projects
    ) p
    GROUP BY user_id
    -- Kết quả: Mỗi user có first_project_time và third_project_time
    -- (NULL nếu user chưa có đủ 3 projects)
),

-- BƯỚC 2: Tìm thời gian gửi Invite thứ 5 cho từng user
Invite_Milestones AS (
    SELECT 
        user_id,
        -- Lấy thời gian invite THỨ 5 (tính trên TẤT CẢ project của user)
        MIN(CASE WHEN rn = 5 THEN invited_at END) AS fifth_invite_time
        
    FROM (
        -- Subquery: Đánh số thứ tự cho từng invite của user
        SELECT 
            p.user_id,              -- Lấy user_id từ bảng projects
            m.invited_at,           -- Thời gian gửi invite
            -- ROW_NUMBER(): Đánh số 1, 2, 3... cho invite
            ROW_NUMBER() OVER (
                PARTITION BY p.user_id   -- Đánh số riêng cho từng user
                ORDER BY m.invited_at    -- Sắp xếp theo thời gian (sớm nhất = 1)
            ) AS rn
        FROM project_members m
        -- JOIN để biết invite này thuộc về user nào
        INNER JOIN projects p ON m.project_id = p.project_id
    ) t
    GROUP BY user_id
    -- Kết quả: Mỗi user có fifth_invite_time (NULL nếu chưa có đủ 5 invites)
),

-- BƯỚC 3: Xây dựng Funnel Metrics cho từng user
Funnel_Metrics AS (
    SELECT 
        u.user_id,
        u.signup_date,  -- Step 1: Thời gian đăng ký
        
        -- STEP 2: Thời gian tạo PROJECT ĐẦU TIÊN (trong 7 ngày)
        CASE WHEN pm.first_project_time <= DATEADD(DAY, 7, u.signup_date) 
             THEN pm.first_project_time 
             -- Nếu project đầu tiên tạo SAU 7 ngày → NULL (không đạt step 2)
        END AS step2_time,
        
        -- STEP 3: Thời gian tạo PROJECT THỨ 3 (trong 7 ngày)
        CASE WHEN pm.third_project_time <= DATEADD(DAY, 7, u.signup_date) 
             THEN pm.third_project_time 
        END AS step3_time,
        
        -- STEP 4: Thời gian đạt FULL ACTIVATION
        -- Điều kiện: Cả 2 milestone đều phải đạt TRONG 7 NGÀY
        -- Thời gian = Sự kiện muộn hơn (vì cần đạt CẢ HAI)
        CASE 
            -- Kiểm tra CẢ 2 điều kiện: 3 projects VÀ 5 invites trong 7 ngày
            WHEN pm.third_project_time <= DATEADD(DAY, 7, u.signup_date) 
             AND im.fifth_invite_time <= DATEADD(DAY, 7, u.signup_date)
            THEN CASE 
                -- Lấy thời điểm muộn hơn giữa 2 sự kiện
                -- (Vì cần hoàn thành CẢ HAI mới được coi là Full Activation)
                WHEN pm.third_project_time >= im.fifth_invite_time 
                THEN pm.third_project_time  -- 3rd project muộn hơn
                ELSE im.fifth_invite_time    -- 5th invite muộn hơn
            END
            -- Nếu thiếu 1 trong 2 điều kiện → NULL (không đạt Full Activation)
        END AS step4_time
        
    FROM users u
    -- LEFT JOIN để giữ TẤT CẢ users (kể cả user không có project/invite)
    LEFT JOIN Project_Milestones pm ON u.user_id = pm.user_id
    LEFT JOIN Invite_Milestones im ON u.user_id = im.user_id
    -- Mỗi user chỉ JOIN với 1 row từ mỗi CTE → Không có cartesian
),

-- BƯỚC 4: PRE-CALCULATE TẤT CẢ METRICS
Funnel_Counts AS (
    SELECT 
        -- Đếm số user ở MỖI BƯỚC
        COUNT(*) AS total_users,                -- Step 1: Tất cả users
        COUNT(step2_time) AS step2_users,       -- Step 2: Có tạo 1st project
        COUNT(step3_time) AS step3_users,       -- Step 3: Có tạo 3rd project
        COUNT(step4_time) AS step4_users,       -- Step 4: Full Activation
        
        -- Tính THỜI GIAN TRUNG BÌNH giữa các bước
        -- DATEDIFF(MINUTE) / 60.0 để ra số giờ (decimal)
        AVG(DATEDIFF(MINUTE, signup_date, step2_time) / 60.0) AS avg_step2_hours,
        AVG(DATEDIFF(MINUTE, step2_time, step3_time) / 60.0) AS avg_step3_hours,
        AVG(DATEDIFF(MINUTE, step3_time, step4_time) / 60.0) AS avg_step4_hours
        
    FROM Funnel_Metrics
)

-- BƯỚC 5: Format output (UNION ALL chỉ để tạo vertical report)
-- ROW 1: STEP 1 - SIGNUP
SELECT '1. Signup' AS Step_Name, 1 AS Step_Order, total_users AS Users_Reached, 
       FORMAT(0.0, 'P1') AS Drop_off_Rate,  -- Không có drop-off ở bước đầu
       0.0 AS Avg_Hours_From_Prev           -- Không có "bước trước"
FROM Funnel_Counts

UNION ALL

-- ROW 2: STEP 2 - FIRST PROJECT
SELECT '2. First Project', 2, step2_users,
       -- Drop-off Rate = 1 - (users đạt bước này / users ở bước trước)
       FORMAT(1.0 - (1.0 * step2_users / NULLIF(total_users, 0)), 'P1'),
       CAST(avg_step2_hours AS DECIMAL(10,1))
FROM Funnel_Counts

UNION ALL

-- ROW 3: STEP 3 - 3RD PROJECT
SELECT '3. 3rd Project', 3, step3_users,
       -- Drop-off so với Step 2 (không phải Step 1!)
       FORMAT(1.0 - (1.0 * step3_users / NULLIF(step2_users, 0)), 'P1'),
       CAST(avg_step3_hours AS DECIMAL(10,1))
FROM Funnel_Counts

UNION ALL

-- ROW 4: STEP 4 - FULL ACTIVATION
SELECT '4. Full Activation', 4, step4_users,
       -- Drop-off so với Step 3
       FORMAT(1.0 - (1.0 * step4_users / NULLIF(step3_users, 0)), 'P1'),
       CAST(avg_step4_hours AS DECIMAL(10,1))
FROM Funnel_Counts;

-- RECOMMENDED INDEXES
-- Index 1: Support ROW_NUMBER() OVER (PARTITION BY user_id ORDER BY created_at)
-- CREATE INDEX IX_projects_user_created_id 
--     ON projects(user_id, created_at, project_id);
-- 
-- Index 2: Support JOIN và ORDER BY trong Invite_Milestones
-- CREATE INDEX IX_project_members_user_invited 
--     ON project_members(project_id) 
--     INCLUDE (invited_at);

SQL Coding Challenge 03

Bạn cần xây dựng Early Warning System để phát hiện khách hàng sắp rời bỏ.

Sếp của bạn yêu cầu: Đừng báo cáo khi họ đã nghỉ (Churn). Hãy báo cho tôi biết khi họ bắt đầu chậm lại.

  • Engagement Velocity (Tốc độ tương tác): Nếu một user đang hoạt động sôi nổi (tạo dự án, mời người) mà đột ngột giảm tốc độ trong tuần thứ 2 hoặc thứ 3, đó là tín hiệu báo động đỏ.

Viết câu truy vấn SQL Server để phân loại sức khỏe của user dựa trên xu hướng hành động trong 3 tuần đầu tiên.

  • Action: 1 Action = 1 lần tạo Project hoặc 1 lần mời Member.
  • Time Windows:
    • Week 1 (W1): Ngày 0-6 (7 ngày đầu).
    • Week 2 (W2): Ngày 7-13.
    • Week 3 (W3): Ngày 14-20.
  • Risk Label:
    • Nếu W3 = 0 (ngừng hoàn toàn) → Churned (quá muộn để cứu)
    • Nếu W3 giảm mạnh so với W2 (giảm >50%) → At-Risk (đây là tín hiệu báo động đỏ!)
    • Nếu user duy trì hoạt động ổn định → Healthy

Tính toán Conversion Rate (Tỷ lệ mua gói Pro) cho từng nhóm Risk Label để chứng minh rằng nhóm At-Risk thực sự nguy hiểm.

Output mong muốn

Risk_LabelTotal_UsersAvg_W1_ActionsAvg_W2_ActionsAvg_W3_ActionsConversion_Rate
Healthy1422.11.91.636.6%
At-Risk187.75.92.00.0%
Churned3071.90.40.04.2%
  • Healthy (142 users – 30.4%)
    • Xu hướng: W1=2.1 → W2=1.9 → W3=1.6 (giảm nhẹ đều đặn)
    • Conversion Rate: 36.6% (cao nhất)
    • Nhóm này duy trì hoạt động ổn định qua 3 tuần → High quality users.
  • At-Risk (18 users – 3.9%) – NHÓM NGUY HIỂM
    • Xu hướng: W1=7.7 → W2=5.9 → W3=2.0 (GIẢM 66% từ W2!)
    • Conversion Rate: 0.0% (KHÔNG AI MUA!)
    • Đánh giá:
      • Đây là nhóm “Power Users đang mất dần”. Họ bắt đầu với engagement GẤP 4 LẦN nhóm Healthy (7.7 vs 2.1)
      • Họ bắt đầu rất engaged (7.7 actions/week) nhưng giảm tốc mạnh.
      • W2 vẫn cao (5.9) nhưng W3 sụt giảm mạnh xuống 2.0 (chỉ còn 34% của W2)
      • 0% conversion = RED FLAG → chứng tỏ khi họ không còn hứng thú → họ rời đi hoàn toàn.
      • ROOT CAUSE: Power Users kỳ vọng cao → Khi sản phẩm không đáp ứng được kỳ vọng → Họ rời bỏ nhanh hơn user thường
  • Churned (307 users – 65.7%)
    • Xu hướng: W1=1.9 → W2=0.4 → W3=0.0 (giảm mạnh ngay W2!)
    • Conversion Rate: 4.2% (rất thấp)
    • Nhóm này engagement thấp ngay từ W1, và giảm rất nhanh ở W2
-- BƯỚC 1: Tính số PROJECTS trong từng tuần (W1, W2, W3) cho mỗi user
WITH Project_Weekly_Actions AS (
    SELECT 
        p.user_id,
        
        -- ĐẾM PROJECTS TRONG WEEK 1 (Ngày 0-6)
        -- DATEDIFF(DAY, signup_date, created_at) tính số ngày từ lúc signup
        SUM(CASE 
            WHEN DATEDIFF(DAY, u.signup_date, p.created_at) BETWEEN 0 AND 6 
            THEN 1      -- Nếu trong Week 1 → đếm = 1
            ELSE 0      -- Ngoài Week 1 → đếm = 0
        END) AS W1_Projects,
        
        -- ĐẾM PROJECTS TRONG WEEK 2 (Ngày 7-13)
        SUM(CASE 
            WHEN DATEDIFF(DAY, u.signup_date, p.created_at) BETWEEN 7 AND 13 
            THEN 1 
            ELSE 0 
        END) AS W2_Projects,
        
        -- ĐẾM PROJECTS TRONG WEEK 3 (Ngày 14-20)
        SUM(CASE 
            WHEN DATEDIFF(DAY, u.signup_date, p.created_at) BETWEEN 14 AND 20 
            THEN 1 
            ELSE 0 
        END) AS W3_Projects
        
    FROM projects p
    -- INNER JOIN để lấy signup_date (cần cho DATEDIFF)
    INNER JOIN users u ON p.user_id = u.user_id
    GROUP BY p.user_id
    -- Kết quả: Mỗi user có W1_Projects, W2_Projects, W3_Projects
),

-- BƯỚC 2: Tính số INVITES trong từng tuần (W1, W2, W3) cho mỗi user
Invite_Weekly_Actions AS (
    SELECT 
        p.user_id,  -- Lấy user_id từ projects
        
        -- ĐẾM INVITES TRONG WEEK 1
        SUM(CASE 
            WHEN DATEDIFF(DAY, u.signup_date, m.invited_at) BETWEEN 0 AND 6 
            THEN 1 
            ELSE 0 
        END) AS W1_Invites,
        
        -- ĐẾM INVITES TRONG WEEK 2
        SUM(CASE 
            WHEN DATEDIFF(DAY, u.signup_date, m.invited_at) BETWEEN 7 AND 13 
            THEN 1 
            ELSE 0 
        END) AS W2_Invites,
        
        -- ĐẾM INVITES TRONG WEEK 3
        SUM(CASE 
            WHEN DATEDIFF(DAY, u.signup_date, m.invited_at) BETWEEN 14 AND 20 
            THEN 1 
            ELSE 0 
        END) AS W3_Invites
        
    FROM project_members m
    -- JOIN để lấy user_id
    INNER JOIN projects p ON m.project_id = p.project_id
    -- JOIN để lấy signup_date (cần cho DATEDIFF)
    INNER JOIN users u ON p.user_id = u.user_id
    GROUP BY p.user_id
    -- Kết quả: Mỗi user có W1_Invites, W2_Invites, W3_Invites
),

-- BƯỚC 3: Kết hợp Projects + Invites = Total Actions cho từng tuần
Weekly_Stats AS (
    SELECT 
        u.user_id,
        
        -- TOTAL ACTIONS = Projects + Invites
        -- COALESCE: Nếu user không có project/invite → trả về 0 thay vì NULL
        COALESCE(pwa.W1_Projects, 0) + COALESCE(iwa.W1_Invites, 0) AS W1_Actions,
        COALESCE(pwa.W2_Projects, 0) + COALESCE(iwa.W2_Invites, 0) AS W2_Actions,
        COALESCE(pwa.W3_Projects, 0) + COALESCE(iwa.W3_Invites, 0) AS W3_Actions,
        
        -- Kiểm tra user có mua gói không
        CASE WHEN s.sub_id IS NOT NULL THEN 1 ELSE 0 END AS Is_Converted
        
    FROM users u
    -- LEFT JOIN để giữ TẤT CẢ users (kể cả không có actions)
    LEFT JOIN Project_Weekly_Actions pwa ON u.user_id = pwa.user_id
    LEFT JOIN Invite_Weekly_Actions iwa ON u.user_id = iwa.user_id
    LEFT JOIN subscriptions s ON u.user_id = s.user_id
    -- Mỗi user chỉ JOIN với 1 row từ mỗi CTE → Không có cartesian
),

-- BƯỚC 4: Phân loại Risk Label dựa trên xu hướng W1 → W2 → W3
Risk_Segmentation AS (
    SELECT 
        user_id,
        W1_Actions, 
        W2_Actions, 
        W3_Actions,
        Is_Converted,
        
        -- GÁN NHÃN RISK LABEL
        -- CHÚ Ý: Thứ tự của CASE WHEN
        CASE 
            -- ĐIỀU KIỆN 1: Nếu W3 = 0 → CHURNED (Ưu tiên kiểm tra đầu tiên)
            -- User ngừng hoạt động hoàn toàn ở Week 3
            WHEN W3_Actions = 0 THEN 'Churned'
            
            -- ĐIỀU KIỆN 2: Nếu W3 giảm >50% so với W2 → AT-RISK
            -- W2_Actions > 0: Tránh chia cho 0
            -- (1.0 * W3 / W2) < 0.5: W3 < 50% của W2
            -- Ví dụ: W2=6, W3=2 → 2/6 = 0.33 < 0.5 → At-Risk
            WHEN W2_Actions > 0 AND (1.0 * W3_Actions / W2_Actions) < 0.5 
                THEN 'At-Risk'
            
            -- ĐIỀU KIỆN 3: Còn lại → HEALTHY
            -- User duy trì hoạt động ổn định
            ELSE 'Healthy'
        END AS Risk_Label
        
    FROM Weekly_Stats
)

-- BƯỚC 5: Tính toán tổng hợp theo từng Risk Label
SELECT 
    Risk_Label,  -- Nhóm theo nhãn rủi ro
    
    -- ĐẾM TỔNG SỐ USER TRONG TỪNG NHÓM
    COUNT(user_id) AS Total_Users,
    
    -- TÍNH TRUNG BÌNH ACTIONS TRONG MỖI TUẦN
    -- CAST(...AS DECIMAL(10,1)): Format thành số thập phân 1 chữ số (ví dụ: 7.5)
    -- Nhân 1.0: Ép kiểu thành số thập phân (tránh integer division)
    CAST(AVG(1.0 * W1_Actions) AS DECIMAL(10,1)) AS Avg_W1_Actions,
    CAST(AVG(1.0 * W2_Actions) AS DECIMAL(10,1)) AS Avg_W2_Actions,
    CAST(AVG(1.0 * W3_Actions) AS DECIMAL(10,1)) AS Avg_W3_Actions,
    
    -- TÍNH CONVERSION RATE
    -- Công thức: (Số user converted / Tổng số user) × 100%
    -- FORMAT(..., 'P1'): Format thành % với 1 chữ số (ví dụ: 29.4%)
    FORMAT(1.0 * SUM(Is_Converted) / COUNT(user_id), 'P1') AS Conversion_Rate
    
FROM Risk_Segmentation
GROUP BY Risk_Label          -- Gom nhóm theo Risk Label
ORDER BY Conversion_Rate DESC; -- Sắp xếp theo Conversion Rate giảm dần

-- RECOMMENDED INDEXES 
-- Index 1: Support JOIN với users và optimize signup_date lookups
-- CREATE INDEX IX_users_signup 
--     ON users(user_id, signup_date);
-- 
-- Index 2: Support Project_Weekly_Actions aggregation
-- CREATE INDEX IX_projects_user_created 
--     ON projects(user_id) 
--     INCLUDE (created_at);
-- 
-- Index 3: Support Invite_Weekly_Actions aggregation
-- CREATE INDEX IX_project_members_project_invited 
--     ON project_members(project_id) 
--     INCLUDE (invited_at);

Với mô hình Challenge-based Learning, bạn không chỉ được học Business Domain, mà còn được giải các Challenges Thực Tế, rèn Tư Duy Phân Tích và tạo Portfolio ngay trong các Khóa Học Phân Tích Dữ Liệu Thực Chiến. Inbox mình Tại Đây nếu bạn muốn đăng ký nhé.