本文设计了一个存贷款明细报表的数据架构方案,采用三层架构设计:DWD层存储原始交易数据,DIM层通过期限映射表将原始期限标准化为7个区间,DWS层按期限区间进行汇总。


方案包含完整的建表语句、测试数据初始化、ETL加工流程和最终报表查询SQL,实现了从明细数据到汇总报表的转换。


文中详细记录了开发过程中遇到的典型问题(如表不存在、数据插入失败、结果不完整等)及其解决方法,并总结了"分层验证"的调试方法论:先验证明细数据,再检查映射关联,最后确认汇总结果。


该方案既保证了数据规范性,又能满足报表报送需求。


数据仓库分层设计练习

一、需求

根据 存款账户明细表 和 借据表 两张基础层表,设计中间汇总层模型(DWS/指标层),用以支撑存贷款明细报表的报送。

报表输出格式

项目 各项存款 各项贷款(余额)
三个月以内 xxx xxx
三个月至六个月 xxx xxx
六个月至一年 xxx xxx
一年至两年 xxx xxx
两年至三年 xxx xxx
三年至五年 xxx xxx
五年以上 xxx xxx

二、数据架构设计

2.1 整体分层架构

text

┌─────────────────┐     ┌──────────────────────────────────────┐     ┌─────────────────┐
│   DWD层(明细层) │     │            DWS层(汇总层)             │     │   ADS层(应用层) │
├─────────────────┤     ├──────────────────────────────────────┤     ├─────────────────┤
│                 │     │                                      │     │                 │
│ DEPOSIT_ACCOUNT │────→│     DWS_DEPOSIT_TERM_SUM            │     │                 │
│    _DETAIL      │     │   存款按期限区间汇总表                 │     │   存贷款明细报表  │
│                 │     │                                      │────→│   (最终输出)    │
├─────────────────┤     ├──────────────────────────────────────┤     │                 │
│                 │     │                                      │     │                 │
│   LOAN_DETAIL   │────→│     DWS_LOAN_TERM_SUM               │     │                 │
│    借据表        │     │   贷款按期限区间汇总表                 │     │                 │
│                 │     │                                      │     │                 │
└─────────────────┘     └──────────────────────────────────────┘     └─────────────────┘

                              ↓
                    ┌─────────────────────┐
                    │   DIM_TERM_MAPPING   │
                    │    期限区间映射表      │
                    │  (将原始期限标准化)   │
                    └─────────────────────┘

2.2 设计说明

设计要点 说明
DWD层 保留明细数据,存储存款和贷款的原始交易记录
DIM层 期限映射表,将原始期限枚举值(如 1Y3Y活期)标准化到7个报表区间
DWS层 按期限区间进行轻度汇总,提升报表查询性能
ADS层 最终报表查询,合并存款和贷款数据

2.3 期限区间映射规则

区间编号 标准区间名称 存款期限枚举 贷款期限枚举
3 三个月以内 活期、3M 3M
4 三个月至六个月 6M 6M
5 六个月至一年 9M、1Y 1Y
6 一年至两年 2Y 2Y
7 两年至三年 3Y 3Y
8 三年至五年 5Y 5Y
9 五年以上 5Y+ 5Y+

三、建表语句

3.1 存款明细表(DWD层)

sql

-- 存款账户明细表
CREATE TABLE DEPOSIT_ACCOUNT_DETAIL (
    ACCOUNT_NO      VARCHAR2(30)  NOT NULL,
    CUST_NO         VARCHAR2(20)  NOT NULL,
    ACCOUNT_BALANCE NUMBER(18,2)  NOT NULL,
    DEPOSIT_TERM    VARCHAR2(20)  NOT NULL,
    CREATE_TIME     DATE          DEFAULT SYSDATE
);

ALTER TABLE DEPOSIT_ACCOUNT_DETAIL ADD CONSTRAINT PK_DEPOSIT_ACCOUNT PRIMARY KEY (ACCOUNT_NO);

COMMENT ON TABLE DEPOSIT_ACCOUNT_DETAIL IS '存款账户明细表';
COMMENT ON COLUMN DEPOSIT_ACCOUNT_DETAIL.ACCOUNT_NO IS '存款账号';
COMMENT ON COLUMN DEPOSIT_ACCOUNT_DETAIL.CUST_NO IS '客户号';
COMMENT ON COLUMN DEPOSIT_ACCOUNT_DETAIL.ACCOUNT_BALANCE IS '账户余额';
COMMENT ON COLUMN DEPOSIT_ACCOUNT_DETAIL.DEPOSIT_TERM IS '存款期限';

3.2 借据表(DWD层)

sql

-- 借据明细表
CREATE TABLE LOAN_DETAIL (
    LOAN_NO         VARCHAR2(20)  NOT NULL,
    CUST_NO         VARCHAR2(20)  NOT NULL,
    LOAN_BALANCE    NUMBER(15,2)  NOT NULL,
    LOAN_AMOUNT     NUMBER(15,2)  NOT NULL,
    LOAN_TERM       VARCHAR2(20)  NOT NULL,
    CREATE_TIME     DATE          DEFAULT SYSDATE
);

ALTER TABLE LOAN_DETAIL ADD CONSTRAINT PK_LOAN_DETAIL PRIMARY KEY (LOAN_NO);

COMMENT ON TABLE LOAN_DETAIL IS '借据明细表';
COMMENT ON COLUMN LOAN_DETAIL.LOAN_NO IS '借据号';
COMMENT ON COLUMN LOAN_DETAIL.CUST_NO IS '客户号';
COMMENT ON COLUMN LOAN_DETAIL.LOAN_BALANCE IS '贷款余额';
COMMENT ON COLUMN LOAN_DETAIL.LOAN_AMOUNT IS '贷款金额';
COMMENT ON COLUMN LOAN_DETAIL.LOAN_TERM IS '贷款期限';

3.3 期限映射表(DIM层)

sql

-- 期限区间映射表
CREATE TABLE DIM_TERM_MAPPING (
    SOURCE_TERM     VARCHAR2(20)  NOT NULL,
    TERM_TYPE       VARCHAR2(10)  NOT NULL,
    TERM_INTERVAL   VARCHAR2(30)  NOT NULL,
    INTERVAL_ORDER  NUMBER(2)     NOT NULL,
    CREATE_TIME     DATE          DEFAULT SYSDATE
);

