Menu
ESC

Nhập từ khóa để tìm kiếm

↑↓ Di chuyển
Enter Mở
ESC Đóng

Đang tải...

RFM analysis: phân loại khách bằng SQL

SQL and Data Analysis cho BA Bài 31/60

Mở đầu — vì sao bài này quan trọng

Hãy hình dung bạn là BA cho một sàn thương mại điện tử như Tiki. Phòng marketing chạy tới bàn bạn và nói: "Tháng này ngân sách remarketing chỉ đủ gửi ưu đãi cho 50.000 khách, nhưng database có 2 triệu người. Cho anh danh sách 50.000 người đáng nhận ưu đãi nhất." Nếu bạn chỉ biết viết câu SELECT FROM customers, bạn bế tắc. Nhưng nếu bạn biết RFM, bạn trả lời được trong vòng một buổi chiều — và quan trọng hơn, bạn trả lời bằng logic kinh doanh* chứ không phải cảm tính.

RFM là một trong những kỹ thuật phân khúc khách hàng (customer segmentation) lâu đời, đơn giản và hiệu quả nhất trong ngành. Nó không cần machine learning, không cần Python, không cần data scientist — chỉ cần SQL và một tư duy tốt. Đó là lý do nó là vũ khí kinh điển của BA: bạn tạo ra giá trị kinh doanh trực tiếp chỉ với những gì bạn đã học (GROUP BY, aggregate, window function, CASE WHEN). Bài học này dạy bạn cách biến một bảng giao dịch thô thành một bản đồ phân loại khách hàng mà sếp và team marketing có thể hành động ngay.

Khái niệm cốt lõi

RFM viết tắt của ba câu hỏi về hành vi mua hàng của mỗi khách:

  • Recency (Độ gần đây) — Khách mua gần nhất cách đây bao lâu? Một người mua hôm qua "nóng" hơn nhiều so với người mua cách đây 8 tháng. Recency thường là chỉ báo mạnh nhất cho khả năng khách quay lại.
  • Frequency (Tần suất) — Trong khoảng thời gian xét, khách mua bao nhiêu lần? Mua 20 lần là khách trung thành; mua 1 lần là người dùng thử.
  • Monetary (Giá trị tiền) — Tổng số tiền khách đã chi là bao nhiêu? Phân biệt khách "cá voi" chi nhiều với khách chi tiêu nhỏ lẻ.
Ý tưởng cốt lõi: ba chiều này tóm tắt phần lớn thông tin bạn cần để biết một khách đáng giáđáng đầu tư tới đâu, mà không cần biết họ mua gì hay vì sao.

Từ giá trị thô đến điểm số (score)

Vấn đề là Recency tính bằng ngày, Frequency tính bằng số lần, Monetary tính bằng tiền — ba đơn vị khác nhau, không so sánh trực tiếp được. Cách kinh điển: chuyển mỗi chiều thành một điểm số rời rạc, thường từ 1 đến 5, bằng cách chia khách thành 5 nhóm bằng nhau (quintile). Công cụ SQL để làm điều này là window function NTILE(5) — chia tập dữ liệu đã sắp xếp thành 5 phần đều nhau.

Lưu ý quan trọng về chiều của thang điểm:

  • Frequency và Monetary: càng cao càng tốt → giá trị lớn nhận điểm 5.
  • Recency: càng nhỏ (mới mua gần đây) càng tốt → số ngày nhỏ nhận điểm 5. Đây là chỗ người mới làm hay sai dấu. Mẹo là sắp xếp recency giảm dần theo độ tốt, hoặc dùng NTILE rồi lật ngược điểm.
Sau khi có ba điểm R, F, M (mỗi cái 1–5), bạn ghép lại thành một mã RFM, ví dụ 5-5-5 (khách hoàn hảo) hay 1-1-1 (khách gần như đã mất). Từ mã này, bạn gán nhãn segment dễ hiểu cho người kinh doanh: Champions, Loyal, At Risk, Hibernating, Lost...

Vì sao quintile mà không phải ngưỡng cố định?

Bạn có thể tự đặt ngưỡng kiểu "mua trên 5 triệu là điểm 5". Nhưng ngưỡng cố định lệ thuộc vào trực giác và nhanh lỗi thời. Quintile (chia theo phân vị) tự thích ứng với phân phối thực tế của dữ liệu: nhóm 20% chi tiêu cao nhất của chính bạn luôn được điểm 5, dù tổng giá trị thị trường tăng hay giảm. Đây là lựa chọn mặc định an toàn cho BA.

Tình huống thực tế

