2. SQL基础回顾:SELECT、WHERE、GROUP BY、HAVING、ORDER BY、JOIN等核心语法在催收数据中的应用

在催收特征筛选工作中,SQL是提取、清洗、聚合和关联数据的核心工具。无论是计算逾期天数、统计客户历史还款行为,还是拼接多张业务表以构建完整的特征宽表,都离不开SQL基础语法的灵活运用。本节将结合催收业务场景,系统回顾最常用的SQL核心语法。

2.1 数据查询与过滤:SELECT + WHERE

SELECT 用于指定要查询的字段,WHERE 用于按条件过滤记录。在催收数据中,最常见的过滤条件是逾期状态、案件阶段、客户分组等。

业务场景示例: 从逾期案件表 overdue_case 中提取所有逾期天数超过30天且当前余额大于5000元的案件ID和客户姓名。
SELECT 
    case_id,
    customer_name,
    overdue_days,
    current_balance
FROM overdue_case
WHERE overdue_days > 30
  AND current_balance > 5000;
关键字 催收业务常用场景 注意事项
SELECT 选择案件ID、客户ID、逾期天数、分配催收员等字段 避免使用 SELECT *,应明确列出所需字段以提升性能
WHERE 过滤逾期天数 > N、案件状态 = '已分配'、催收员ID = 指定值 注意 NULL 值的处理,使用 IS NULL 或 IS NOT NULL
AND / OR 组合多个条件,如逾期天数 > 30 且 余额 > 10000 使用括号明确优先级,避免逻辑歧义
IN / NOT IN 筛选指定催收员列表、特定产品类型 IN 列表不宜过长,否则考虑使用临时表或 JOIN
LIKE 模糊匹配客户姓名、地址等文本字段 避免前置通配符 %,会导致索引失效
技巧提示: 在特征筛选阶段,WHERE 条件应尽量使用索引字段(如案件ID、分配日期),以加速数据提取。对于大表,避免在 WHERE 中对字段进行函数运算(如 WHERE DATE(create_time) = '2025-01-01'),应改写为范围条件(如 WHERE create_time >= '2025-01-01' AND create_time < '2025-01-02')。
SELECT + WHERE 数据过滤流程 overdue_case 源数据表 WHERE 过滤 逾期天数 > 30 SELECT 投影 选择目标字段 结果集 过滤后的目标数据 过滤条件:逾期天数 > 30 AND 当前余额 > 5000 索引优化:在 overdue_days 和 current_balance 字段上建立索引

2.2 数据聚合与分组:GROUP BY + HAVING

GROUP BY 用于按一个或多个字段对数据进行分组,配合聚合函数(如 COUNT、SUM、AVG、MAX、MIN)计算每组的统计值。HAVING 用于对分组后的结果进行过滤,类似于 WHERE 但作用于聚合后的数据。

业务场景示例: 统计每个催收员名下逾期天数超过60天的案件数量,并筛选出案件数超过10个的催收员。
SELECT 
    collector_id,
    COUNT(case_id) AS high_risk_case_count,
    AVG(current_balance) AS avg_balance
FROM overdue_case
WHERE overdue_days > 60
GROUP BY collector_id
HAVING COUNT(case_id) > 10
ORDER BY high_risk_case_count DESC;
语法元素 催收业务应用 执行顺序
GROUP BY 按催收员、产品类型、逾期区间、城市等分组 在 WHERE 之后,HAVING 之前
聚合函数 COUNT(案件数)、SUM(逾期金额)、AVG(逾期天数)、MAX(最长逾期) 在 GROUP BY 分组后对每组计算
HAVING 过滤分组后的统计结果,如案件数 > 10、平均余额 > 5000 在 GROUP BY 之后,ORDER BY 之前
WHERE vs HAVING WHERE 过滤原始行,HAVING 过滤分组后的聚合结果 WHERE 先执行,减少分组数据量
常见错误: 在 SELECT 中使用了非聚合字段但未将其包含在 GROUP BY 中。例如:SELECT collector_id, case_id, COUNT(*) FROM overdue_case GROUP BY collector_id 会报错,因为 case_id 不在 GROUP BY 中且未被聚合。解决方案:要么将 case_id 加入 GROUP BY,要么使用聚合函数(如 MAX(case_id))包裹。