ALTER TABLE DIM_TERM_MAPPING ADD CONSTRAINT PK_TERM_MAPPING PRIMARY KEY (SOURCE_TERM, TERM_TYPE);

3.4 存款汇总表(DWS层)

sql

-- DWS层:存款按期限区间汇总表
CREATE TABLE DWS_DEPOSIT_TERM_SUM (
    STAT_DATE       DATE          NOT NULL,
    TERM_INTERVAL   VARCHAR2(30)  NOT NULL,
    INTERVAL_ORDER  NUMBER(2)     NOT NULL,
    TOTAL_BALANCE   NUMBER(20,2)  NOT NULL,
    ACCOUNT_COUNT   NUMBER(10)    NOT NULL,
    UPDATE_TIME     DATE          DEFAULT SYSDATE
);

ALTER TABLE DWS_DEPOSIT_TERM_SUM ADD CONSTRAINT PK_DWS_DEPOSIT_SUM PRIMARY KEY (STAT_DATE, TERM_INTERVAL);

3.5 贷款汇总表(DWS层)

sql

-- DWS层:贷款按期限区间汇总表
CREATE TABLE DWS_LOAN_TERM_SUM (
    STAT_DATE       DATE          NOT NULL,
    TERM_INTERVAL   VARCHAR2(30)  NOT NULL,
    INTERVAL_ORDER  NUMBER(2)     NOT NULL,
    TOTAL_BALANCE   NUMBER(20,2)  NOT NULL,
    LOAN_COUNT      NUMBER(10)    NOT NULL,
    UPDATE_TIME     DATE          DEFAULT SYSDATE
);

ALTER TABLE DWS_LOAN_TERM_SUM ADD CONSTRAINT PK_DWS_LOAN_SUM PRIMARY KEY (STAT_DATE, TERM_INTERVAL);

四、映射表数据初始化

sql

-- 存款期限映射
INSERT INTO DIM_TERM_MAPPING (SOURCE_TERM, TERM_TYPE, TERM_INTERVAL, INTERVAL_ORDER) VALUES ('活期', 'DEPOSIT', '三个月以内', 3);
INSERT INTO DIM_TERM_MAPPING (SOURCE_TERM, TERM_TYPE, TERM_INTERVAL, INTERVAL_ORDER) VALUES ('3M', 'DEPOSIT', '三个月以内', 3);
INSERT INTO DIM_TERM_MAPPING (SOURCE_TERM, TERM_TYPE, TERM_INTERVAL, INTERVAL_ORDER) VALUES ('6M', 'DEPOSIT', '三个月至六个月', 4);
INSERT INTO DIM_TERM_MAPPING (SOURCE_TERM, TERM_TYPE, TERM_INTERVAL, INTERVAL_ORDER) VALUES ('9M', 'DEPOSIT', '六个月至一年', 5);
INSERT INTO DIM_TERM_MAPPING (SOURCE_TERM, TERM_TYPE, TERM_INTERVAL, INTERVAL_ORDER) VALUES ('1Y', 'DEPOSIT', '六个月至一年', 5);
INSERT INTO DIM_TERM_MAPPING (SOURCE_TERM, TERM_TYPE, TERM_INTERVAL, INTERVAL_ORDER) VALUES ('2Y', 'DEPOSIT', '一年至两年', 6);
INSERT INTO DIM_TERM_MAPPING (SOURCE_TERM, TERM_TYPE, TERM_INTERVAL, INTERVAL_ORDER) VALUES ('3Y', 'DEPOSIT', '两年至三年', 7);
INSERT INTO DIM_TERM_MAPPING (SOURCE_TERM, TERM_TYPE, TERM_INTERVAL, INTERVAL_ORDER) VALUES ('5Y', 'DEPOSIT', '三年至五年', 8);
INSERT INTO DIM_TERM_MAPPING (SOURCE_TERM, TERM_TYPE, TERM_INTERVAL, INTERVAL_ORDER) VALUES ('5Y+', 'DEPOSIT', '五年以上', 9);

-- 贷款期限映射
INSERT INTO DIM_TERM_MAPPING (SOURCE_TERM, TERM_TYPE, TERM_INTERVAL, INTERVAL_ORDER) VALUES ('3M', 'LOAN', '三个月以内', 3);
INSERT INTO DIM_TERM_MAPPING (SOURCE_TERM, TERM_TYPE, TERM_INTERVAL, INTERVAL_ORDER) VALUES ('6M', 'LOAN', '三个月至六个月', 4);
INSERT INTO DIM_TERM_MAPPING (SOURCE_TERM, TERM_TYPE, TERM_INTERVAL, INTERVAL_ORDER) VALUES ('1Y', 'LOAN', '六个月至一年', 5);
INSERT INTO DIM_TERM_MAPPING (SOURCE_TERM, TERM_TYPE, TERM_INTERVAL, INTERVAL_ORDER) VALUES ('2Y', 'LOAN', '一年至两年', 6);
INSERT INTO DIM_TERM_MAPPING (SOURCE_TERM, TERM_TYPE, TERM_INTERVAL, INTERVAL_ORDER) VALUES ('3Y', 'LOAN', '两年至三年', 7);
INSERT INTO DIM_TERM_MAPPING (SOURCE_TERM, TERM_TYPE, TERM_INTERVAL, INTERVAL_ORDER) VALUES ('5Y', 'LOAN', '三年至五年', 8);
INSERT INTO DIM_TERM_MAPPING (SOURCE_TERM, TERM_TYPE, TERM_INTERVAL, INTERVAL_ORDER) VALUES ('5Y+', 'LOAN', '五年以上', 9);

COMMIT;

五、测试数据

5.1 存款测试数据

sql

