在数据分析领域,了解客户是推动增长的关键。客户细分最有效且经过时间检验的方法之一是 RFM 分析。但你不需要昂贵的 CRM 软件来实现它------你可以使用标准 SQL 在数据库中构建强大的 RFM 模型。
在本指南中,我们将详细介绍如何计算 Recency(最近性)、Frequency(频率)和 Monetary(金额)评分,并将客户群体细分为可操作的群组,如"Champions"(冠军客户)、"At Risk"(流失风险客户)和"New Customers"(新客户)。

RFM 细分概览,显示 Champions、At Risk 等客户群体
什么是 RFM 分析?
RFM 代表描述客户行为的三个关键指标:
- Recency ® - 最近性:客户最近一次购买是什么时候?
- Frequency (F) - 频率:客户购买的频率如何?
- Monetary (M) - 金额:客户花费了多少钱?
通过在这三个维度上对客户进行评分(通常从 1 到 5),你可以将他们分组到不同的细分中。例如,在所有三个类别中得分都很高的客户是"Champion"(冠军客户),而 Monetary 价值高但 Recency 低的客户可能"At Risk"(有流失风险)。
RFM 分析的价值
精准营销:根据客户细分制定针对性的营销策略,提高转化率和 ROI。
客户保留:识别流失风险客户,及时采取挽留措施。
资源优化:将营销预算和资源集中在最有价值的客户群体上。
个性化体验:为不同细分的客户提供定制化的产品推荐和服务。
步骤 1:准备数据
要执行 RFM 分析,你通常需要一个交易表。假设我们有一个 orders_rfm_demo 表,包含 customer_id、order_date 和 total_amount 字段。
第一步是将这些数据聚合到客户级别。我们需要:
- 最后订单日期 :
MAX(order_date) - 订单数量 :
COUNT(order_id) - 总消费 :
SUM(total_amount)
示例数据(orders_rfm_demo 表):
| order_id | customer_id | order_date | total_amount |
|---|---|---|---|
| 1001 | C001 | 2023-12-15 | 150.00 |
| 1002 | C001 | 2023-11-20 | 200.00 |
| 1003 | C002 | 2023-12-28 | 80.00 |
| 1004 | C003 | 2023-10-05 | 500.00 |
| 1005 | C003 | 2023-11-10 | 300.00 |
聚合查询:
sql
SELECT
customer_id,
MAX(order_date) as last_order_date,
COUNT(order_id) as frequency,
SUM(total_amount) as monetary
FROM orders_rfm_demo
GROUP BY customer_id;
聚合结果示例:
| customer_id | last_order_date | frequency | monetary |
|---|---|---|---|
| C001 | 2023-12-15 | 2 | 350.00 |
| C002 | 2023-12-28 | 1 | 80.00 |
| C003 | 2023-11-10 | 2 | 800.00 |
步骤 2:计算 RFM 值
现在,让我们将这些原始数字转换为"R"、"F"和"M"值。
对于 Recency ,我们需要自最后一次购买以来的天数。我们将 last_order_date 与参考日期(通常是"今天"或分析日期)进行比较。
计算 Recency 的查询:
sql
SELECT
customer_id,
julianday('2024-01-01') - julianday(MAX(order_date)) as recency_days,
COUNT(order_id) as frequency,
SUM(total_amount) as monetary
FROM orders_rfm_demo
GROUP BY customer_id;
注意 :在大多数 SQL 方言(如 PostgreSQL)中,你可能使用 CURRENT_DATE - MAX(order_date),但这里我们使用固定日期以确保可重现性。
计算结果示例:
| customer_id | recency_days | frequency | monetary |
|---|---|---|---|
| C001 | 17 | 2 | 350.00 |
| C002 | 4 | 1 | 80.00 |
| C003 | 52 | 2 | 800.00 |
不同数据库的日期计算
PostgreSQL:
sql
CURRENT_DATE - MAX(order_date) as recency_days
MySQL:
sql
DATEDIFF(CURRENT_DATE, MAX(order_date)) as recency_days
SQL Server:
sql
DATEDIFF(day, MAX(order_date), GETDATE()) as recency_days
SQLite:
sql
julianday('2024-01-01') - julianday(MAX(order_date)) as recency_days
步骤 3:使用 NTILE 确定 RFM 评分
这是魔法发生的地方。我们不能只看原始金额,因为不同企业的消费范围差异很大。相反,我们使用以下逻辑对客户进行相对排名:"前 20% 得 5 分,后 20% 得 1 分"。
SQL 窗口函数 NTILE(5) 非常适合这个任务。
评分逻辑:
- Recency 评分 :天数越少越好(5 分),天数越多越差(1 分)。
NTILE将 1 分配给最低值。所以对于 Recency(低值好),低recency_days会得到NTILE1。我们想要相反的结果。我们可以对NTILE函数使用recency_days DESC排序,这样最大的天数(最差)得到 1,最小的(最好)得到 5。 - Frequency 评分 :越高越好。按
frequency ASC排序。 - Monetary 评分 :越高越好。按
monetary ASC排序。
完整的 RFM 评分查询:
sql
WITH base_metrics AS (
SELECT
customer_id,
-- 计算距离最后订单的天数(假设分析日期为 2024-01-01)
CAST(julianday('2024-01-01') - julianday(MAX(order_date)) AS INTEGER) as recency_days,
COUNT(order_id) as frequency,
SUM(total_amount) as monetary
FROM orders_rfm_demo
GROUP BY customer_id
),
scored_data AS (
SELECT
customer_id,
recency_days,
frequency,
monetary,
-- 较低的 recency_days 更好,所以我们按 DESC 排序以获得最佳的 5 分
NTILE(5) OVER (ORDER BY recency_days DESC) as r_score,
NTILE(5) OVER (ORDER BY frequency ASC) as f_score,
NTILE(5) OVER (ORDER BY monetary ASC) as m_score
FROM base_metrics
)
SELECT
customer_id,
recency_days,
frequency,
monetary,
r_score,
f_score,
m_score
FROM scored_data
ORDER BY r_score DESC, f_score DESC, m_score DESC;
评分结果示例:
| customer_id | recency_days | frequency | monetary | r_score | f_score | m_score |
|---|---|---|---|---|---|---|
| C002 | 4 | 1 | 80.00 | 5 | 1 | 1 |
| C001 | 17 | 2 | 350.00 | 4 | 3 | 3 |
| C003 | 52 | 2 | 800.00 | 2 | 3 | 5 |
NTILE 函数详解
NTILE(n) 将结果集分为 n 个大致相等的组,并为每行分配一个组号(1 到 n)。
工作原理:
- 如果有 100 个客户,
NTILE(5)将它们分为 5 组,每组 20 个客户 - 第 1 组得 1 分,第 2 组得 2 分,依此类推
- 排序顺序决定了哪些客户进入哪个组
为什么 Recency 使用 DESC 排序:
NTILE默认将 1 分配给最低值- 对于 Recency,低天数(最近购买)应该得高分
- 使用 DESC 排序后,最大天数(最久未购买)得 1 分,最小天数(最近购买)得 5 分
步骤 4:创建可读的客户细分
拥有像"555"或"121"这样的评分对机器有用,但人类更喜欢标签。我们可以使用 CASE 语句将这些评分转换为命名的细分。
常见的细分定义:
- Champions(冠军客户):R=4-5, F=4-5, M=4-5
- Loyal Customers(忠诚客户):F=3-5, R=3-5(无论 M 如何)
- Potential Loyalists(潜在忠诚客户):最近的客户,频率中等
- New Customers(新客户):R=4-5, F=1
- At Risk(流失风险客户):R≤2, F≥3(即将流失)
- Standard(标准客户):其他
添加客户细分的完整查询:
sql
WITH base_metrics AS (
SELECT
customer_id,
CAST(julianday('2024-01-01') - julianday(MAX(order_date)) AS INTEGER) as recency_days,
COUNT(order_id) as frequency,
SUM(total_amount) as monetary
FROM orders_rfm_demo
GROUP BY customer_id
),
scored_data AS (
SELECT
customer_id,
recency_days,
frequency,
monetary,
NTILE(5) OVER (ORDER BY recency_days DESC) as r_score,
NTILE(5) OVER (ORDER BY frequency ASC) as f_score,
NTILE(5) OVER (ORDER BY monetary ASC) as m_score
FROM base_metrics
)
SELECT
customer_id,
recency_days,
frequency,
monetary,
r_score,
f_score,
m_score,
CASE
WHEN r_score >= 4 AND f_score >= 4 AND m_score >= 4 THEN 'Champions'
WHEN r_score >= 3 AND f_score >= 3 THEN 'Loyal Customers'
WHEN r_score >= 4 AND f_score = 1 THEN 'New Customers'
WHEN r_score <= 2 AND f_score >= 3 THEN 'At Risk'
ELSE 'Standard'
END as customer_segment
FROM scored_data
ORDER BY r_score DESC, f_score DESC, m_score DESC;
最终结果示例:
| customer_id | recency_days | frequency | monetary | r_score | f_score | m_score | customer_segment |
|---|---|---|---|---|---|---|---|
| C002 | 4 | 1 | 80.00 | 5 | 1 | 1 | New Customers |
| C001 | 17 | 2 | 350.00 | 4 | 3 | 3 | Loyal Customers |
| C003 | 52 | 2 | 800.00 | 2 | 3 | 5 | At Risk |
客户细分详解
Champions(冠军客户)
- 特征:最近购买、频繁购买、高消费
- 营销策略:VIP 待遇、独家优惠、新品首发、推荐计划
- 目标:保持忠诚度,鼓励口碑传播
Loyal Customers(忠诚客户)
- 特征:定期购买、中高消费
- 营销策略:会员奖励、生日优惠、忠诚度积分
- 目标:提升到 Champions 级别
Potential Loyalists(潜在忠诚客户)
- 特征:最近购买、中等频率
- 营销策略:个性化推荐、会员计划邀请
- 目标:增加购买频率
New Customers(新客户)
- 特征:最近首次购买
- 营销策略:欢迎邮件、新手指南、首单优惠
- 目标:促进第二次购买
At Risk(流失风险客户)
- 特征:曾经频繁购买,但最近未购买
- 营销策略:挽回优惠、问卷调查、个性化沟通
- 目标:重新激活
Hibernating(休眠客户)
- 特征:很久未购买
- 营销策略:重新激活活动、特别折扣
- 目标:唤醒兴趣
Lost(流失客户)
- 特征:很久未购买且消费低
- 营销策略:最后一次挽回尝试或放弃
- 目标:决定是否值得投入资源
SQL 中 RFM 的最佳实践
1. 过滤异常值
在计算之前排除退货(负值)或测试交易。
sql
WHERE total_amount > 0
AND customer_id NOT LIKE 'TEST%'
2. 时间范围很重要
RFM 通常在滑动窗口(例如,过去 12 个月)上计算。较旧的数据可能会扭曲 Recency。
sql
WHERE order_date >= DATE_SUB(CURRENT_DATE, INTERVAL 12 MONTH)
3. 分箱分布
NTILE 强制等大小的组。如果你的数据倾斜(例如,90% 的客户只购买过一次),NTILE 可能会任意分割它们。在这种情况下,使用自定义 CASE 范围进行评分(例如,"Frequency 1 = 1 分","Freq 2-5 = 3 分")可能比严格的百分位数更准确。
sql
CASE
WHEN frequency = 1 THEN 1
WHEN frequency BETWEEN 2 AND 5 THEN 3
WHEN frequency > 5 THEN 5
END as f_score
4. 定期更新
RFM 分析应该定期更新(每周或每月),以反映最新的客户行为。
5. 结合其他指标
RFM 可以与其他指标结合使用,如客户生命周期价值(CLV)、客户获取成本(CAC)等。
实际应用场景
1. 电商平台
根据 RFM 细分发送个性化邮件营销活动,提高转化率。
2. 订阅服务
识别流失风险客户,提前采取挽留措施,降低流失率。
3. 零售业
优化库存管理,为不同细分的客户准备合适的产品。
4. 金融服务
识别高价值客户,提供专属理财服务和产品。
5. 会员制企业
设计分层会员计划,根据 RFM 评分提供差异化权益。
性能优化
1. 创建索引
为常用的查询列创建索引:
sql
CREATE INDEX idx_customer_order ON orders_rfm_demo(customer_id, order_date);
2. 物化视图
对于大数据集,考虑使用物化视图:
sql
CREATE MATERIALIZED VIEW rfm_scores AS
SELECT ...
3. 分区表
对于非常大的交易表,考虑按日期分区:
sql
CREATE TABLE orders_rfm_demo (
...
) PARTITION BY RANGE (order_date);
结论
RFM 分析是数据科学中的低垂果实,可以立即提供价值。通过直接在 SQL 中实现它,你可以创建动态仪表板报告,随着新订单的到来自动更新。无需导出 CSV 或运行 Python 脚本------你的数据库可以处理繁重的工作!
关键要点:
- RFM 分析基于三个维度:Recency、Frequency、Monetary
- 使用
NTILE(5)将客户分为 5 个等级 - 根据 RFM 评分组合定义客户细分
- 为不同细分制定针对性的营销策略
- 定期更新分析以反映最新的客户行为
掌握 SQL 中的 RFM 分析,你将能够更好地理解客户,优化营销策略,提高客户保留率和收入。
相关文章推荐
- Calculating Percentiles and Median in SQL - AVG 告诉你平均值,但中位数和百分位数呢?
- Ranking Data with SQL: RANK, DENSE_RANK, and ROW_NUMBER Explained - 构建排行榜、查找顶尖表现者或分页结果
- Mastering SQL LEAD and LAG Functions for Row Comparisons - 需要比较一行与其前一行或后一行吗?
本文转载自 www.hisqlboy.com
原文标题:Customer Segmentation with RFM Analysis in SQL
原文链接:https://www.hisqlboy.com/blog/customer-segmentation-rfm-analysis-sql
原作者:SQL Boy Team
转载日期:2026-02-11