Ví dụ 1 — Tiki và bài toán "tiêu đúng ngân sách remarketing"

Bối cảnh: Một sàn TMĐT giả định kiểu Tiki có bảng orders(order_id, customer_id, order_date, amount). Quý vừa rồi team marketing đốt 800 triệu gửi voucher cho toàn bộ khách từng mua, nhưng tỷ lệ chuyển đổi chỉ 1,2%. Phần lớn ngân sách rơi vào nhóm khách đã "ngủ đông" cả năm — gửi voucher cho họ gần như vứt tiền.

BA chạy RFM và phát hiện: nhóm Champions (R=5, F=5, M=5) chỉ chiếm 6% số khách nhưng đóng góp 41% doanh thu; nhóm At Risk (trước đây mua nhiều nhưng gần đây im hơi — R thấp, F và M cao) chiếm 9%. Đề xuất: thay vì rải đều, dồn voucher giá trị cao cho nhóm At Risk (kéo họ quay lại) và voucher tri ân nhẹ cho Champions (giữ chân, tránh "đốt tiền" cho người chắc chắn quay lại). Kết quả mô phỏng: cùng 800 triệu nhưng tập trung, tỷ lệ chuyển đổi lên 4,5%.

Bài học rút ra: RFM không chỉ phân loại — nó giúp phân bổ ngân sách theo giá trị kỳ vọng. Champions không cần dụ nhiều; At Risk mới là nơi mỗi đồng voucher tạo ra nhiều giá trị tăng thêm nhất.

Ví dụ 2 — GrabFood và việc phát hiện khách sắp rời bỏ

Bối cảnh: Một nền tảng đặt món kiểu GrabFood ở TP.HCM. Đặc thù: tần suất rất cao (khách trung thành đặt 15–30 lần/tháng) nhưng giá trị mỗi đơn nhỏ (80–200k). Nếu chỉ nhìn Monetary, bạn sẽ đánh giá thấp những khách đặt đều đặn mà giá trị từng đơn không lớn.

BA xây RFM theo tháng và đặc biệt theo dõi nhóm có F cao, M cao nhưng R đang tụt (điểm Recency rớt từ 5 xuống 2 trong hai tháng). Đây là tín hiệu sớm của churn: khách trung thành đang đặt thưa dần — có thể vì một đối thủ tung khuyến mãi, hoặc trải nghiệm giao hàng gần đây tệ. Team CRM gửi ngay mã free-ship cho riêng nhóm này. Trong nhóm được can thiệp, tỷ lệ "quay lại đặt trong 14 ngày" cao hơn 23% so với nhóm đối chứng không nhận gì.

Bài học rút ra: Recency là "đèn báo sớm". Với mô hình tần suất cao, sự thay đổi của điểm R qua thời gian còn quan trọng hơn giá trị tuyệt đối. RFM trở thành công cụ cảnh báo churn, không chỉ là ảnh chụp tĩnh.

Ví dụ 3 — Chuỗi mỹ phẩm và nhận diện "khách mới tiềm năng"

Bối cảnh: Một chuỗi bán lẻ mỹ phẩm online. Marketing than: "Khách mới mua xong là biến mất, làm sao biết ai đáng chăm?" BA phân khúc và tìm nhóm New Customers / Promising: R=5 (vừa mua), nhưng F=1 (mới mua đúng một lần). Trong nhóm này, BA tách tiếp theo Monetary của đơn đầu: người mua đơn đầu giá trị cao (M=4–5) có xác suất mua lần hai cao hơn hẳn.

Đề xuất: với nhóm "mới + đơn đầu giá trị cao", gửi email gợi ý sản phẩm bổ trợ (cross-sell) trong vòng 7 ngày — đúng lúc khách còn "nóng". Chiến dịch này nâng tỷ lệ mua lần hai từ 11% lên 19%.

Bài học rút ra: RFM giúp tách "khách mới đáng đầu tư" khỏi "khách mới qua đường". Cùng là R cao F thấp, nhưng kết hợp thêm M cho ra hành động hoàn toàn khác.

Hướng dẫn từng bước

Ta dựng pipeline RFM hoàn chỉnh trên bảng orders(customer_id, order_date, amount). Dùng CTE cho dễ đọc (kỹ thuật bạn đã học ở Bài 16).

Bước 1 — Tính giá trị R, F, M thô cho mỗi khách.

Ta cần một "ngày tham chiếu" (analysis date) — thường là ngày chạy báo cáo, hoặc ngày đơn hàng mới nhất trong dữ liệu + 1.