INSERT INTO DEPOSIT_ACCOUNT_DETAIL (ACCOUNT_NO, CUST_NO, ACCOUNT_BALANCE, DEPOSIT_TERM) VALUES ('ACC001', 'CUST01', 50000.00, '活期');
INSERT INTO DEPOSIT_ACCOUNT_DETAIL (ACCOUNT_NO, CUST_NO, ACCOUNT_BALANCE, DEPOSIT_TERM) VALUES ('ACC002', 'CUST02', 30000.00, '3M');
INSERT INTO DEPOSIT_ACCOUNT_DETAIL (ACCOUNT_NO, CUST_NO, ACCOUNT_BALANCE, DEPOSIT_TERM) VALUES ('ACC003', 'CUST03', 80000.00, '6M');
INSERT INTO DEPOSIT_ACCOUNT_DETAIL (ACCOUNT_NO, CUST_NO, ACCOUNT_BALANCE, DEPOSIT_TERM) VALUES ('ACC004', 'CUST04', 120000.00, '1Y');
INSERT INTO DEPOSIT_ACCOUNT_DETAIL (ACCOUNT_NO, CUST_NO, ACCOUNT_BALANCE, DEPOSIT_TERM) VALUES ('ACC005', 'CUST01', 60000.00, '9M');
INSERT INTO DEPOSIT_ACCOUNT_DETAIL (ACCOUNT_NO, CUST_NO, ACCOUNT_BALANCE, DEPOSIT_TERM) VALUES ('ACC006', 'CUST05', 200000.00, '2Y');
INSERT INTO DEPOSIT_ACCOUNT_DETAIL (ACCOUNT_NO, CUST_NO, ACCOUNT_BALANCE, DEPOSIT_TERM) VALUES ('ACC007', 'CUST06', 150000.00, '3Y');
INSERT INTO DEPOSIT_ACCOUNT_DETAIL (ACCOUNT_NO, CUST_NO, ACCOUNT_BALANCE, DEPOSIT_TERM) VALUES ('ACC008', 'CUST07', 300000.00, '5Y');
INSERT INTO DEPOSIT_ACCOUNT_DETAIL (ACCOUNT_NO, CUST_NO, ACCOUNT_BALANCE, DEPOSIT_TERM) VALUES ('ACC009', 'CUST08', 500000.00, '5Y+');
COMMIT;

5.2 贷款测试数据

sql

INSERT INTO LOAN_DETAIL (LOAN_NO, CUST_NO, LOAN_BALANCE, LOAN_AMOUNT, LOAN_TERM) VALUES ('LOAN001', 'CUST01', 30000.00, 50000.00, '3M');
INSERT INTO LOAN_DETAIL (LOAN_NO, CUST_NO, LOAN_BALANCE, LOAN_AMOUNT, LOAN_TERM) VALUES ('LOAN002', 'CUST02', 60000.00, 100000.00, '6M');
INSERT INTO LOAN_DETAIL (LOAN_NO, CUST_NO, LOAN_BALANCE, LOAN_AMOUNT, LOAN_TERM) VALUES ('LOAN003', 'CUST03', 150000.00, 200000.00, '1Y');
INSERT INTO LOAN_DETAIL (LOAN_NO, CUST_NO, LOAN_BALANCE, LOAN_AMOUNT, LOAN_TERM) VALUES ('LOAN004', 'CUST04', 250000.00, 300000.00, '2Y');
INSERT INTO LOAN_DETAIL (LOAN_NO, CUST_NO, LOAN_BALANCE, LOAN_AMOUNT, LOAN_TERM) VALUES ('LOAN005', 'CUST05', 350000.00, 500000.00, '3Y');
INSERT INTO LOAN_DETAIL (LOAN_NO, CUST_NO, LOAN_BALANCE, LOAN_AMOUNT, LOAN_TERM) VALUES ('LOAN006', 'CUST01', 180000.00, 200000.00, '3Y');
INSERT INTO LOAN_DETAIL (LOAN_NO, CUST_NO, LOAN_BALANCE, LOAN_AMOUNT, LOAN_TERM) VALUES ('LOAN007', 'CUST06', 450000.00, 600000.00, '5Y');
INSERT INTO LOAN_DETAIL (LOAN_NO, CUST_NO, LOAN_BALANCE, LOAN_AMOUNT, LOAN_TERM) VALUES ('LOAN008', 'CUST07', 800000.00, 1000000.00, '5Y+');
COMMIT;

六、ETL加工语句

6.1 存款汇总(DWD → DWS)

sql

TRUNCATE TABLE DWS_DEPOSIT_TERM_SUM;

INSERT INTO DWS_DEPOSIT_TERM_SUM (STAT_DATE, TERM_INTERVAL, INTERVAL_ORDER, TOTAL_BALANCE, ACCOUNT_COUNT, UPDATE_TIME)
SELECT 
    TRUNC(SYSDATE) AS STAT_DATE,
    M.TERM_INTERVAL,
    M.INTERVAL_ORDER,
    SUM(D.ACCOUNT_BALANCE) AS TOTAL_BALANCE,
    COUNT(DISTINCT D.ACCOUNT_NO) AS ACCOUNT_COUNT,
    SYSDATE AS UPDATE_TIME
FROM DEPOSIT_ACCOUNT_DETAIL D
INNER JOIN DIM_TERM_MAPPING M
    ON D.DEPOSIT_TERM = M.SOURCE_TERM
    AND M.TERM_TYPE = 'DEPOSIT'
GROUP BY M.TERM_INTERVAL, M.INTERVAL_ORDER;

6.2 贷款汇总(DWD → DWS)

sql

TRUNCATE TABLE DWS_LOAN_TERM_SUM;

INSERT INTO DWS_LOAN_TERM_SUM (STAT_DATE, TERM_INTERVAL, INTERVAL_ORDER, TOTAL_BALANCE, LOAN_COUNT, UPDATE_TIME)
SELECT 
    TRUNC(SYSDATE) AS STAT_DATE,
    M.TERM_INTERVAL,
    M.INTERVAL_ORDER,
    SUM(L.LOAN_BALANCE) AS TOTAL_BALANCE,
    COUNT(DISTINCT L.LOAN_NO) AS LOAN_COUNT,
    SYSDATE AS UPDATE_TIME
FROM LOAN_DETAIL L
INNER JOIN DIM_TERM_MAPPING M
    ON L.LOAN_TERM = M.SOURCE_TERM
    AND M.TERM_TYPE = 'LOAN'
GROUP BY M.TERM_INTERVAL, M.INTERVAL_ORDER;

COMMIT;

七、最终报表查询

sql