2.3 数据排序:ORDER BY

ORDER BY 用于对查询结果按一个或多个字段进行升序(ASC)或降序(DESC)排序。在催收特征筛选中,排序常用于查看风险最高的案件、最紧急的待办事项等。

-- 按逾期天数降序排列,查看风险最高的前20个案件
SELECT 
    case_id,
    customer_name,
    overdue_days,
    current_balance
FROM overdue_case
WHERE case_status = '待催收'
ORDER BY overdue_days DESC, current_balance DESC
LIMIT 20;
排序优化: ORDER BY 字段应尽量使用索引,避免对大结果集进行文件排序(filesort)。如果排序字段较多,可以考虑建立复合索引。在特征筛选的探索阶段,可以先 LIMIT 少量数据测试排序结果,确认无误后再全量执行。

2.4 多表关联:JOIN

催收数据通常分散在多张表中,如客户信息表、案件表、还款记录表、催收通话记录表等。JOIN 用于将这些表按关联键连接起来,构建包含完整信息的特征宽表。

JOIN 类型 催收业务场景 返回结果
INNER JOIN 只保留两表中匹配成功的记录,如案件表与客户表通过客户ID关联 仅匹配成功的行
LEFT JOIN 保留左表全部记录,右表无匹配时填充 NULL,如案件表左连还款记录表 左表全部 + 右表匹配行
RIGHT JOIN 保留右表全部记录,左表无匹配时填充 NULL(较少使用) 右表全部 + 左表匹配行
FULL OUTER JOIN 保留两表全部记录,无匹配时填充 NULL(部分数据库不支持) 两表全部行
业务场景示例: 将逾期案件表与客户信息表关联,同时左连最近一次还款记录,构建包含客户基本信息和还款行为的特征宽表。
SELECT 
    oc.case_id,
    oc.overdue_days,
    oc.current_balance,
    ci.customer_name,
    ci.credit_score,
    ci.income_level,
    rp.last_repayment_date,
    rp.last_repayment_amount
FROM overdue_case oc
INNER JOIN customer_info ci ON oc.customer_id = ci.customer_id
LEFT JOIN (
    SELECT 
        customer_id,
        MAX(repayment_date) AS last_repayment_date,
        MAX(repayment_amount) AS last_repayment_amount
    FROM repayment_record
    GROUP BY customer_id
) rp ON oc.customer_id = rp.customer_id
WHERE oc.overdue_days > 30;
催收数据多表 JOIN 关联关系 overdue_case 逾期案件表 case_id (PK) customer_id (FK) customer_info 客户信息表 customer_id (PK) credit_score, income repayment_record 还款记录表 customer_id (FK) repayment_date, amount INNER JOIN on customer_id LEFT JOIN on customer_id 结果特征宽表 包含案件、客户、还款信息

2.5 子查询与公用表表达式(CTE)

在催收特征筛选过程中,经常需要先计算一些中间统计量,再基于这些统计量进行进一步筛选。子查询和 CTE 是组织复杂查询的两种有效方式。

业务场景示例: 找出每个催收员名下逾期金额超过该催收员平均逾期金额的案件。
-- 使用 CTE 方式(推荐,可读性更好)
WITH collector_avg AS (
    SELECT 
        collector_id,
        AVG(current_balance) AS avg_balance
    FROM overdue_case
    WHERE overdue_days > 0
    GROUP BY collector_id
)
SELECT 
    oc.case_id,
    oc.collector_id,
    oc.current_balance,
    ca.avg_balance
