3. SQL环境搭建:数据库选型、金融数据集导入与窗口/聚合函数基础
在开始组合风险归因与决策优化之前,必须搭建一个稳定、高效的SQL分析环境。本章将指导你完成数据库选型、金融数据集导入,并夯实窗口函数与聚合函数的基础——这是后续所有风险计算与归因分析的基石。
3.1 数据库选型:PostgreSQL vs MySQL
金融风控场景对SQL引擎的要求集中在:窗口函数支持度、复杂聚合性能、CTE(公用表表达式)递归能力以及数值精度。以下是两大主流开源数据库的对比:
| 对比维度 | PostgreSQL | MySQL |
|---|---|---|
| 窗口函数支持 | 完整支持(RANK、DENSE_RANK、LAG、LEAD、FIRST_VALUE、LAST_VALUE、NTILE等) | 8.0+ 支持基础窗口函数,但部分高级功能(如FILTER子句)缺失 |
| CTE递归 | 原生支持递归CTE,适合层级归因与树状结构计算 | 8.0+ 支持递归CTE,但性能优化不如PG |
| 数值精度 | 支持NUMERIC/DECIMAL高精度类型,适合金融金额计算 | DECIMAL精度足够,但浮点运算存在隐式转换风险 |
| 并行查询 | 支持并行顺序扫描、并行聚合、并行JOIN | 并行能力较弱,大表聚合易成瓶颈 |
| 扩展性 | 支持自定义聚合函数、扩展插件(如pg_stat_statements) | 扩展能力有限,依赖存储引擎 |
| 金融风控推荐 | ⭐⭐⭐⭐⭐(强烈推荐) | ⭐⭐⭐(适合轻量级场景) |
选型建议:本课程所有实战案例均基于 PostgreSQL 15+ 构建。如果你使用MySQL 8.0+,大部分窗口函数语法兼容,但部分高级归因计算(如递归层级归因)需要改写为存储过程或应用层实现。
3.2 金融数据集导入
我们将使用一个模拟的投资组合风险数据集,包含以下核心表:
- portfolio_holdings — 持仓明细(证券代码、持仓数量、成本价、市值)
- security_master — 证券主表(行业分类、板块、风险因子暴露)
- daily_returns — 日收益率序列(日期、证券代码、收益率)
- risk_factors — 风险因子数据(因子名称、因子收益率、协方差矩阵)
以下为建表与导入示例(PostgreSQL语法):
-- 创建持仓表
CREATE TABLE portfolio_holdings (
holding_id SERIAL PRIMARY KEY,
security_code VARCHAR(20) NOT NULL,
holding_date DATE NOT NULL,
quantity NUMERIC(18,4),
cost_price NUMERIC(18,4),
market_value NUMERIC(18,4),
portfolio_id VARCHAR(20) DEFAULT 'PF_MAIN'
);
-- 创建证券主表
CREATE TABLE security_master (
security_code VARCHAR(20) PRIMARY KEY,
security_name VARCHAR(100),
industry VARCHAR(50),
sector VARCHAR(50),
beta NUMERIC(10,4),
market_cap NUMERIC(18,2)
);
-- 创建日收益率表
CREATE TABLE daily_returns (
return_id SERIAL PRIMARY KEY,
security_code VARCHAR(20) NOT NULL,
return_date DATE NOT NULL,
daily_return NUMERIC(10,6),
UNIQUE(security_code, return_date)
);
-- 导入CSV数据(示例路径)
COPY portfolio_holdings(security_code, holding_date, quantity, cost_price, market_value)
FROM '/data/portfolio_holdings.csv'
DELIMITER ',' CSV HEADER;
COPY security_master(security_code, security_name, industry, sector, beta, market_cap)
FROM '/data/security_master.csv'
DELIMITER ',' CSV HEADER;
COPY daily_returns(security_code, return_date, daily_return)
FROM '/data/daily_returns.csv'
DELIMITER ',' CSV HEADER;
数据导入注意事项:
- 金融数据中的金额字段建议使用
NUMERIC(18,4)或NUMERIC(18,2),避免浮点精度丢失。 - 日期字段统一使用
DATE类型,不要使用字符串存储。 - 导入前检查CSV文件编码(推荐UTF-8),避免中文乱码。
- 为
security_code + return_date建立唯一索引,防止重复数据。
3.3 聚合函数基础
聚合函数是风险归因的计算核心。以下为金融风控中最常用的聚合函数及其应用场景:
| 聚合函数 | 语法示例 | 风控应用场景 |
|---|---|---|
| SUM | SUM(market_value) |
计算组合总市值、行业总暴露 |
| AVG | AVG(daily_return) |
计算平均日收益率、平均风险因子暴露 |
| COUNT | COUNT(DISTINCT security_code) |
统计持仓证券数量、行业数量 |
| MAX / MIN | MAX(daily_return) |
识别最大/最小日收益率、极端风险事件 |
| STDDEV | STDDEV(daily_return) |
计算波动率(风险度量核心指标) |
| PERCENTILE_CONT | PERCENTILE_CONT(0.05) WITHIN GROUP (ORDER BY daily_return) |
计算VaR(风险价值)的分位数 |
实战示例:计算每个行业的持仓市值占比与波动率
SELECT
sm.industry,
SUM(ph.market_value) AS total_market_value,
ROUND(SUM(ph.market_value) / SUM(SUM(ph.market_value)) OVER () * 100, 2) AS weight_pct,
ROUND(STDDEV(dr.daily_return) * SQRT(252), 4) AS annualized_volatility
FROM portfolio_holdings ph
JOIN security_master sm ON ph.security_code = sm.security_code
JOIN daily_returns dr ON ph.security_code = dr.security_code
WHERE dr.return_date BETWEEN '2024-01-01' AND '2024-12-31'
GROUP BY sm.industry
ORDER BY total_market_value DESC;
3.4 窗口函数基础
窗口函数是组合风险归因的核心工具。与聚合函数不同,窗口函数不减少行数,而是在每一行上计算一个基于窗口范围的值。以下为三大类窗口函数:
- 排序窗口函数:ROW_NUMBER、RANK、DENSE_RANK、NTILE — 用于风险排序、分位数划分
- 偏移窗口函数:LAG、LEAD — 用于计算收益率变化、风险暴露偏移
- 聚合窗口函数:SUM() OVER、AVG() OVER、STDDEV() OVER — 用于滚动计算、累计归因
语法结构:
窗口函数 OVER (
[PARTITION BY 分组列]
[ORDER BY 排序列]
[ROWS/RANGE BETWEEN 起始行 AND 结束行]
)
实战示例:计算每只证券相对于其行业的累计收益率排名与滚动波动率
SELECT
dr.security_code,
sm.industry,
dr.return_date,
dr.daily_return,
-- 行业内的累计收益率排名(按日期排序)
SUM(dr.daily_return) OVER (
PARTITION BY dr.security_code
ORDER BY dr.return_date
ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW
) AS cumulative_return,
-- 行业内的波动率排名(按波动率降序)
RANK() OVER (
PARTITION BY sm.industry
ORDER BY STDDEV(dr.daily_return) OVER (
PARTITION BY dr.security_code
ORDER BY dr.return_date
ROWS BETWEEN 20 PRECEDING AND 1 PRECEDING
) DESC
) AS vol_rank_in_industry,
-- 前一日收益率(用于计算日间变化)
LAG(dr.daily_return, 1) OVER (
PARTITION BY dr.security_code
ORDER BY dr.return_date
) AS prev_day_return
FROM daily_returns dr
JOIN security_master sm ON dr.security_code = sm.security_code
WHERE dr.return_date BETWEEN '2024-01-01' AND '2024-12-31'
ORDER BY dr.security_code, dr.return_date;
窗口函数 vs 聚合函数核心区别:
- 聚合函数 + GROUP BY → 输出行数 = 分组数(数据压缩)
- 窗口函数 + OVER → 输出行数 = 原表行数(保留明细)
- 在风险归因中,窗口函数可以同时保留个体风险和组合风险信息,这是后续边际风险贡献计算的基础。
3.5 环境搭建验证与SVG架构图
完成数据库选型、建表与数据导入后,建议执行以下验证脚本:
-- 验证1:检查数据完整性
SELECT '持仓记录数' AS check_item, COUNT(*) AS cnt FROM portfolio_holdings
UNION ALL
SELECT '证券主表记录数', COUNT(*) FROM security_master
UNION ALL
SELECT '日收益率记录数', COUNT(*) FROM daily_returns;
-- 验证2:检查日期范围
SELECT MIN(return_date) AS start_date, MAX(return_date) AS end_date FROM daily_returns;
-- 验证3:检查窗口函数是否可用
SELECT security_code, return_date, daily_return,
SUM(daily_return) OVER (ORDER BY return_date ROWS BETWEEN 5 PRECEDING AND CURRENT ROW) AS rolling_5d_return
FROM daily_returns
WHERE security_code = 'SEC001'
LIMIT 10;
以下为本章的SQL环境搭建与数据流架构图,展示从原始数据到风险归因计算的全链路:
3.6 常见问题与避坑指南
| 问题场景 | 错误示例 | 正确做法 |
|---|---|---|
| 窗口函数与GROUP BY混用 | SELECT industry, SUM(market_value), RANK() OVER(ORDER BY SUM(market_value)) FROM t GROUP BY industry |
先聚合再窗口:使用子查询或CTE将聚合结果作为窗口函数的输入 |
| 窗口函数ORDER BY方向错误 | SUM(return) OVER(ORDER BY date DESC) 导致累计逻辑混乱 |
明确时间顺序:ORDER BY date ASC 配合 ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW |
| 金融金额使用FLOAT类型 | market_value FLOAT 导致精度丢失 |
使用 NUMERIC(18,4) 或 DECIMAL(18,4) |
| 忽略NULL值处理 | AVG(daily_return) 包含NULL导致结果偏差 |
使用 COALESCE(daily_return, 0) 或 AVG(daily_return) FILTER(WHERE daily_return IS NOT NULL) |
本章小结:你已经完成了SQL风险分析环境的搭建,掌握了聚合函数与窗口函数的核心语法。在下一阶段(第4章),我们将基于这些基础,构建组合风险因子暴露矩阵,并开始进行多因子归因计算。请确保当前环境中的窗口函数可以正常运行上述示例代码。