SELECT 
    COALESCE(D.TERM_INTERVAL, L.TERM_INTERVAL) AS "项目",
    NVL(D.TOTAL_BALANCE, 0) AS "各项存款",
    NVL(L.TOTAL_BALANCE, 0) AS "各项贷款(余额)",
    COALESCE(D.INTERVAL_ORDER, L.INTERVAL_ORDER) AS "排序"
FROM 
    DWS_DEPOSIT_TERM_SUM D
FULL OUTER JOIN 
    DWS_LOAN_TERM_SUM L
    ON D.TERM_INTERVAL = L.TERM_INTERVAL
ORDER BY "排序";

八、执行结果

8.1 DWS存款汇总表

STAT_DATE TERM_INTERVAL INTERVAL_ORDER TOTAL_BALANCE ACCOUNT_COUNT
2026-05-23 三个月以内 3 80,000 2
2026-05-23 三个月至六个月 4 80,000 1
2026-05-23 六个月至一年 5 180,000 2
2026-05-23 一年至两年 6 200,000 1
2026-05-23 两年至三年 7 150,000 1
2026-05-23 三年至五年 8 300,000 1
2026-05-23 五年以上 9 500,000 1

8.2 DWS贷款汇总表

STAT_DATE TERM_INTERVAL INTERVAL_ORDER TOTAL_BALANCE LOAN_COUNT
2026-05-23 三个月以内 3 30,000 1
2026-05-23 三个月至六个月 4 60,000 1
2026-05-23 六个月至一年 5 150,000 1
2026-05-23 一年至两年 6 250,000 1
2026-05-23 两年至三年 7 530,000 2
2026-05-23 三年至五年 8 450,000 1
2026-05-23 五年以上 9 800,000 1

8.3 最终报表输出

项目 各项存款 各项贷款(余额) 排序
三个月以内 80,000 30,000 3
三个月至六个月 80,000 60,000 4
六个月至一年 180,000 150,000 5
一年至两年 200,000 250,000 6
两年至三年 150,000 530,000 7
三年至五年 300,000 450,000 8
五年以上 500,000 800,000 9

九、附录:简化版解法

作为对比,以下是更简洁的设计方案,跳过映射表直接按原始期限聚合:

sql

-- 创建汇总表
CREATE TABLE BAL_SUM (
    TERM         VARCHAR2(20),
    BALANCE      NUMBER(18,2),
    LOAN_BALANCE NUMBER(15,2)
);

-- ETL加工
INSERT INTO BAL_SUM
SELECT NVL(T3.DEPOSIT_TERM, T4.LOAN_TERM) AS TERM
      ,T3.SUM_BALANCE AS BALANCE
      ,T4.SUM_LOAN_BALANCE AS LOAN_BALANCE
FROM (
      SELECT DEPOSIT_TERM, SUM(ACCOUNT_BALANCE) AS SUM_BALANCE
      FROM DEPOSIT_ACCOUNT_DETAIL
      GROUP BY DEPOSIT_TERM
     ) T3
FULL JOIN (
      SELECT LOAN_TERM, SUM(LOAN_BALANCE) AS SUM_LOAN_BALANCE
      FROM LOAN_DETAIL
      GROUP BY LOAN_TERM
) T4 ON T3.DEPOSIT_TERM = T4.LOAN_TERM;

SELECT * FROM BAL_SUM;

两种解法对比

对比维度 分层设计解法 简化版解法
表数量 5张 1张
期限标准化 统一到7档区间 保留原始枚举值
可扩展性 新增期限只需维护映射表 需修改应用层SQL
适用场景 规范数仓、长期维护 快速出数、一次性需求

十、总结

本练习完成了以下工作:

  1. ✅ 设计了符合数仓分层规范的 DWD、DIM、DWS 三层架构

  2. ✅ 实现了期限枚举值到标准报表区间的映射转换

  3. ✅ 编写了完整的建表、初始化、ETL、查询 SQL 脚本

  4. ✅ 提供了覆盖所有期限区间的测试数据

  5. ✅ 输出了符合要求的存贷款明细报表


drop所有相关表,重新测试

--drop所有相关表,重新测试
-- 删除所有相关表(按依赖顺序,先删子表,再删主表)
DROP TABLE DWS_DEPOSIT_TERM_SUM PURGE;
DROP TABLE DWS_LOAN_TERM_SUM PURGE;
DROP TABLE DIM_TERM_MAPPING PURGE;
DROP TABLE DEPOSIT_ACCOUNT_DETAIL PURGE;
DROP TABLE LOAN_DETAIL PURGE;
DROP TABLE BAL_SUM PURGE;

-- 确认已全部删除
SELECT table_name FROM user_tables
WHERE table_name IN ('DWS_DEPOSIT_TERM_SUM', 'DWS_LOAN_TERM_SUM', 'DIM_TERM_MAPPING',
                     'DEPOSIT_ACCOUNT_DETAIL', 'LOAN_DETAIL', 'BAL_SUM');

-- =========================================
-- 1. 存款明细表(DWD层)
-- =========================================
CREATE TABLE DEPOSIT_ACCOUNT_DETAIL (
    ACCOUNT_NO      VARCHAR2(30)  NOT NULL,
    CUST_NO         VARCHAR2(20)  NOT NULL,
    ACCOUNT_BALANCE NUMBER(18,2)  NOT NULL,
    DEPOSIT_TERM    VARCHAR2(20)  NOT NULL,
    CREATE_TIME     DATE          DEFAULT SYSDATE
);

ALTER TABLE DEPOSIT_ACCOUNT_DETAIL ADD CONSTRAINT PK_DEPOSIT_ACCOUNT PRIMARY KEY (ACCOUNT_NO);

COMMENT ON TABLE DEPOSIT_ACCOUNT_DETAIL IS '存款账户明细表';
COMMENT ON COLUMN DEPOSIT_ACCOUNT_DETAIL.ACCOUNT_NO IS '存款账号';
COMMENT ON COLUMN DEPOSIT_ACCOUNT_DETAIL.CUST_NO IS '客户号';
COMMENT ON COLUMN DEPOSIT_ACCOUNT_DETAIL.ACCOUNT_BALANCE IS '账户余额';
COMMENT ON COLUMN DEPOSIT_ACCOUNT_DETAIL.DEPOSIT_TERM IS '存款期限';