FROM overdue_case oc
INNER JOIN collector_avg ca ON oc.collector_id = ca.collector_id
WHERE oc.current_balance > ca.avg_balance
ORDER BY oc.collector_id, oc.current_balance DESC;
方式 优点 适用场景
子查询(FROM 子句中) 无需额外命名,适合简单的一次性计算 子查询只使用一次,逻辑简单
CTE(WITH 子句) 可读性强,可被多次引用,支持递归 复杂查询、需要多次引用中间结果、递归查询(如组织层级)
标量子查询(SELECT 子句中) 直接在 SELECT 中计算单值 需要为每行计算一个关联的标量值
性能建议: 在催收大数据场景下,CTE 通常比多层嵌套子查询性能更好,因为数据库可以优化 CTE 的执行计划。如果 CTE 被多次引用,数据库可能只执行一次并缓存结果。但要注意,并非所有数据库引擎都支持 CTE 的物化(Materialization),建议在实际环境中测试执行计划。

2.6 综合实战:构建催收特征基础宽表

将上述语法综合运用,构建一个用于催收模型训练的客户特征宽表。该宽表将包含客户基本信息、逾期行为特征、历史还款特征和催收交互特征。

WITH 
-- 1. 客户基础信息
customer_base AS (
    SELECT 
        customer_id,
        customer_name,
        credit_score,
        income_level,
        city,
        registration_date
    FROM customer_info
    WHERE is_active = 1
),
-- 2. 逾期行为特征
overdue_features AS (
    SELECT 
        customer_id,
        COUNT(case_id) AS total_overdue_cases,
        MAX(overdue_days) AS max_overdue_days,
        AVG(overdue_days) AS avg_overdue_days,
        SUM(current_balance) AS total_overdue_balance,
        AVG(current_balance) AS avg_overdue_balance
    FROM overdue_case
    WHERE case_status IN ('待催收', '催收中')
    GROUP BY customer_id
),
-- 3. 历史还款特征
repayment_features AS (
    SELECT 
        customer_id,
        COUNT(repayment_id) AS total_repayment_count,
        SUM(repayment_amount) AS total_repayment_amount,
        AVG(repayment_amount) AS avg_repayment_amount,
        MAX(repayment_date) AS last_repayment_date,
        DATEDIFF(CURRENT_DATE, MAX(repayment_date)) AS days_since_last_repayment
    FROM repayment_record
    GROUP BY customer_id
)
-- 4. 关联生成特征宽表
SELECT 
    cb.customer_id,
    cb.customer_name,
    cb.credit_score,
    cb.income_level,
    cb.city,
    COALESCE(of.total_overdue_cases, 0) AS total_overdue_cases,
    COALESCE(of.max_overdue_days, 0) AS max_overdue_days,
    COALESCE(of.avg_overdue_days, 0) AS avg_overdue_days,
    COALESCE(of.total_overdue_balance, 0) AS total_overdue_balance,
    COALESCE(rf.total_repayment_count, 0) AS total_repayment_count,
    COALESCE(rf.total_repayment_amount, 0) AS total_repayment_amount,
    rf.days_since_last_repayment
FROM customer_base cb
LEFT JOIN overdue_features of ON cb.customer_id = of.customer_id
LEFT JOIN repayment_features rf ON cb.customer_id = rf.customer_id
WHERE of.total_overdue_cases IS NOT NULL  -- 只保留有逾期记录的客户
ORDER BY total_overdue_balance DESC;
注意事项: 在构建特征宽表时,务必注意 NULL 值的处理。使用 COALESCE 函数将 NULL 替换为 0 或其他默认值,避免模型训练时出现缺失值错误。同时,LEFT JOIN 后要确认关联键是否唯一,避免因一对多关系导致数据膨胀(例如一个客户有多个案件,关联后行数会翻倍)。

2.7 本章小结

本节系统回顾了SQL核心语法在催收数据中的具体应用:

  • SELECT + WHERE:基础数据提取与条件过滤,注意索引优化和NULL处理
  • GROUP BY + HAVING:分组聚合统计,用于计算催收员绩效、逾期区间分布等
  • ORDER BY:结果排序,常用于风险排序和优先级管理
  • JOIN:多表关联,构建特征宽表的核心手段,注意关联键的唯一性
  • 子查询与CTE:组织复杂查询,提升可读性和维护性