WITH rfm_base AS (
    SELECT
        customer_id,
        DATEDIFF(
            (SELECT MAX(order_date) FROM orders),
            MAX(order_date)
        ) AS recency_days,        -- số ngày từ lần mua cuối đến mốc tham chiếu
        COUNT(*)        AS frequency,   -- số đơn
        SUM(amount)     AS monetary     -- tổng chi tiêu
    FROM orders
    GROUP BY customer_id
)

Bước 2 — Chuyển giá trị thô thành điểm 1–5 bằng NTILE.

, rfm_scored AS (
    SELECT
        customer_id,
        recency_days,
        frequency,
        monetary,
        -- Recency: ÍT ngày = TỐT. NTILE chia tăng dần theo recency_days,
        -- nên nhóm đầu (ít ngày) đáng lẽ là tốt nhất → lật điểm: 6 - ntile
        6 - NTILE(5) OVER (ORDER BY recency_days)        AS r_score,
        NTILE(5) OVER (ORDER BY frequency)               AS f_score,
        NTILE(5) OVER (ORDER BY monetary)                AS m_score
    FROM rfm_base
)

Giải thích chỗ "lật": NTILE(5) OVER (ORDER BY recency_days) gán điểm 1 cho 20% khách có recency_days nhỏ nhất (mới mua nhất). Nhưng ta muốn họ được điểm 5. Lấy 6 - ntile để đảo: 1→5, 2→4, ..., 5→1. Với Frequency và Monetary thì giữ nguyên vì cao = tốt.

Bước 3 — Ghép mã RFM và gán nhãn segment.

SELECT
    customer_id,
    r_score, f_score, m_score,
    CONCAT(r_score, '-', f_score, '-', m_score) AS rfm_cell,
    CASE
        WHEN r_score >= 4 AND f_score >= 4 AND m_score >= 4 THEN 'Champions'
        WHEN r_score >= 3 AND f_score >= 4                  THEN 'Loyal'
        WHEN r_score >= 4 AND f_score <= 2                  THEN 'New / Promising'
        WHEN r_score <= 2 AND f_score >= 4 AND m_score >= 4 THEN 'At Risk'
        WHEN r_score <= 2 AND f_score <= 2                  THEN 'Hibernating'
        WHEN r_score = 1                                    THEN 'Lost'
        ELSE 'Needs Attention'
    END AS segment
FROM rfm_scored
ORDER BY m_score DESC, f_score DESC, r_score DESC;

Bước 4 — Tổng hợp theo segment để báo cáo cho sếp. Đừng đưa danh sách 2 triệu dòng cho marketing. Hãy đưa bảng tóm tắt:

-- giả sử query trên đã lưu vào view/CTE tên customer_rfm
SELECT
    segment,
    COUNT(*)                              AS so_khach,
    ROUND(AVG(monetary), 0)               AS chi_tieu_tb,
    SUM(monetary)                         AS tong_doanh_thu,
    ROUND(100.0 * SUM(monetary) / SUM(SUM(monetary)) OVER (), 1) AS pct_doanh_thu
FROM customer_rfm
GROUP BY segment
ORDER BY tong_doanh_thu DESC;

Bảng này mới là thứ "nói chuyện được" với người kinh doanh: mỗi segment có bao nhiêu khách, đóng góp bao nhiêu phần trăm doanh thu — từ đó quyết định đổ ngân sách vào đâu.

> Ghi chú phương ngữ SQL: DATEDIFF ở MySQL nhận (ngày_lớn, ngày_nhỏ). Trên PostgreSQL bạn viết (SELECT MAX(order_date) FROM orders) - MAX(order_date) để ra số ngày, và dùng || thay cho CONCAT nếu muốn. Logic NTILE và CASE thì giống nhau.

Lỗi thường gặp & mẹo

Lỗi 1 — Sai dấu của Recency. Đây là lỗi phổ biến nhất. Quên "lật" điểm khiến khách mới mua bị gán R=1 và khách bỏ đi cả năm thành R=5 — toàn bộ phân khúc đảo ngược. Luôn kiểm tra lại: lấy một khách bạn biết vừa mua hôm qua, đảm bảo họ có r_score = 5.

Lỗi 2 — Quên lọc khoảng thời gian. Nếu bảng orders chứa cả dữ liệu 10 năm, Monetary và Frequency bị "thổi phồng" bởi quá khứ xa. Thông lệ: giới hạn cửa sổ phân tích, ví dụ 12 tháng gần nhất (WHERE order_date >= DATE_SUB(CURRENT_DATE, INTERVAL 12 MONTH)), trừ khi có lý do nhìn toàn bộ vòng đời.