-- =========================================
-- 2. 借据表(DWD层)
-- =========================================
CREATE TABLE LOAN_DETAIL (
    LOAN_NO         VARCHAR2(20)  NOT NULL,
    CUST_NO         VARCHAR2(20)  NOT NULL,
    LOAN_BALANCE    NUMBER(15,2)  NOT NULL,
    LOAN_AMOUNT     NUMBER(15,2)  NOT NULL,
    LOAN_TERM       VARCHAR2(20)  NOT NULL,
    CREATE_TIME     DATE          DEFAULT SYSDATE
);

ALTER TABLE LOAN_DETAIL ADD CONSTRAINT PK_LOAN_DETAIL PRIMARY KEY (LOAN_NO);

COMMENT ON TABLE LOAN_DETAIL IS '借据明细表';
COMMENT ON COLUMN LOAN_DETAIL.LOAN_NO IS '借据号';
COMMENT ON COLUMN LOAN_DETAIL.CUST_NO IS '客户号';
COMMENT ON COLUMN LOAN_DETAIL.LOAN_BALANCE IS '贷款余额';
COMMENT ON COLUMN LOAN_DETAIL.LOAN_AMOUNT IS '贷款金额';
COMMENT ON COLUMN LOAN_DETAIL.LOAN_TERM IS '贷款期限';

-- =========================================
-- 3. 期限映射表(维度层)
-- =========================================
CREATE TABLE DIM_TERM_MAPPING (
    SOURCE_TERM     VARCHAR2(20)  NOT NULL,
    TERM_TYPE       VARCHAR2(10)  NOT NULL,
    TERM_INTERVAL   VARCHAR2(30)  NOT NULL,
    INTERVAL_ORDER  NUMBER(2)     NOT NULL,
    CREATE_TIME     DATE          DEFAULT SYSDATE
);

ALTER TABLE DIM_TERM_MAPPING ADD CONSTRAINT PK_TERM_MAPPING PRIMARY KEY (SOURCE_TERM, TERM_TYPE);

COMMENT ON TABLE DIM_TERM_MAPPING IS '期限区间映射表';
COMMENT ON COLUMN DIM_TERM_MAPPING.SOURCE_TERM IS '源表期限值';
COMMENT ON COLUMN DIM_TERM_MAPPING.TERM_TYPE IS '类型:DEPOSIT/LOAN';
COMMENT ON COLUMN DIM_TERM_MAPPING.TERM_INTERVAL IS '标准区间名称';
COMMENT ON COLUMN DIM_TERM_MAPPING.INTERVAL_ORDER IS '区间排序编号';

-- =========================================
-- 4. DWS层:存款按期限区间汇总表
-- =========================================
CREATE TABLE DWS_DEPOSIT_TERM_SUM (
    STAT_DATE       DATE          NOT NULL,
    TERM_INTERVAL   VARCHAR2(30)  NOT NULL,
    INTERVAL_ORDER  NUMBER(2)     NOT NULL,
    TOTAL_BALANCE   NUMBER(20,2)  NOT NULL,
    ACCOUNT_COUNT   NUMBER(10)    NOT NULL,
    UPDATE_TIME     DATE          DEFAULT SYSDATE
);

ALTER TABLE DWS_DEPOSIT_TERM_SUM ADD CONSTRAINT PK_DWS_DEPOSIT_SUM PRIMARY KEY (STAT_DATE, TERM_INTERVAL);

-- =========================================
-- 5. DWS层:贷款按期限区间汇总表
-- =========================================
CREATE TABLE DWS_LOAN_TERM_SUM (
    STAT_DATE       DATE          NOT NULL,
    TERM_INTERVAL   VARCHAR2(30)  NOT NULL,
    INTERVAL_ORDER  NUMBER(2)     NOT NULL,
    TOTAL_BALANCE   NUMBER(20,2)  NOT NULL,
    LOAN_COUNT      NUMBER(10)    NOT NULL,
    UPDATE_TIME     DATE          DEFAULT SYSDATE
);

ALTER TABLE DWS_LOAN_TERM_SUM ADD CONSTRAINT PK_DWS_LOAN_SUM PRIMARY KEY (STAT_DATE, TERM_INTERVAL);

-- 存款期限映射
INSERT INTO DIM_TERM_MAPPING (SOURCE_TERM, TERM_TYPE, TERM_INTERVAL, INTERVAL_ORDER) VALUES ('活期', 'DEPOSIT', '三个月以内', 3);
INSERT INTO DIM_TERM_MAPPING (SOURCE_TERM, TERM_TYPE, TERM_INTERVAL, INTERVAL_ORDER) VALUES ('3M', 'DEPOSIT', '三个月以内', 3);
INSERT INTO DIM_TERM_MAPPING (SOURCE_TERM, TERM_TYPE, TERM_INTERVAL, INTERVAL_ORDER) VALUES ('6M', 'DEPOSIT', '三个月至六个月', 4);
INSERT INTO DIM_TERM_MAPPING (SOURCE_TERM, TERM_TYPE, TERM_INTERVAL, INTERVAL_ORDER) VALUES ('9M', 'DEPOSIT', '六个月至一年', 5);
INSERT INTO DIM_TERM_MAPPING (SOURCE_TERM, TERM_TYPE, TERM_INTERVAL, INTERVAL_ORDER) VALUES ('1Y', 'DEPOSIT', '六个月至一年', 5);
INSERT INTO DIM_TERM_MAPPING (SOURCE_TERM, TERM_TYPE, TERM_INTERVAL, INTERVAL_ORDER) VALUES ('2Y', 'DEPOSIT', '一年至两年', 6);
INSERT INTO DIM_TERM_MAPPING (SOURCE_TERM, TERM_TYPE, TERM_INTERVAL, INTERVAL_ORDER) VALUES ('3Y', 'DEPOSIT', '两年至三年', 7);
INSERT INTO DIM_TERM_MAPPING (SOURCE_TERM, TERM_TYPE, TERM_INTERVAL, INTERVAL_ORDER) VALUES ('5Y', 'DEPOSIT', '三年至五年', 8);
INSERT INTO DIM_TERM_MAPPING (SOURCE_TERM, TERM_TYPE, TERM_INTERVAL, INTERVAL_ORDER) VALUES ('5Y+', 'DEPOSIT', '五年以上', 9);

