3. SQL 基础回顾:SELECT、WHERE、JOIN、GROUP BY、HAVING、ORDER BY 核心语法精讲
在保险精算坏样本归因分析中,SQL 是提取、清洗和聚合数据的核心工具。本章将系统回顾六大核心子句的语法与执行逻辑,并结合精算场景(如保单表、理赔表、费用表)进行实战演示。掌握这些基础,是后续构建复杂归因查询的基石。
3.1 核心子句执行顺序与逻辑模型
SQL 语句的书写顺序与数据库引擎的实际执行顺序不同。理解这一差异,能帮助精算师更准确地编写查询,避免因逻辑误解导致的归因偏差。
| 书写顺序 | 子句 | 执行顺序 | 核心作用 |
|---|---|---|---|
| 1 | SELECT | 5 | 投影:选择需要的列,计算表达式 |
| 2 | FROM | 1 | 确定数据源表,执行 JOIN 生成虚拟表 |
| 3 | WHERE | 2 | 行过滤:对 FROM 结果逐行筛选 |
| 4 | GROUP BY | 3 | 分组:将数据按列值聚合为组 |
| 5 | HAVING | 4 | 组过滤:对 GROUP BY 后的组进行筛选 |
| 6 | ORDER BY | 6 | 排序:对最终结果集进行排序 |
精算提示: 在坏样本归因中,
WHERE 用于过滤“正常样本”,HAVING 用于筛选“异常聚合组”。例如:先用 WHERE claim_amount > 0 排除无理赔记录,再用 HAVING COUNT(*) > 100 找出高频理赔组。
3.2 SELECT & FROM:数据投影与源表关联
SELECT 用于指定输出列,FROM 用于指定数据源。在精算归因中,通常需要从多张表(如保单表 policy、理赔表 claim、费用表 expense)中提取字段。
-- 基础查询:从保单表提取关键字段
SELECT
policy_id,
product_code,
underwrite_date,
premium_amount
FROM policy
WHERE status = '有效';
归因实战要点:
- 使用
SELECT DISTINCT去重,识别唯一产品/渠道组合。 - 使用
SELECT TOP 100(SQL Server)或LIMIT 100(MySQL/PostgreSQL)快速预览数据。 - 避免
SELECT *,显式列出字段可提升可读性与性能。
3.3 WHERE:行级过滤——定位坏样本候选集
WHERE 在分组前对每一行进行过滤,是坏样本归因的第一步。精算场景中常见的过滤条件包括:
- 时间窗口:
WHERE claim_date BETWEEN '2024-01-01' AND '2024-12-31' - 异常值排除:
WHERE premium_amount > 0 AND claim_amount < 1000000 - 业务状态:
WHERE policy_status = '有效' AND cancel_flag = 'N' - 坏样本标记:
WHERE loss_ratio > 0.8 OR fraud_score > 0.7
-- 示例:筛选出2024年高赔付率保单
SELECT
policy_id,
product_code,
SUM(premium) AS total_premium,
SUM(claim_paid) AS total_claim
FROM policy p
JOIN claim c ON p.policy_id = c.policy_id
WHERE p.underwrite_year = 2024
AND c.claim_status = '已决'
AND p.premium > 0
GROUP BY policy_id, product_code
HAVING (SUM(claim_paid) / SUM(premium)) > 0.8;
常见陷阱:
WHERE 中不能使用聚合函数(如 SUM、AVG)。若需对聚合结果过滤,必须使用 HAVING。例如:WHERE SUM(claim_amount) > 10000 是错误的,应改为 HAVING SUM(claim_amount) > 10000。
3.4 JOIN:多表关联——构建归因宽表
坏样本归因通常需要将保单、理赔、费用、代理人等多张表关联成一张宽表。JOIN 类型的选择直接影响归因结果的完整性。
| JOIN 类型 | 返回行数 | 精算应用场景 |
|---|---|---|
| INNER JOIN | 仅匹配行 | 仅分析既有保单又有理赔的记录(如已决赔案) |
| LEFT JOIN | 左表全部 + 右表匹配 | 保留所有保单,无理赔的保单用 NULL 填充(如零赔案分析) |
| RIGHT JOIN | 右表全部 + 左表匹配 | 保留所有理赔记录,即使保单已失效(如欺诈理赔追溯) |
| FULL OUTER JOIN | 两表全部 | 保单与理赔全量对账,发现孤儿保单或幽灵理赔 |
-- 示例:LEFT JOIN 保留所有保单,关联理赔与费用
SELECT
p.policy_id,
p.product_code,
p.premium,
COALESCE(SUM(c.claim_paid), 0) AS total_claim,
COALESCE(SUM(e.acq_fee + e.maint_fee), 0) AS total_expense
FROM policy p
LEFT JOIN claim c ON p.policy_id = c.policy_id
LEFT JOIN expense e ON p.policy_id = e.policy_id
WHERE p.status = '有效'
GROUP BY p.policy_id, p.product_code, p.premium;
3.5 GROUP BY & HAVING:分组聚合与组级过滤
GROUP BY 将数据按一个或多个维度分组,HAVING 对分组后的聚合结果进行过滤。这是坏样本归因的核心环节,用于识别异常聚合单元(如高赔付率产品线、高退保率渠道)。
- 常用聚合函数:
COUNT()、SUM()、AVG()、MAX()、MIN() - 分组维度: 产品代码、销售渠道、地区、核保月份、代理人等级
- HAVING 条件: 赔付率 > 行业均值 + 2σ、退保率 > 20%、件均理赔额异常
-- 示例:按产品与渠道分组,筛选坏样本集中组
SELECT
product_code,
channel_code,
COUNT(DISTINCT policy_id) AS policy_cnt,
SUM(claim_paid) / NULLIF(SUM(premium), 0) AS loss_ratio,
AVG(claim_paid) AS avg_claim
FROM policy p
JOIN claim c ON p.policy_id = c.policy_id
WHERE p.underwrite_date >= '2024-01-01'
GROUP BY product_code, channel_code
HAVING COUNT(DISTINCT policy_id) >= 50
AND (SUM(claim_paid) / NULLIF(SUM(premium), 0)) > 0.7
ORDER BY loss_ratio DESC;
精算技巧: 使用
NULLIF(denominator, 0) 避免除零错误。在 HAVING 中优先使用聚合后的指标(如赔付率),而非原始字段,因为 HAVING 是在分组后执行的。
3.6 ORDER BY:排序——定位归因根因
ORDER BY 对最终结果集进行排序,帮助精算师快速定位贡献度最高或异常最严重的维度。排序方向:
ASC(默认):升序,适用于找出最小赔付率、最低保费等。DESC:降序,适用于找出最大坏样本贡献、最高赔付率等。
-- 示例:按坏样本贡献度降序排列,定位 Top 10 根因产品
SELECT
product_code,
COUNT(DISTINCT policy_id) AS bad_policy_cnt,
SUM(claim_paid) AS total_bad_loss,
ROUND(SUM(claim_paid) * 1.0 / SUM(SUM(claim_paid)) OVER(), 4) AS loss_contribution_pct
FROM policy p
JOIN claim c ON p.policy_id = c.policy_id
WHERE p.loss_ratio_flag = 'Y'
GROUP BY product_code
ORDER BY total_bad_loss DESC
LIMIT 10;
3.7 综合实战:坏样本归因查询模板
以下是一个完整的坏样本归因查询模板,整合了本章所有核心子句。该模板可直接用于保险精算日常分析。
-- 坏样本归因查询模板
SELECT
a.product_code,
a.channel_code,
a.region_code,
COUNT(DISTINCT a.policy_id) AS total_policies,
SUM(a.claim_paid) AS total_loss,
SUM(a.premium) AS total_premium,
ROUND(SUM(a.claim_paid) / NULLIF(SUM(a.premium), 0), 4) AS loss_ratio,
ROUND(SUM(a.claim_paid) * 1.0 / SUM(SUM(a.claim_paid)) OVER(), 4) AS loss_contribution_pct
FROM (
SELECT
p.policy_id,
p.product_code,
p.channel_code,
p.region_code,
p.premium,
COALESCE(c.claim_paid, 0) AS claim_paid
FROM policy p
LEFT JOIN claim c ON p.policy_id = c.policy_id
WHERE p.status = '有效'
AND p.underwrite_date >= '2024-01-01'
) a
GROUP BY a.product_code, a.channel_code, a.region_code
HAVING COUNT(DISTINCT a.policy_id) >= 30
AND (SUM(a.claim_paid) / NULLIF(SUM(a.premium), 0)) > 0.6
ORDER BY loss_contribution_pct DESC;
模板使用说明:
- 子查询(FROM 子句内): 先完成多表关联与行级过滤,生成干净的宽表。
- GROUP BY 维度: 按产品、渠道、地区三维分组,可根据业务需要增减。
- HAVING 条件: 要求组内保单数 ≥ 30(统计显著性),赔付率 > 60%(坏样本阈值)。
- ORDER BY: 按损失贡献度降序排列,直接定位 Top 根因。
3.8 本章小结
本章系统回顾了 SQL 六大核心子句的语法与执行逻辑,并结合保险精算坏样本归因场景进行了实战演示。关键要点:
- 执行顺序:FROM → WHERE → GROUP BY → HAVING → SELECT → ORDER BY。
- WHERE 用于行级过滤,HAVING 用于组级过滤,不可混用。
- JOIN 类型选择影响归因完整性,LEFT JOIN 常用于保留全量保单。
- GROUP BY + HAVING + ORDER BY 是坏样本归因的核心三件套。
- 使用
NULLIF避免除零,使用窗口函数计算贡献度。