3. SQL基础回顾:SELECT、WHERE、GROUP BY、HAVING、ORDER BY语法回顾,以及反洗钱数据表结构示例
在进入反洗钱特征工程之前,必须牢固掌握SQL核心查询语法。本章将系统回顾SELECT、WHERE、GROUP BY、HAVING、ORDER BY五大子句的执行逻辑与书写顺序,并结合反洗钱场景下的交易表(TRANSACTIONS)和客户表(CUSTOMERS)进行实战演示。
3.1 反洗钱数据表结构示例
以下两张表是后续所有SQL练习的基础。请熟记字段名称与数据类型。
3.1.1 交易表(TRANSACTIONS)
| 字段名 | 数据类型 | 说明 |
|---|---|---|
| TX_ID | VARCHAR(20) | 交易流水号(主键) |
| CUST_ID | VARCHAR(10) | 客户编号(外键→CUSTOMERS) |
| TX_AMOUNT | DECIMAL(18,2) | 交易金额(人民币) |
| TX_DATETIME | DATETIME | 交易发生时间 |
| TX_TYPE | VARCHAR(10) | 交易类型:转账/取现/消费 |
| COUNTERPARTY | VARCHAR(10) | 对手方客户编号 |
| CHANNEL | VARCHAR(10) | 渠道:网银/柜面/ATM |
3.1.2 客户表(CUSTOMERS)
| 字段名 | 数据类型 | 说明 |
|---|---|---|
| CUST_ID | VARCHAR(10) | 客户编号(主键) |
| CUST_NAME | VARCHAR(50) | 客户姓名 |
| ID_TYPE | VARCHAR(5) | 证件类型:身份证/护照 |
| ID_NUMBER | VARCHAR(20) | 证件号码 |
| RISK_LEVEL | VARCHAR(5) | 风险等级:高/中/低 |
| OPEN_DATE | DATE | 开户日期 |
提示:实际反洗钱系统中交易表通常包含数十个字段,此处为教学需要做了精简。重点掌握CUST_ID、TX_AMOUNT、TX_DATETIME、TX_TYPE这四个核心字段。
3.2 SQL五大子句执行逻辑
SQL语句的书写顺序与执行顺序不同,这是初学者最容易出错的地方。下图清晰展示了二者的区别。
关键记忆点:WHERE在GROUP BY之前执行,因此WHERE中不能使用聚合函数;HAVING在GROUP BY之后执行,专门用于过滤聚合后的结果。ORDER BY在SELECT之后执行,因此可以使用SELECT中定义的别名。
3.3 各子句详解与反洗钱示例
3.3.1 SELECT — 投影与计算
SELECT用于指定要返回的列,支持字段、表达式、别名和聚合函数。
-- 基础投影:查询交易表中所有字段
SELECT * FROM TRANSACTIONS;
-- 选择特定字段并重命名
SELECT TX_ID AS 交易流水号, TX_AMOUNT AS 金额, TX_DATETIME AS 交易时间
FROM TRANSACTIONS;
-- 使用表达式:将金额转换为万元单位
SELECT TX_ID, ROUND(TX_AMOUNT / 10000, 2) AS 金额_万元
FROM TRANSACTIONS;
-- 聚合函数:统计总交易笔数和总金额
SELECT COUNT(*) AS 总笔数, SUM(TX_AMOUNT) AS 总金额
FROM TRANSACTIONS;
3.3.2 WHERE — 行级过滤
WHERE在数据分组之前过滤行,支持比较运算符、逻辑运算符和IN/BETWEEN/LIKE等特殊条件。
-- 过滤大额交易(单笔≥5万元)
SELECT * FROM TRANSACTIONS
WHERE TX_AMOUNT >= 50000;
-- 多条件组合:高风险客户的大额取现
SELECT T.*
FROM TRANSACTIONS T
JOIN CUSTOMERS C ON T.CUST_ID = C.CUST_ID
WHERE C.RISK_LEVEL = '高'
AND T.TX_TYPE = '取现'
AND T.TX_AMOUNT >= 20000;
-- 时间范围过滤:近30天交易
SELECT * FROM TRANSACTIONS
WHERE TX_DATETIME >= DATEADD(DAY, -30, GETDATE());
-- 排除特定渠道
SELECT * FROM TRANSACTIONS
WHERE CHANNEL NOT IN ('ATM', '网银');
反洗钱常见陷阱:WHERE中不能使用聚合函数(如SUM、COUNT)。如果需要过滤聚合结果,必须使用HAVING子句。例如“查询总交易金额超过100万的客户”必须用HAVING,而不能用WHERE。
3.3.3 GROUP BY — 分组聚合
GROUP BY将数据按一个或多个列分组,配合聚合函数计算每组的统计值。这是反洗钱特征计算的核心操作。
-- 按客户分组:计算每个客户的交易总金额和笔数
SELECT CUST_ID,
COUNT(*) AS 交易笔数,
SUM(TX_AMOUNT) AS 交易总金额,
AVG(TX_AMOUNT) AS 平均单笔金额
FROM TRANSACTIONS
GROUP BY CUST_ID;
-- 多字段分组:按客户和交易类型分组
SELECT CUST_ID,
TX_TYPE,
COUNT(*) AS 笔数,
SUM(TX_AMOUNT) AS 金额合计
FROM TRANSACTIONS
GROUP BY CUST_ID, TX_TYPE
ORDER BY CUST_ID, TX_TYPE;
3.3.4 HAVING — 组级过滤
HAVING在GROUP BY之后执行,用于过滤分组后的聚合结果。HAVING中可以使用聚合函数,这是与WHERE最大的区别。
-- 筛选交易总金额超过100万的客户
SELECT CUST_ID,
SUM(TX_AMOUNT) AS 交易总金额
FROM TRANSACTIONS
GROUP BY CUST_ID
HAVING SUM(TX_AMOUNT) > 1000000;
-- 筛选平均单笔金额超过5万且交易笔数≥3的客户
SELECT CUST_ID,
COUNT(*) AS 笔数,
AVG(TX_AMOUNT) AS 平均金额
FROM TRANSACTIONS
GROUP BY CUST_ID
HAVING AVG(TX_AMOUNT) > 50000
AND COUNT(*) >= 3;
3.3.5 ORDER BY — 排序输出
ORDER BY在SELECT之后执行,可以对最终结果集按一个或多个列排序,支持ASC(升序,默认)和DESC(降序)。
-- 按交易金额降序排列,查看最大金额交易
SELECT * FROM TRANSACTIONS
ORDER BY TX_AMOUNT DESC;
-- 多级排序:先按客户升序,再按交易时间降序
SELECT * FROM TRANSACTIONS
ORDER BY CUST_ID ASC, TX_DATETIME DESC;
-- 结合聚合结果排序:按交易总金额降序排列客户
SELECT CUST_ID,
SUM(TX_AMOUNT) AS 交易总金额
FROM TRANSACTIONS
GROUP BY CUST_ID
ORDER BY 交易总金额 DESC;
技巧:ORDER BY中可以使用SELECT中定义的列别名(如上面的“交易总金额”),因为ORDER BY在SELECT之后执行。但WHERE和GROUP BY中不能使用别名,因为它们先于SELECT执行。
3.4 完整语法模板与执行流程
将五大子句组合在一起,形成完整的SQL查询模板。请牢记以下书写顺序:
SELECT -- ⑤ 投影列、聚合函数、表达式
FROM -- ① 数据源(表/视图/子查询)
WHERE -- ② 行级过滤条件(不能含聚合函数)
GROUP BY -- ③ 分组字段
HAVING -- ④ 组级过滤条件(可以含聚合函数)
ORDER BY -- ⑥ 排序字段(可以使用别名)
;
下面是一个完整的反洗钱特征查询示例:
-- 查询:高风险客户中,近90天交易总金额超过200万且交易笔数≥5的客户
-- 按交易总金额降序排列
SELECT C.CUST_ID,
C.CUST_NAME,
COUNT(T.TX_ID) AS 交易笔数,
SUM(T.TX_AMOUNT) AS 交易总金额,
AVG(T.TX_AMOUNT) AS 平均单笔金额
FROM TRANSACTIONS T
JOIN CUSTOMERS C ON T.CUST_ID = C.CUST_ID
WHERE C.RISK_LEVEL = '高'
AND T.TX_DATETIME >= DATEADD(DAY, -90, GETDATE())
GROUP BY C.CUST_ID, C.CUST_NAME
HAVING SUM(T.TX_AMOUNT) > 2000000
AND COUNT(T.TX_ID) >= 5
ORDER BY 交易总金额 DESC;
3.5 常见错误与避坑指南
| 错误类型 | 错误示例 | 正确写法 | 说明 |
|---|---|---|---|
| WHERE中使用聚合函数 | WHERE SUM(AMOUNT) > 100 | HAVING SUM(AMOUNT) > 100 | 聚合函数只能在HAVING或SELECT中使用 |
| SELECT中非聚合列未包含在GROUP BY中 | SELECT CUST_ID, TX_TYPE, SUM(AMOUNT) … GROUP BY CUST_ID | GROUP BY CUST_ID, TX_TYPE | SELECT中所有非聚合列必须出现在GROUP BY中 |
| WHERE中使用列别名 | WHERE 金额 > 10000 | WHERE TX_AMOUNT > 10000 | WHERE执行时别名尚未定义 |
| HAVING与WHERE混淆 | HAVING TX_AMOUNT > 50000 | WHERE TX_AMOUNT > 50000 | 非聚合条件应放在WHERE中提高效率 |
最佳实践:在编写反洗钱特征SQL时,先写FROM和WHERE过滤原始数据,再写GROUP BY分组,接着用HAVING过滤分组结果,最后用SELECT投影和ORDER BY排序。按照这个逻辑顺序书写,能大幅减少语法错误。