-- 贷款期限映射
INSERT INTO DIM_TERM_MAPPING (SOURCE_TERM, TERM_TYPE, TERM_INTERVAL, INTERVAL_ORDER) VALUES ('3M', 'LOAN', '三个月以内', 3);
INSERT INTO DIM_TERM_MAPPING (SOURCE_TERM, TERM_TYPE, TERM_INTERVAL, INTERVAL_ORDER) VALUES ('6M', 'LOAN', '三个月至六个月', 4);
INSERT INTO DIM_TERM_MAPPING (SOURCE_TERM, TERM_TYPE, TERM_INTERVAL, INTERVAL_ORDER) VALUES ('1Y', 'LOAN', '六个月至一年', 5);
INSERT INTO DIM_TERM_MAPPING (SOURCE_TERM, TERM_TYPE, TERM_INTERVAL, INTERVAL_ORDER) VALUES ('2Y', 'LOAN', '一年至两年', 6);
INSERT INTO DIM_TERM_MAPPING (SOURCE_TERM, TERM_TYPE, TERM_INTERVAL, INTERVAL_ORDER) VALUES ('3Y', 'LOAN', '两年至三年', 7);
INSERT INTO DIM_TERM_MAPPING (SOURCE_TERM, TERM_TYPE, TERM_INTERVAL, INTERVAL_ORDER) VALUES ('5Y', 'LOAN', '三年至五年', 8);
INSERT INTO DIM_TERM_MAPPING (SOURCE_TERM, TERM_TYPE, TERM_INTERVAL, INTERVAL_ORDER) VALUES ('5Y+', 'LOAN', '五年以上', 9);

COMMIT;

-- =========================================
-- 存款测试数据(9条,覆盖所有期限)
-- =========================================
INSERT INTO DEPOSIT_ACCOUNT_DETAIL (ACCOUNT_NO, CUST_NO, ACCOUNT_BALANCE, DEPOSIT_TERM) VALUES ('ACC001', 'CUST01', 50000.00, '活期');
INSERT INTO DEPOSIT_ACCOUNT_DETAIL (ACCOUNT_NO, CUST_NO, ACCOUNT_BALANCE, DEPOSIT_TERM) VALUES ('ACC002', 'CUST02', 30000.00, '3M');
INSERT INTO DEPOSIT_ACCOUNT_DETAIL (ACCOUNT_NO, CUST_NO, ACCOUNT_BALANCE, DEPOSIT_TERM) VALUES ('ACC003', 'CUST03', 80000.00, '6M');
INSERT INTO DEPOSIT_ACCOUNT_DETAIL (ACCOUNT_NO, CUST_NO, ACCOUNT_BALANCE, DEPOSIT_TERM) VALUES ('ACC004', 'CUST04', 120000.00, '1Y');
INSERT INTO DEPOSIT_ACCOUNT_DETAIL (ACCOUNT_NO, CUST_NO, ACCOUNT_BALANCE, DEPOSIT_TERM) VALUES ('ACC005', 'CUST01', 60000.00, '9M');
INSERT INTO DEPOSIT_ACCOUNT_DETAIL (ACCOUNT_NO, CUST_NO, ACCOUNT_BALANCE, DEPOSIT_TERM) VALUES ('ACC006', 'CUST05', 200000.00, '2Y');
INSERT INTO DEPOSIT_ACCOUNT_DETAIL (ACCOUNT_NO, CUST_NO, ACCOUNT_BALANCE, DEPOSIT_TERM) VALUES ('ACC007', 'CUST06', 150000.00, '3Y');
INSERT INTO DEPOSIT_ACCOUNT_DETAIL (ACCOUNT_NO, CUST_NO, ACCOUNT_BALANCE, DEPOSIT_TERM) VALUES ('ACC008', 'CUST07', 300000.00, '5Y');
INSERT INTO DEPOSIT_ACCOUNT_DETAIL (ACCOUNT_NO, CUST_NO, ACCOUNT_BALANCE, DEPOSIT_TERM) VALUES ('ACC009', 'CUST08', 500000.00, '5Y+');

-- =========================================
-- 贷款测试数据(8条,覆盖所有期限)
-- =========================================
INSERT INTO LOAN_DETAIL (LOAN_NO, CUST_NO, LOAN_BALANCE, LOAN_AMOUNT, LOAN_TERM) VALUES ('LOAN001', 'CUST01', 30000.00, 50000.00, '3M');
INSERT INTO LOAN_DETAIL (LOAN_NO, CUST_NO, LOAN_BALANCE, LOAN_AMOUNT, LOAN_TERM) VALUES ('LOAN002', 'CUST02', 60000.00, 100000.00, '6M');
INSERT INTO LOAN_DETAIL (LOAN_NO, CUST_NO, LOAN_BALANCE, LOAN_AMOUNT, LOAN_TERM) VALUES ('LOAN003', 'CUST03', 150000.00, 200000.00, '1Y');
INSERT INTO LOAN_DETAIL (LOAN_NO, CUST_NO, LOAN_BALANCE, LOAN_AMOUNT, LOAN_TERM) VALUES ('LOAN004', 'CUST04', 250000.00, 300000.00, '2Y');
INSERT INTO LOAN_DETAIL (LOAN_NO, CUST_NO, LOAN_BALANCE, LOAN_AMOUNT, LOAN_TERM) VALUES ('LOAN005', 'CUST05', 350000.00, 500000.00, '3Y');
INSERT INTO LOAN_DETAIL (LOAN_NO, CUST_NO, LOAN_BALANCE, LOAN_AMOUNT, LOAN_TERM) VALUES ('LOAN006', 'CUST01', 180000.00, 200000.00, '3Y');
INSERT INTO LOAN_DETAIL (LOAN_NO, CUST_NO, LOAN_BALANCE, LOAN_AMOUNT, LOAN_TERM) VALUES ('LOAN007', 'CUST06', 450000.00, 600000.00, '5Y');
INSERT INTO LOAN_DETAIL (LOAN_NO, CUST_NO, LOAN_BALANCE, LOAN_AMOUNT, LOAN_TERM) VALUES ('LOAN008', 'CUST07', 800000.00, 1000000.00, '5Y+');

COMMIT;

-- 清空汇总表
TRUNCATE TABLE DWS_DEPOSIT_TERM_SUM;
TRUNCATE TABLE DWS_LOAN_TERM_SUM;