Lỗi 3 — Đếm cả đơn hủy/hoàn vào Frequency và Monetary. Một khách đặt rồi hủy 10 đơn không phải khách trung thành. Luôn lọc WHERE status = 'completed' (hoặc tương đương) trước khi tính. RFM tính trên giao dịch thành công.

Lỗi 4 — NTILE bị lệch khi dữ liệu trùng nhiều. Nếu 60% khách chỉ mua đúng 1 lần, NTILE(5) OVER (ORDER BY frequency) sẽ cắt nhóm "frequency = 1" thành nhiều quintile khác nhau một cách tùy tiện — hai khách giống hệt nhau (cùng mua 1 lần) lại rơi vào điểm F khác nhau. Mẹo: với Frequency rời rạc và lệch nặng, cân nhắc gán điểm bằng CASE WHEN frequency = 1 THEN 1 WHEN frequency <= 3 THEN 2 ... theo ngưỡng kinh doanh thay vì NTILE máy móc.

Mẹo 1 — Đừng dùng cả 125 ô (5×5×5). Về lý thuyết có 125 mã RFM. Không ai hành động được với 125 nhóm. Hãy gộp thành 6–10 segment có tên gọi kinh doanh rõ ràng như ở Bước 3. Tên segment là cầu nối giữa SQL của bạn và hành động của marketing.

Mẹo 2 — Có thể giảm còn RF (bỏ M) khi mô hình giá trị đơn đồng đều. Với dịch vụ thuê bao giá cố định, Monetary gần như tỉ lệ thuận với Frequency, nên M không thêm thông tin. Lúc đó RF 2 chiều gọn và đủ.

Mẹo 3 — Lưu kết quả thành snapshot theo thời gian. Chạy RFM mỗi tháng và lưu lại (kèm cột snapshot_month). Khi đó bạn theo dõi được sự dịch chuyển segment — ai từ Loyal rớt xuống At Risk — chính là tín hiệu vàng cho đội giữ chân khách.

Bài tập thực hành

Dùng bảng orders(order_id, customer_id, order_date, amount, status).

  • Cơ bản: Viết CTE rfm_base tính recency_days, frequency, monetary cho mỗi khách, chỉ tính đơn status = 'completed' trong 12 tháng gần nhất.
  • Cho điểm: Mở rộng thành rfm_scored với r_score, f_score, m_score (1–5) bằng NTILE. Tự kiểm tra dấu của Recency bằng cách lấy một khách có recency_days nhỏ nhất và xác nhận r_score = 5.
  • Gán nhãn: Thêm cột segment với ít nhất 6 nhãn (Champions, Loyal, New, At Risk, Hibernating, Lost). Viết câu mô tả ngắn cho mỗi nhãn nên nhận hành động marketing gì.
  • Báo cáo: Tạo bảng tóm tắt: mỗi segment có bao nhiêu khách, chi tiêu trung bình, và chiếm bao nhiêu phần trăm tổng doanh thu (dùng window function trong tử/mẫu như Bước 4).
  • Nâng cao: Giả sử bạn có hai snapshot RFM của tháng 5 và tháng 6. Viết query tìm danh sách khách rớt từ segment 'Loyal' (tháng 5) xuống 'At Risk' (tháng 6) — đây là danh sách ưu tiên cho chiến dịch giữ chân.

Tóm tắt

RFM trả lời ba câu hỏi đơn giản — mua gần đây chưa (Recency), mua thường xuyên không (Frequency), chi nhiều không (Monetary) — rồi biến chúng thành điểm 1–5 để so sánh khách với nhau. Với BA, đây là kỹ thuật phân khúc "đáng đồng tiền" nhất: chỉ cần GROUP BY, NTILE và CASE WHEN, bạn đã có thể nói cho team kinh doanh biết nên dồn ngân sách vào ai.

Những điểm cần khắc cốt ghi tâm: nhớ lật dấu Recency (mới mua = điểm cao); luôn lọc đơn thành công và giới hạn cửa sổ thời gian; đừng sa lầy vào 125 ô mà hãy gộp thành dăm bảy segment có tên gọi mà người kinh doanh hiểu được; và mạnh nhất là chạy RFM định kỳ để bắt khoảnh khắc khách dịch chuyển segment — đó là lúc một câu SQL của bạn cứu được một khách hàng sắp rời đi. Khi bạn nối được từ bảng giao dịch thô tới một quyết định marketing cụ thể, bạn không còn là người "truy vấn dữ liệu" mà đã là người tạo ra giá trị từ dữ liệu.