未记录事实核验 · 状态:维护中
记录仅说明核验时间与维护状态,不保证适用于所有环境;使用前请留意正文中的版本、适用条件与风险提示。
warning 适用边界与维护说明
本文适合有Oracle与数仓开发基础的读者讨论银行ODS、DW、DM分层、维度建模及集市ETL,不是可独立跑通或直接部署的生产方案。源表与数据库链接、ETL日志表、职业分类函数、产品维度及事实加载逻辑未完整提供;当前增量抽取水位与日志事务、客户SCD Type 2版本处理、月度余额聚合及物化视图快速刷新配置仍有具体缺陷,不能据此认定数据完整、指标准确或快速刷新可用。日期维度插入列值已匹配,但周末判断依赖NLS_TERRITORY,节假日和调休需另行维护;财务年月按自然年演示,分区上界之外的数据需扩展分区。客户字段应使用脱敏或合成数据,评分与生命周期价值公式仅为假设规则,不应用于银行生产风险决策或经营估值,正文也未提供完整生产安全与合规设计。本次仅作全文结构与代码静态审阅,未作外部事实核验或运行验证。
Oracle银行数仓架构设计
银行数据仓库概述
银行数据仓库是支撑银行业务决策、风险管控、监管报告的核心基础设施。Oracle作为银行业主流数据库平台,在构建企业级数据仓库方面有着丰富的技术支撑和最佳实践。
银行数仓整体架构
1. 分层架构设计
ODS层 (Operational Data Store)
- 数据源头:核心银行系统、信贷系统、风控系统
- 数据特点:接近源系统结构、最小化转换
- 更新频率:实时或准实时
DW层 (Data Warehouse)
- 数据模型:维度建模,星型/雪花模型
- 数据特点:清洗、整合、标准化
- 更新频率:日/小时批量
DM层 (Data Mart)
- 面向主题:客户、产品、渠道、风险
- 数据特点:聚合、汇总、预计算
- 更新频率:日/周/月
2. Oracle架构组件选择
-- 创建表空间规划示例
-- ODS层表空间
CREATE TABLESPACE ODS_DATA
DATAFILE '/u01/oradata/bankdw/ods_data01.dbf' SIZE 10G
AUTOEXTEND ON NEXT 1G MAXSIZE 50G;
-- DW层表空间
CREATE TABLESPACE DW_DATA
DATAFILE '/u01/oradata/bankdw/dw_data01.dbf' SIZE 20G
AUTOEXTEND ON NEXT 2G MAXSIZE 100G;
-- DM层表空间
CREATE TABLESPACE DM_DATA
DATAFILE '/u01/oradata/bankdw/dm_data01.dbf' SIZE 5G
AUTOEXTEND ON NEXT 500M MAXSIZE 20G;
-- 索引表空间
CREATE TABLESPACE DW_INDEX
DATAFILE '/u01/oradata/bankdw/dw_index01.dbf' SIZE 10G
AUTOEXTEND ON NEXT 1G MAXSIZE 30G;
ODS层设计实现
1. 源系统数据接入
核心银行系统客户表
CREATE TABLE ods_customer_info (
customer_id VARCHAR2(20) NOT NULL,
customer_name VARCHAR2(100),
id_card VARCHAR2(18),
phone VARCHAR2(11),
email VARCHAR2(100),
birth_date DATE,
gender CHAR(1),
address VARCHAR2(200),
postal_code VARCHAR2(6),
education_level VARCHAR2(20),
occupation VARCHAR2(50),
annual_income NUMBER(15,2),
create_time DATE,
update_time DATE,
source_system VARCHAR2(20) DEFAULT 'CORE_BANK',
etl_batch_id VARCHAR2(20),
etl_time DATE DEFAULT SYSDATE
) TABLESPACE ODS_DATA;
-- 创建分区表优化查询性能
CREATE TABLE ods_transaction_log (
trans_id VARCHAR2(30) NOT NULL,
account_id VARCHAR2(20),
trans_type VARCHAR2(10),
amount NUMBER(15,2),
balance_after NUMBER(15,2),
trans_time DATE,
channel_code VARCHAR2(10),
branch_code VARCHAR2(10),
teller_id VARCHAR2(20),
description VARCHAR2(200),
source_system VARCHAR2(20),
etl_batch_id VARCHAR2(20),
etl_time DATE
)
PARTITION BY RANGE (trans_time) (
PARTITION p_202301 VALUES LESS THAN (DATE '2023-02-01'),
PARTITION p_202302 VALUES LESS THAN (DATE '2023-03-01'),
PARTITION p_202303 VALUES LESS THAN (DATE '2023-04-01')
) TABLESPACE ODS_DATA;
2. 数据增量抽取策略
-- 增量抽取存储过程
CREATE OR REPLACE PROCEDURE extract_customer_incremental(
p_extract_date IN DATE DEFAULT SYSDATE
)
AS
v_last_extract_time DATE;
v_current_batch_id VARCHAR2(20);
v_extracted_count NUMBER := 0;
BEGIN
-- 生成批次ID
v_current_batch_id := 'CUST_' || TO_CHAR(p_extract_date, 'YYYYMMDDHH24MISS');
-- 获取上次抽取时间
SELECT MAX(etl_time) INTO v_last_extract_time
FROM ods_customer_info
WHERE source_system = 'CORE_BANK';
-- 如果是首次抽取,则抽取所有数据
IF v_last_extract_time IS NULL THEN
v_last_extract_time := DATE '1900-01-01';
END IF;
-- 从源系统抽取增量数据
INSERT INTO ods_customer_info (
customer_id, customer_name, id_card, phone, email,
birth_date, gender, address, postal_code, education_level,
occupation, annual_income, create_time, update_time,
source_system, etl_batch_id, etl_time
)
SELECT customer_id, customer_name, id_card, phone, email,
birth_date, gender, address, postal_code, education_level,
occupation, annual_income, create_time, update_time,
'CORE_BANK', v_current_batch_id, SYSDATE
FROM core_bank.customer_info@core_bank_link
WHERE update_time > v_last_extract_time;
v_extracted_count := SQL%ROWCOUNT;
COMMIT;
-- 记录抽取日志
INSERT INTO etl_log (
batch_id, table_name, extract_type, record_count,
start_time, end_time, status
) VALUES (
v_current_batch_id, 'ods_customer_info', 'INCREMENTAL',
v_extracted_count, SYSDATE, SYSDATE, 'SUCCESS'
);
COMMIT;
DBMS_OUTPUT.PUT_LINE('客户信息增量抽取完成,抽取记录数:' || v_extracted_count);
EXCEPTION
WHEN OTHERS THEN
ROLLBACK;
INSERT INTO etl_log (
batch_id, table_name, extract_type, record_count,
start_time, end_time, status, error_message
) VALUES (
v_current_batch_id, 'ods_customer_info', 'INCREMENTAL',
0, SYSDATE, SYSDATE, 'FAILED', SQLERRM
);
COMMIT;
RAISE;
END extract_customer_incremental;
/
DW层维度建模
1. 客户维度表设计
-- 客户维度表
CREATE TABLE dim_customer (
customer_key NUMBER(10) NOT NULL,
customer_id VARCHAR2(20) NOT NULL,
customer_name VARCHAR2(100),
id_card VARCHAR2(18),
phone VARCHAR2(11),
email VARCHAR2(100),
birth_date DATE,
age NUMBER(3),
age_group VARCHAR2(20),
gender CHAR(1),
gender_desc VARCHAR2(10),
address VARCHAR2(200),
city VARCHAR2(50),
province VARCHAR2(50),
postal_code VARCHAR2(6),
education_level VARCHAR2(20),
education_level_desc VARCHAR2(50),
occupation VARCHAR2(50),
occupation_category VARCHAR2(30),
annual_income NUMBER(15,2),
income_level VARCHAR2(20),
customer_grade VARCHAR2(20),
vip_flag CHAR(1),
register_date DATE,
first_transaction_date DATE,
last_transaction_date DATE,
account_count NUMBER(3),
total_balance NUMBER(15,2),
is_active CHAR(1),
effective_date DATE,
expiry_date DATE,
current_flag CHAR(1),
create_time DATE,
update_time DATE
) TABLESPACE DW_DATA;
-- 创建代理键序列
CREATE SEQUENCE dim_customer_key_seq START WITH 1 INCREMENT BY 1;
-- 客户维度ETL处理
CREATE OR REPLACE PROCEDURE load_dim_customer
AS
v_processed_count NUMBER := 0;
BEGIN
-- 处理新增客户
INSERT INTO dim_customer (
customer_key, customer_id, customer_name, id_card,
phone, email, birth_date, age, age_group, gender, gender_desc,
address, city, province, postal_code, education_level,
education_level_desc, occupation, occupation_category,
annual_income, income_level, customer_grade, vip_flag,
register_date, account_count, total_balance, is_active,
effective_date, expiry_date, current_flag, create_time, update_time
)
SELECT
dim_customer_key_seq.NEXTVAL,
ods.customer_id,
ods.customer_name,
ods.id_card,
ods.phone,
ods.email,
ods.birth_date,
FLOOR(MONTHS_BETWEEN(SYSDATE, ods.birth_date) / 12) as age,
CASE
WHEN FLOOR(MONTHS_BETWEEN(SYSDATE, ods.birth_date) / 12) < 25 THEN '青年'
WHEN FLOOR(MONTHS_BETWEEN(SYSDATE, ods.birth_date) / 12) < 45 THEN '中年'
ELSE '老年'
END as age_group,
ods.gender,
CASE ods.gender WHEN 'M' THEN '男' WHEN 'F' THEN '女' ELSE '未知' END as gender_desc,
ods.address,
REGEXP_SUBSTR(ods.address, '[^省]+省?([^市]+市?)') as city,
REGEXP_SUBSTR(ods.address, '[^省]+省?') as province,
ods.postal_code,
ods.education_level,
CASE ods.education_level
WHEN 'PHD' THEN '博士'
WHEN 'MASTER' THEN '硕士'
WHEN 'BACHELOR' THEN '本科'
WHEN 'COLLEGE' THEN '大专'
WHEN 'HIGH_SCHOOL' THEN '高中'
ELSE '其他'
END as education_level_desc,
ods.occupation,
get_occupation_category(ods.occupation) as occupation_category,
ods.annual_income,
CASE
WHEN ods.annual_income >= 1000000 THEN '高收入'
WHEN ods.annual_income >= 200000 THEN '中高收入'
WHEN ods.annual_income >= 100000 THEN '中等收入'
WHEN ods.annual_income >= 50000 THEN '中低收入'
ELSE '低收入'
END as income_level,
'BRONZE' as customer_grade, -- 初始等级
'N' as vip_flag,
ods.create_time as register_date,
0 as account_count,
0 as total_balance,
'Y' as is_active,
SYSDATE as effective_date,
DATE '9999-12-31' as expiry_date,
'Y' as current_flag,
SYSDATE as create_time,
SYSDATE as update_time
FROM ods_customer_info ods
WHERE NOT EXISTS (
SELECT 1 FROM dim_customer dim
WHERE dim.customer_id = ods.customer_id
AND dim.current_flag = 'Y'
);
v_processed_count := SQL%ROWCOUNT;
-- 处理客户信息变更(SCD Type 2)
-- 先关闭旧记录
UPDATE dim_customer
SET expiry_date = SYSDATE - 1/86400,
current_flag = 'N',
update_time = SYSDATE
WHERE customer_id IN (
SELECT ods.customer_id
FROM ods_customer_info ods
JOIN dim_customer dim ON ods.customer_id = dim.customer_id
WHERE dim.current_flag = 'Y'
AND (ods.customer_name != dim.customer_name
OR ods.phone != dim.phone
OR ods.email != dim.email
OR ods.address != dim.address)
);
-- 插入新版本记录
INSERT INTO dim_customer (
customer_key, customer_id, customer_name, id_card,
phone, email, birth_date, age, age_group, gender, gender_desc,
address, city, province, postal_code, education_level,
education_level_desc, occupation, occupation_category,
annual_income, income_level, customer_grade, vip_flag,
register_date, account_count, total_balance, is_active,
effective_date, expiry_date, current_flag, create_time, update_time
)
SELECT
dim_customer_key_seq.NEXTVAL,
ods.customer_id,
ods.customer_name,
ods.id_card,
ods.phone,
ods.email,
ods.birth_date,
FLOOR(MONTHS_BETWEEN(SYSDATE, ods.birth_date) / 12) as age,
CASE
WHEN FLOOR(MONTHS_BETWEEN(SYSDATE, ods.birth_date) / 12) < 25 THEN '青年'
WHEN FLOOR(MONTHS_BETWEEN(SYSDATE, ods.birth_date) / 12) < 45 THEN '中年'
ELSE '老年'
END as age_group,
ods.gender,
CASE ods.gender WHEN 'M' THEN '男' WHEN 'F' THEN '女' ELSE '未知' END as gender_desc,
ods.address,
REGEXP_SUBSTR(ods.address, '[^省]+省?([^市]+市?)') as city,
REGEXP_SUBSTR(ods.address, '[^省]+省?') as province,
ods.postal_code,
ods.education_level,
CASE ods.education_level
WHEN 'PHD' THEN '博士'
WHEN 'MASTER' THEN '硕士'
WHEN 'BACHELOR' THEN '本科'
WHEN 'COLLEGE' THEN '大专'
WHEN 'HIGH_SCHOOL' THEN '高中'
ELSE '其他'
END as education_level_desc,
ods.occupation,
get_occupation_category(ods.occupation) as occupation_category,
ods.annual_income,
CASE
WHEN ods.annual_income >= 1000000 THEN '高收入'
WHEN ods.annual_income >= 200000 THEN '中高收入'
WHEN ods.annual_income >= 100000 THEN '中等收入'
WHEN ods.annual_income >= 50000 THEN '中低收入'
ELSE '低收入'
END as income_level,
dim_old.customer_grade,
dim_old.vip_flag,
dim_old.register_date,
dim_old.account_count,
dim_old.total_balance,
'Y' as is_active,
SYSDATE as effective_date,
DATE '9999-12-31' as expiry_date,
'Y' as current_flag,
SYSDATE as create_time,
SYSDATE as update_time
FROM ods_customer_info ods
JOIN dim_customer dim_old ON ods.customer_id = dim_old.customer_id
WHERE dim_old.current_flag = 'N'
AND dim_old.expiry_date = SYSDATE - 1/86400;
COMMIT;
DBMS_OUTPUT.PUT_LINE('客户维度表加载完成,处理记录数:' || v_processed_count);
EXCEPTION
WHEN OTHERS THEN
ROLLBACK;
RAISE;
END load_dim_customer;
/
2. 时间维度表设计
-- 时间维度表
CREATE TABLE dim_date (
date_key NUMBER(8) NOT NULL,
full_date DATE NOT NULL,
year_num NUMBER(4),
quarter_num NUMBER(1),
quarter_name VARCHAR2(10),
month_num NUMBER(2),
month_name VARCHAR2(20),
month_short_name VARCHAR2(10),
day_num NUMBER(2),
day_name VARCHAR2(20),
day_short_name VARCHAR2(10),
day_of_week NUMBER(1),
day_of_month NUMBER(2),
day_of_quarter NUMBER(3),
day_of_year NUMBER(3),
week_of_month NUMBER(1),
week_of_quarter NUMBER(2),
week_of_year NUMBER(2),
is_weekend CHAR(1),
is_holiday CHAR(1),
holiday_name VARCHAR2(50),
is_workday CHAR(1),
fiscal_year NUMBER(4),
fiscal_quarter NUMBER(1),
fiscal_month NUMBER(2)
) TABLESPACE DW_DATA;
-- 时间维度数据生成
CREATE OR REPLACE PROCEDURE populate_dim_date(
p_start_date IN DATE,
p_end_date IN DATE
)
AS
v_date DATE := p_start_date;
BEGIN
WHILE v_date <= p_end_date LOOP
INSERT INTO dim_date VALUES (
TO_NUMBER(TO_CHAR(v_date, 'YYYYMMDD')), -- date_key
v_date, -- full_date
EXTRACT(YEAR FROM v_date), -- year_num
CEIL(EXTRACT(MONTH FROM v_date) / 3), -- quarter_num
'Q' || CEIL(EXTRACT(MONTH FROM v_date) / 3), -- quarter_name
EXTRACT(MONTH FROM v_date), -- month_num
TO_CHAR(v_date, 'MONTH', 'NLS_DATE_LANGUAGE=SIMPLIFIED CHINESE'), -- month_name
TO_CHAR(v_date, 'MON', 'NLS_DATE_LANGUAGE=SIMPLIFIED CHINESE'), -- month_short_name
EXTRACT(DAY FROM v_date), -- day_num
TO_CHAR(v_date, 'DAY', 'NLS_DATE_LANGUAGE=SIMPLIFIED CHINESE'), -- day_name
TO_CHAR(v_date, 'DY', 'NLS_DATE_LANGUAGE=SIMPLIFIED CHINESE'), -- day_short_name
TO_NUMBER(TO_CHAR(v_date, 'D')), -- day_of_week
EXTRACT(DAY FROM v_date), -- day_of_month
v_date - TRUNC(v_date, 'Q') + 1, -- day_of_quarter
TO_NUMBER(TO_CHAR(v_date, 'DDD')), -- day_of_year
CEIL(EXTRACT(DAY FROM v_date) / 7), -- week_of_month
CEIL((v_date - TRUNC(v_date, 'Q') + 1) / 7), -- week_of_quarter
TO_NUMBER(TO_CHAR(v_date, 'WW')), -- week_of_year
CASE WHEN TO_CHAR(v_date, 'D') IN ('1', '7') THEN 'Y' ELSE 'N' END, -- is_weekend
'N', -- is_holiday (需要根据实际情况更新)
NULL, -- holiday_name
CASE WHEN TO_CHAR(v_date, 'D') NOT IN ('1', '7') THEN 'Y' ELSE 'N' END, -- is_workday
CASE WHEN EXTRACT(MONTH FROM v_date) >= 1 THEN EXTRACT(YEAR FROM v_date)
ELSE EXTRACT(YEAR FROM v_date) - 1 END, -- fiscal_year
CASE WHEN EXTRACT(MONTH FROM v_date) BETWEEN 1 AND 3 THEN 1
WHEN EXTRACT(MONTH FROM v_date) BETWEEN 4 AND 6 THEN 2
WHEN EXTRACT(MONTH FROM v_date) BETWEEN 7 AND 9 THEN 3
ELSE 4 END, -- fiscal_quarter
EXTRACT(MONTH FROM v_date) -- fiscal_month(此示例按自然年设置财务月份)
);
v_date := v_date + 1;
END LOOP;
COMMIT;
END populate_dim_date;
/
3. 事实表设计
-- 交易事实表
CREATE TABLE fact_transaction (
trans_key NUMBER(15) NOT NULL,
trans_id VARCHAR2(30) NOT NULL,
customer_key NUMBER(10),
account_key NUMBER(10),
product_key NUMBER(10),
channel_key NUMBER(10),
branch_key NUMBER(10),
date_key NUMBER(8),
time_key NUMBER(6),
trans_type_key NUMBER(5),
amount NUMBER(15,2),
balance_before NUMBER(15,2),
balance_after NUMBER(15,2),
fee_amount NUMBER(10,2),
trans_count NUMBER DEFAULT 1,
create_time DATE
)
PARTITION BY RANGE (date_key) (
PARTITION p_202301 VALUES LESS THAN (20230201),
PARTITION p_202302 VALUES LESS THAN (20230301),
PARTITION p_202303 VALUES LESS THAN (20230401)
) TABLESPACE DW_DATA;
-- 客户账户余额快照事实表
CREATE TABLE fact_account_balance_daily (
balance_key NUMBER(15) NOT NULL,
customer_key NUMBER(10),
account_key NUMBER(10),
product_key NUMBER(10),
date_key NUMBER(8),
balance_amount NUMBER(15,2),
available_balance NUMBER(15,2),
frozen_amount NUMBER(15,2),
interest_amount NUMBER(15,2),
daily_deposit_amount NUMBER(15,2),
daily_withdraw_amount NUMBER(15,2),
daily_trans_count NUMBER(5),
account_age_days NUMBER(10),
create_time DATE
)
PARTITION BY RANGE (date_key) (
PARTITION p_202301 VALUES LESS THAN (20230201),
PARTITION p_202302 VALUES LESS THAN (20230301),
PARTITION p_202303 VALUES LESS THAN (20230401)
) TABLESPACE DW_DATA;
DM层数据集市
1. 客户分析数据集市
-- 客户价值分析汇总表
CREATE TABLE dm_customer_value_analysis (
customer_id VARCHAR2(20),
customer_name VARCHAR2(100),
analysis_date DATE,
total_assets NUMBER(15,2),
total_liabilities NUMBER(15,2),
net_worth NUMBER(15,2),
monthly_avg_balance NUMBER(15,2),
monthly_transaction_count NUMBER(8),
monthly_transaction_amount NUMBER(15,2),
product_count NUMBER(3),
relationship_score NUMBER(5,2),
profitability_score NUMBER(5,2),
risk_score NUMBER(5,2),
customer_lifetime_value NUMBER(15,2),
customer_segment VARCHAR2(20),
next_best_action VARCHAR2(100),
create_time DATE
) TABLESPACE DM_DATA;
-- 客户行为分析汇总表
CREATE TABLE dm_customer_behavior_analysis (
customer_id VARCHAR2(20),
analysis_month VARCHAR2(7), -- YYYY-MM
login_frequency NUMBER(5),
transaction_frequency NUMBER(8),
channel_preference VARCHAR2(20),
transaction_time_pattern VARCHAR2(50),
product_usage_pattern VARCHAR2(100),
risk_behavior_score NUMBER(5,2),
churn_probability NUMBER(5,4),
cross_sell_opportunity VARCHAR2(200),
create_time DATE
) TABLESPACE DM_DATA;
2. 数据集市ETL实现
-- 客户价值分析ETL
CREATE OR REPLACE PROCEDURE load_dm_customer_value_analysis(
p_analysis_date IN DATE DEFAULT SYSDATE
)
AS
v_analysis_month VARCHAR2(7) := TO_CHAR(p_analysis_date, 'YYYY-MM');
BEGIN
-- 删除当月已有数据
DELETE FROM dm_customer_value_analysis
WHERE TO_CHAR(analysis_date, 'YYYY-MM') = v_analysis_month;
-- 插入客户价值分析数据
INSERT INTO dm_customer_value_analysis (
customer_id, customer_name, analysis_date,
total_assets, total_liabilities, net_worth,
monthly_avg_balance, monthly_transaction_count, monthly_transaction_amount,
product_count, relationship_score, profitability_score, risk_score,
customer_lifetime_value, customer_segment, next_best_action, create_time
)
WITH customer_assets AS (
SELECT
dc.customer_id,
dc.customer_name,
SUM(CASE WHEN ap.product_type IN ('SAVINGS', 'CURRENT', 'FIXED')
THEN fab.balance_amount ELSE 0 END) as total_assets,
SUM(CASE WHEN ap.product_type = 'LOAN'
THEN fab.balance_amount ELSE 0 END) as total_liabilities,
AVG(fab.balance_amount) as monthly_avg_balance,
SUM(fab.daily_trans_count) as monthly_transaction_count,
SUM(fab.daily_deposit_amount + fab.daily_withdraw_amount) as monthly_transaction_amount,
COUNT(DISTINCT ap.product_key) as product_count
FROM dim_customer dc
JOIN fact_account_balance_daily fab ON dc.customer_key = fab.customer_key
JOIN dim_account_product ap ON fab.product_key = ap.product_key
JOIN dim_date dd ON fab.date_key = dd.date_key
WHERE dc.current_flag = 'Y'
AND dd.year_num = EXTRACT(YEAR FROM p_analysis_date)
AND dd.month_num = EXTRACT(MONTH FROM p_analysis_date)
GROUP BY dc.customer_id, dc.customer_name
)
SELECT
ca.customer_id,
ca.customer_name,
p_analysis_date as analysis_date,
ca.total_assets,
ca.total_liabilities,
ca.total_assets - ca.total_liabilities as net_worth,
ca.monthly_avg_balance,
ca.monthly_transaction_count,
ca.monthly_transaction_amount,
ca.product_count,
-- 关系评分计算
LEAST(100, ca.product_count * 20 +
CASE WHEN ca.monthly_transaction_count > 50 THEN 40
WHEN ca.monthly_transaction_count > 20 THEN 30
WHEN ca.monthly_transaction_count > 10 THEN 20
ELSE 10 END) as relationship_score,
-- 盈利能力评分
LEAST(100, CASE WHEN ca.monthly_avg_balance >= 100000 THEN 80
WHEN ca.monthly_avg_balance >= 50000 THEN 60
WHEN ca.monthly_avg_balance >= 10000 THEN 40
ELSE 20 END +
CASE WHEN ca.monthly_transaction_amount >= 50000 THEN 20
WHEN ca.monthly_transaction_amount >= 20000 THEN 15
WHEN ca.monthly_transaction_amount >= 5000 THEN 10
ELSE 5 END) as profitability_score,
-- 风险评分(简化版本)
CASE WHEN ca.total_liabilities / NULLIF(ca.total_assets, 0) > 0.8 THEN 80
WHEN ca.total_liabilities / NULLIF(ca.total_assets, 0) > 0.6 THEN 60
WHEN ca.total_liabilities / NULLIF(ca.total_assets, 0) > 0.4 THEN 40
ELSE 20 END as risk_score,
-- 客户生命周期价值(简化计算)
ca.monthly_avg_balance * 0.02 * 12 as customer_lifetime_value,
-- 客户分群
CASE WHEN ca.total_assets >= 1000000 THEN 'VIP'
WHEN ca.total_assets >= 100000 THEN 'HIGH_VALUE'
WHEN ca.total_assets >= 10000 THEN 'MEDIUM_VALUE'
ELSE 'LOW_VALUE' END as customer_segment,
-- 下一步最佳行动
CASE WHEN ca.product_count = 1 THEN '推荐理财产品'
WHEN ca.monthly_transaction_count < 5 THEN '提升活跃度'
WHEN ca.total_assets > 500000 AND ca.product_count < 3 THEN '推荐投资产品'
ELSE '维护关系' END as next_best_action,
SYSDATE as create_time
FROM customer_assets ca;
COMMIT;
DBMS_OUTPUT.PUT_LINE('客户价值分析数据加载完成');
EXCEPTION
WHEN OTHERS THEN
ROLLBACK;
RAISE;
END load_dm_customer_value_analysis;
/
性能优化策略
1. 分区策略
-- 按日期范围分区的大表
ALTER TABLE fact_transaction ADD PARTITION p_202304
VALUES LESS THAN (20230501);
-- 子分区策略(按日期和客户分区)
CREATE TABLE fact_transaction_subpart (
trans_key NUMBER(15),
customer_id VARCHAR2(20),
trans_date DATE,
amount NUMBER(15,2)
)
PARTITION BY RANGE (trans_date)
SUBPARTITION BY HASH (customer_id) SUBPARTITIONS 4
(
PARTITION p_2023_q1 VALUES LESS THAN (DATE '2023-04-01'),
PARTITION p_2023_q2 VALUES LESS THAN (DATE '2023-07-01')
);
2. 索引优化
-- 创建位图索引用于数据仓库查询
CREATE BITMAP INDEX idx_customer_gender_bitmap
ON dim_customer(gender) TABLESPACE DW_INDEX;
CREATE BITMAP INDEX idx_customer_age_group_bitmap
ON dim_customer(age_group) TABLESPACE DW_INDEX;
-- 创建复合索引支持常用查询
CREATE INDEX idx_fact_trans_cust_date
ON fact_transaction(customer_key, date_key) TABLESPACE DW_INDEX;
-- 创建函数索引
CREATE INDEX idx_customer_upper_name
ON dim_customer(UPPER(customer_name)) TABLESPACE DW_INDEX;
3. 物化视图
-- 创建汇总物化视图
CREATE MATERIALIZED VIEW mv_customer_monthly_summary
BUILD IMMEDIATE
REFRESH FAST ON DEMAND
AS
SELECT
dc.customer_id,
dd.year_num,
dd.month_num,
SUM(ft.amount) as total_amount,
COUNT(*) as trans_count,
AVG(ft.amount) as avg_amount
FROM fact_transaction ft
JOIN dim_customer dc ON ft.customer_key = dc.customer_key
JOIN dim_date dd ON ft.date_key = dd.date_key
WHERE dc.current_flag = 'Y'
GROUP BY dc.customer_id, dd.year_num, dd.month_num;
-- 创建物化视图日志以支持快速刷新
CREATE MATERIALIZED VIEW LOG ON fact_transaction
WITH ROWID, SEQUENCE (customer_key, date_key, amount);
银行数据仓库的设计需要综合考虑业务需求、数据质量、性能要求和监管合规等多个方面。Oracle提供了丰富的技术特性来支撑大规模银行数据仓库的建设和运维。