-- 存款 ETL
INSERT INTO DWS_DEPOSIT_TERM_SUM (STAT_DATE, TERM_INTERVAL, INTERVAL_ORDER, TOTAL_BALANCE, ACCOUNT_COUNT, UPDATE_TIME)
SELECT
    TRUNC(SYSDATE) AS STAT_DATE,
    M.TERM_INTERVAL,
    M.INTERVAL_ORDER,
    SUM(D.ACCOUNT_BALANCE) AS TOTAL_BALANCE,
    COUNT(DISTINCT D.ACCOUNT_NO) AS ACCOUNT_COUNT,
    SYSDATE AS UPDATE_TIME
FROM DEPOSIT_ACCOUNT_DETAIL D
INNER JOIN DIM_TERM_MAPPING M
    ON D.DEPOSIT_TERM = M.SOURCE_TERM
    AND M.TERM_TYPE = 'DEPOSIT'
GROUP BY M.TERM_INTERVAL, M.INTERVAL_ORDER;

-- 贷款 ETL
INSERT INTO DWS_LOAN_TERM_SUM (STAT_DATE, TERM_INTERVAL, INTERVAL_ORDER, TOTAL_BALANCE, LOAN_COUNT, UPDATE_TIME)
SELECT
    TRUNC(SYSDATE) AS STAT_DATE,
    M.TERM_INTERVAL,
    M.INTERVAL_ORDER,
    SUM(L.LOAN_BALANCE) AS TOTAL_BALANCE,
    COUNT(DISTINCT L.LOAN_NO) AS LOAN_COUNT,
    SYSDATE AS UPDATE_TIME
FROM LOAN_DETAIL L
INNER JOIN DIM_TERM_MAPPING M
    ON L.LOAN_TERM = M.SOURCE_TERM
    AND M.TERM_TYPE = 'LOAN'
GROUP BY M.TERM_INTERVAL, M.INTERVAL_ORDER;

COMMIT;

-- 存款汇总表(应有 7 行)
SELECT * FROM DWS_DEPOSIT_TERM_SUM ORDER BY INTERVAL_ORDER;

-- 贷款汇总表(应有 7 行)
SELECT * FROM DWS_LOAN_TERM_SUM ORDER BY INTERVAL_ORDER;

-- 最终报表
SELECT 
    COALESCE(D.TERM_INTERVAL, L.TERM_INTERVAL) AS "项目",
    NVL(D.TOTAL_BALANCE, 0) AS "各项存款",
    NVL(L.TOTAL_BALANCE, 0) AS "各项贷款(余额)",
    COALESCE(D.INTERVAL_ORDER, L.INTERVAL_ORDER) AS "排序"
FROM 
    DWS_DEPOSIT_TERM_SUM D
FULL OUTER JOIN 
    DWS_LOAN_TERM_SUM L
    ON D.TERM_INTERVAL = L.TERM_INTERVAL
ORDER BY "排序";


存贷款明细报表练习问题排查与解决总结


一、问题概览

在完成本次练习过程中,主要遇到了以下几类问题:

问题类型 具体表现 严重程度
表不存在 ORA-00942: 表或视图不存在 🔴 严重
数据插入0行 ETL执行后DWS表为空 🔴 严重
查询结果不完整 只显示2行,应有7行 🟡 中等
数据错位 期限字段存储了账号值 🟡 中等
映射表缺失 部分期限无法关联 🟢 轻微

二、详细问题排查与解决

问题1:ORA-00942 表或视图不存在

错误信息:

text

ORA-00942: table or view does not exist

原因分析:

  • 执行查询时,表还没有创建

  • 表创建在不同的用户/模式下

  • 表名拼写错误

排查步骤:

sql

-- 查看当前用户下所有表
SELECT table_name FROM user_tables;

-- 查看所有可访问的表
SELECT table_name FROM all_tables WHERE owner = '当前用户名';

解决方法:

  1. 按依赖顺序创建表(先创建DWD层,再创建DIM层,最后创建DWS层)

  2. 确保在同一用户下执行所有SQL

  3. 查询时使用正确的表名(Oracle默认大写)


问题2:ETL执行后插入0行

错误信息:

text

0 rows inserted

原因分析:

  • 明细表中没有数据

  • 映射表中没有对应的期限值

  • 关联条件不匹配(大小写、空格)

  • GROUP BY 子句使用不当

排查步骤:

步骤1:检查明细表是否有数据

sql

SELECT COUNT(*) FROM DEPOSIT_ACCOUNT_DETAIL;
SELECT COUNT(*) FROM LOAN_DETAIL;

步骤2:检查映射表是否有对应值

sql

-- 查看明细表中的所有期限
SELECT DISTINCT DEPOSIT_TERM FROM DEPOSIT_ACCOUNT_DETAIL;

-- 查看映射表中的所有期限
SELECT DISTINCT SOURCE_TERM FROM DIM_TERM_MAPPING WHERE TERM_TYPE = 'DEPOSIT';

步骤3:检查关联是否成功

sql

-- 使用LEFT JOIN找出无法关联的记录
SELECT 
    D.DEPOSIT_TERM,
    M.SOURCE_TERM,
    CASE WHEN M.SOURCE_TERM IS NULL THEN '❌ 匹配失败' ELSE '✅ 匹配成功' END AS 状态
FROM DEPOSIT_ACCOUNT_DETAIL D
LEFT JOIN DIM_TERM_MAPPING M 
    ON D.DEPOSIT_TERM = M.SOURCE_TERM 
    AND M.TERM_TYPE = 'DEPOSIT';

步骤4:检查GROUP BY是否正确

sql

-- Oracle要求:SELECT中的非聚合列必须在GROUP BY中
-- ✅ 正确写法
GROUP BY M.TERM_INTERVAL, M.INTERVAL_ORDER

-- ❌ 错误写法
GROUP BY M.TERM_INTERVAL  -- 如果SELECT中有INTERVAL_ORDER会报错

解决方法:

  • 补全映射表中缺失的期限值

  • 确保关联字段的格式一致(去除空格、统一大小写)

  • 使用 INNER JOIN 测试关联结果

  • 修正 GROUP BY 子句


问题3:数据错位(期限字段存了账号值)

错误表现:

text

SELECT DEPOSIT_TERM FROM DEPOSIT_ACCOUNT_DETAIL;
-- 返回结果:ACC001, ACC002, ACC003...(应该是1Y、3Y等)

原因分析:

  • INSERT语句没有指定列名,且字段顺序与建表语句不一致

错误写法:

sql

-- 建表顺序:ACCOUNT_NO, CUST_NO, ACCOUNT_BALANCE, DEPOSIT_TERM
-- 但插入时顺序错误
INSERT INTO DEPOSIT_ACCOUNT_DETAIL VALUES ('1Y', 'CUST01', 100000, 'ACC001', SYSDATE);
-- 结果:1Y被存入了ACCOUNT_NO,ACC001被存入了DEPOSIT_TERM

解决方法:

sql

-- ✅ 正确写法:明确指定列名
INSERT INTO DEPOSIT_ACCOUNT_DETAIL (ACCOUNT_NO, CUST_NO, ACCOUNT_BALANCE, DEPOSIT_TERM)
VALUES ('ACC001', 'CUST01', 100000.00, '1Y');

最佳实践: 所有INSERT语句都显式指定列名,避免依赖字段顺序。


问题4:查询结果不完整(只有2行)

错误表现:

text

| 项目 | 各项存款 | 各项贷款(余额) |
| 六个月至一年 | 200000 | 300000 |
| 两年至三年 | 100000 | 400000 |
-- 缺少其他5个区间

原因分析:

  • 明细表中缺少其他期限的测试数据

  • 映射表缺少对应期限的映射

  • ETL执行不完整

排查步骤:

步骤1:确认明细表数据分布

sql

SELECT DEPOSIT_TERM, COUNT(*), SUM(ACCOUNT_BALANCE) 
FROM DEPOSIT_ACCOUNT_DETAIL 
GROUP BY DEPOSIT_TERM 
ORDER BY DEPOSIT_TERM;

步骤2:确认映射表覆盖情况

sql

SELECT TERM_INTERVAL, COUNT(*) 
FROM DIM_TERM_MAPPING 
WHERE TERM_TYPE = 'DEPOSIT' 
GROUP BY TERM_INTERVAL;
-- 应该显示7行

步骤3:确认DWS表数据

sql

SELECT * FROM DWS_DEPOSIT_TERM_SUM ORDER BY INTERVAL_ORDER;
-- 应该有7行

解决方法:

  1. 补全所有期限的测试数据

  2. 确保映射表包含所有7个区间的映射

  3. 重新执行完整的ETL流程


问题5:映射表数据重复插入提示0行

表现:

text

0 rows inserted

原因分析:

  • 主键冲突:PRIMARY KEY (SOURCE_TERM, TERM_TYPE) 约束

  • 数据已存在,使用了 INSERT 而不是 INSERT OR UPDATE

解决方法:

方法1:使用MERGE(推荐)

sql

MERGE INTO DIM_TERM_MAPPING M
USING (SELECT '1Y' AS SOURCE_TERM, 'DEPOSIT' AS TERM_TYPE FROM DUAL) S
ON (M.SOURCE_TERM = S.SOURCE_TERM AND M.TERM_TYPE = S.TERM_TYPE)
WHEN NOT MATCHED THEN
    INSERT (SOURCE_TERM, TERM_TYPE, TERM_INTERVAL, INTERVAL_ORDER)
    VALUES ('1Y', 'DEPOSIT', '六个月至一年', 5);

方法2:使用NOT EXISTS(兼容性好)

sql

INSERT INTO DIM_TERM_MAPPING (SOURCE_TERM, TERM_TYPE, TERM_INTERVAL, INTERVAL_ORDER)
SELECT '1Y', 'DEPOSIT', '六个月至一年', 5 FROM DUAL
WHERE NOT EXISTS (
    SELECT 1 FROM DIM_TERM_MAPPING 
    WHERE SOURCE_TERM = '1Y' AND TERM_TYPE = 'DEPOSIT'
);

三、问题排查方法论总结

3.1 排查流程图

text

开始
  ↓
问题出现
  ↓
确认错误类型 ←──┐
  ↓              │
ORA-00942?       │
  ↓ YES          │
检查表是否存在 ──┘
  ↓ NO
创建缺失的表
  ↓
插入0行?
  ↓ YES
检查明细表数据 → 无数据 → 插入测试数据
  ↓ 有数据
检查映射表关联 → 关联失败 → 补全映射表/检查格式
  ↓ 关联成功
检查GROUP BY → 语法错误 → 修正GROUP BY
  ↓ 正确
查询结果不完整?
  ↓ YES
检查数据分布 → 数据不全 → 补全测试数据
  ↓ 数据完整
问题解决

3.2 核心排查SQL速查表

排查目的 SQL语句
查看所有表 SELECT table_name FROM user_tables;
查看明细表数据量 SELECT COUNT(*) FROM 表名;
查看字段值分布 SELECT 字段, COUNT(*) FROM 表名 GROUP BY 字段;
检查关联是否成功 LEFT JOIN + CASE WHEN ... IS NULL
查看映射表覆盖情况 SELECT * FROM 映射表 ORDER BY INTERVAL_ORDER;
验证DWS汇总结果 SELECT * FROM DWS表 ORDER BY INTERVAL_ORDER;

3.3 关键经验教训

经验 说明
指定列名 所有INSERT语句都显式指定列名,避免字段顺序错误
先验证再ETL 先用SELECT验证关联结果,确认无误后再执行INSERT
逐步构建 不要一次性执行大段SQL,分步执行并验证中间结果
使用LEFT JOIN定位问题 当关联失败时,LEFT JOIN能快速找出无法匹配的记录
维护数据字典 保持映射表的完整性,确保覆盖所有业务值
事务提交 执行INSERT后记得COMMIT

四、最终检查清单

在提交练习前,请确认以下事项:

  • 所有表都已成功创建

  • 映射表包含全部7个期限区间

  • 测试数据覆盖全部期限区间

  • ETL执行后DWS表各有7行数据

  • 最终报表输出7行数据

  • 存款和贷款金额与测试数据一致

  • 所有SQL都已COMMIT


五、一句话总结

ETL调试的核心是分层验证:先验明细表 → 再验映射关联 → 最后验汇总聚合,每一步用SELECT确认后再执行INSERT。

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