Bài 7 — Dữ liệu ngân hàng cho Data team
Bài 7 — Dữ liệu ngân hàng cho Data team
Nếu bạn là Data Engineer hay Data Analyst mới bước chân vào ngân hàng, điều khiến bạn choáng ngợp đầu tiên không phải là khối lượng dữ liệu, mà là đường đi của dữ liệu. Một con số "dư nợ cuối ngày" mà sếp nhìn trên dashboard buổi sáng đã đi qua bốn, năm tầng hệ thống trước khi đến mắt người ra quyết định. Hiểu được hành trình đó là điều kiện cần để bạn làm đúng việc: lấy đúng số, đối soát đúng chỗ, và giải thích được khi số liệu "lệch".
Bài này đi từ con số không: dòng chảy dữ liệu trong ngân hàng, các thực thể cốt lõi, mô hình kho dữ liệu (DWH) kiểu Kimball, vì sao phải chụp ảnh số dư cuối ngày, cách đối soát chất lượng dữ liệu, các báo cáo điển hình — và một bộ ví dụ SQL chạy được ngay trên SQL Builder sandbox để bạn luyện tay.
Bức tranh tổng thể: dữ liệu đi từ đâu đến đâu
Trong một ngân hàng, hệ thống nguồn chính là Core Banking — nơi ghi nhận mọi giao dịch, tài khoản, khách hàng theo thời gian thực (hoặc gần thực). Nhưng bạn gần như không bao giờ chạy báo cáo trực tiếp trên core, vì hai lý do: (1) core phải phục vụ giao dịch nghiệp vụ, một truy vấn báo cáo nặng có thể làm chậm cả hệ thống ATM/Internet Banking; (2) cấu trúc dữ liệu trong core được tối ưu cho ghi (OLTP), không phải cho phân tích (OLAP).
Vì vậy dữ liệu được "kéo" ra theo từng tầng:
Core Banking — nguồn sự thật
Core là "single source of truth" của nghiệp vụ. Mọi thay đổi số dư, mọi bút toán đều phát sinh ở đây. Với Data team, core là nơi cuối cùng để đối chiếu khi nghi ngờ số liệu, nhưng là nơi đầu tiên mà dữ liệu sinh ra.
EOD extract — trích xuất cuối ngày
EOD (End Of Day) là quy trình khoá sổ cuối ngày của ngân hàng. Sau khi core hoàn tất EOD (tính lãi, hạch toán phí, cập nhật số dư), hệ thống sẽ trích xuất dữ liệu của ngày đó — thường dưới dạng file hoặc bảng staging. Đây là thời điểm "ảnh chụp" số liệu đáng tin cậy nhất: mọi giao dịch trong ngày đã được hạch toán xong.
ODS — Operational Data Store
ODS là tầng dữ liệu gần như bản sao của core, đã được gom từ nhiều hệ thống nguồn (core, thẻ, treasury, LOS — hệ thống khởi tạo khoản vay...) về một nơi. Đặc điểm của ODS:
- Dữ liệu chi tiết, gần thời gian thực hoặc cập nhật theo lô ngắn.
- Cấu trúc còn khá giống nguồn (chuẩn hoá cao, ít biến đổi).
- Phục vụ truy vấn vận hành: tra cứu giao dịch, đối soát, phục vụ phòng nghiệp vụ.
ODS giúp giảm tải cho core và là "vùng đệm" để làm sạch trước khi đưa vào kho.
DWH — Data Warehouse (mô hình Kimball/sao)
DWH là nơi dữ liệu được mô hình hoá lại theo hướng phân tích, thường dùng mô hình sao (star schema) kiểu Kimball: một bảng fact ở trung tâm, bao quanh là các bảng dimension. Dữ liệu ở đây đã được biến đổi (transform), chuẩn hoá nghiệp vụ, lịch sử hoá (giữ lại biến đổi theo thời gian).
Data Mart & BI
Data Mart là tập con của DWH, được tổng hợp sẵn cho một chủ đề/phòng ban (ví dụ mart cho khối Bán lẻ, mart cho Quản trị rủi ro). Dữ liệu ở đây thường đã aggregate để dashboard load nhanh. Tầng BI (Power BI, Tableau, Superset...) đọc từ mart để dựng báo cáo.
Báo cáo: quản trị và tuân thủ
Cuối đường ống là hai loại báo cáo:
- Báo cáo quản trị (MIS): phục vụ điều hành — huy động, dư nợ, lợi nhuận theo chi nhánh/sản phẩm.
- Báo cáo tuân thủ NHNN: các mẫu biểu bắt buộc gửi Ngân hàng Nhà nước (cân đối tài khoản, phân loại nợ, tỷ lệ an toàn vốn...). Loại này thường lấy thẳng từ DWH/GL vì yêu cầu chính xác tuyệt đối và phải truy vết được.
Các thực thể dữ liệu cốt lõi
Dù core của ngân hàng nào cũng có hàng nghìn bảng, có một nhóm thực thể "xương sống" mà bất kỳ Data team nào cũng phải nắm:
| Thực thể | Mô tả | Vai trò |
|---|---|---|
| Customer (CIF) | Hồ sơ thông tin khách hàng, định danh bằng số CIF (Customer Information File) | Trung tâm 360 độ về khách hàng |
| Account | Tài khoản (tiền gửi, vãng lai, tiết kiệm...) | Gắn với một CIF, có số dư |
| Transaction | Giao dịch ghi nợ/ghi có trên tài khoản | Phát sinh dòng tiền |
| Loan | Khoản vay, lịch trả nợ, nhóm nợ | Nguồn dư nợ & rủi ro tín dụng |
| GL Balance | Số dư sổ cái kế toán (General Ledger) | Nguồn của báo cáo tài chính & cân đối |
Quan hệ khoá cơ bản (theo chiều một-nhiều):
Đọc sơ đồ: một khách hàng (CIF) có nhiều tài khoản; một tài khoản có nhiều giao dịch; một khách hàng có thể có nhiều khoản vay. Đây là nền tảng để bạn JOIN dữ liệu đúng cách. Sai khoá nối là sai số, không cứu được.
Dimension và Fact: hai loại bảng trong kho
Trong mô hình Kimball, mọi bảng quy về hai loại:
- Dimension (chiều): mô tả ai, cái gì, ở đâu, khi nào — ví dụ
dim_customer,dim_branch,dim_product,dim_date. Dimension trả lời câu hỏi "cắt lát theo gì". Chúng tương đối tĩnh và mang thuộc tính mô tả (tên, thành phố, loại sản phẩm). - Fact (sự kiện/độ đo): chứa con số đo lường — số tiền giao dịch, số dư, doanh số. Ví dụ
fact_transaction,fact_balance_daily. Fact thường rất lớn và chỉ chứa khoá ngoại trỏ về dimension cùng các cột số (measures).
Một báo cáo điển hình = lấy measure từ fact, group by các thuộc tính của dimension. Ví dụ: tổng số tiền giao dịch (fact) theo chi nhánh (dimension) theo tháng (dim_date).
Snapshot số dư cuối ngày — vì sao bắt buộc
Đây là khái niệm khiến nhiều người mới bối rối. Số dư tài khoản trong core là một giá trị thay đổi liên tục — bạn nhìn lúc 9h sáng khác lúc 5h chiều. Nhưng báo cáo cần một con số ổn định, có thể tái lập: "huy động ngày 30/06 là bao nhiêu?".
Giải pháp là daily balance snapshot — mỗi cuối ngày, sau EOD, ghi lại số dư của mọi tài khoản vào bảng fact dạng snapshot, kèm cột ngày. Bảng fact_balance_daily sẽ có dạng: mỗi dòng = một tài khoản × một ngày × số dư cuối ngày.
Lợi ích:
- Tái lập được lịch sử: muốn biết dư nợ ngày bất kỳ trong quá khứ, chỉ cần lọc theo ngày — không cần "tua lại" giao dịch.
- Tính biến động dễ dàng: so sánh huy động hôm nay với hôm qua, đầu tháng với cuối tháng.
- Đối soát: tổng snapshot phải khớp với cân đối GL của ngày đó.
Cái giá phải trả là dung lượng: nếu có 10 triệu tài khoản, mỗi ngày sinh thêm 10 triệu dòng. Nên thường người ta partition theo ngày/tháng và áp dụng chính sách lưu trữ.
Chất lượng dữ liệu & đối soát số dư
Một Data team trong ngân hàng dành phần lớn thời gian không phải để "phân tích hay", mà để đảm bảo số liệu đúng. Vài nguyên tắc:
- Đối soát core vs DWH: tổng số dư từ snapshot DWH phải khớp tổng số dư trên core (theo loại tiền, theo chi nhánh). Lệch dù 1 đồng cũng phải tìm ra nguyên nhân — thường do giao dịch trễ, do múi giờ cắt EOD, hoặc do bút toán điều chỉnh.
- Đối soát với GL: số dư chi tiết (theo tài khoản) phải khớp số dư tổng hợp trên sổ cái. Đây là "phép thử vàng".
- Kiểm tra toàn vẹn khoá: mọi
accountphải trỏ về mộtcustomertồn tại; mọitransactionphải trỏ về mộtaccounttồn tại (không có "mồ côi"). - Kiểm tra giá trị bất thường: số dư âm bất thường, giao dịch số tiền bằng 0, ngày tạo ở tương lai.
- Tính đầy đủ (completeness): số bản ghi EOD hôm nay có hợp lý so với hôm qua không (không thiếu file, không trùng lô).
Khi báo cáo "lệch", quy trình điều tra thường là: BI ↛ Mart ↛ DWH ↛ ODS ↛ Core, lần ngược về nguồn cho đến khi tìm ra tầng làm sai.
Các báo cáo điển hình bạn sẽ gặp
- Huy động (deposits) theo chi nhánh: tổng số dư tiền gửi, cắt theo chi nhánh/sản phẩm/kỳ hạn.
- Dư nợ (loan outstanding) theo chi nhánh: tổng dư nợ cho vay, cắt theo nhóm nợ, sản phẩm.
- CASA ratio: tỷ lệ tiền gửi không kỳ hạn (Current Account + Savings Account) trên tổng huy động. CASA cao = chi phí vốn rẻ, là chỉ số ngân hàng nào cũng theo dõi sát.
- NPL (Non-Performing Loan): tỷ lệ nợ xấu = dư nợ nhóm 3-4-5 / tổng dư nợ. Chỉ số rủi ro tín dụng cốt lõi.
- Khách hàng mới: số CIF mới mở trong kỳ, cắt theo kênh, chi nhánh.
- Churn: khách hàng rời bỏ — tài khoản đóng, hoặc không phát sinh giao dịch trong N tháng (định nghĩa churn tùy ngân hàng).
Tất cả những báo cáo này, về bản chất SQL, đều là aggregate một measure theo một vài dimension. Giờ ta thực hành.
Bộ ví dụ SQL thực hành (chạy được trong SQL Builder)
Các ví dụ dưới đây dùng schema sandbox đơn giản hoá, chỉ để minh hoạ kỹ thuật SQL. Schema thật của ngân hàng phức tạp hơn nhiều (hàng nghìn bảng, lịch sử hoá, nhiều loại số dư). Schema sandbox:
departments(id, name)employees(id, name, department_id, salary, hired_at)customers(id, full_name, city, created_at)accounts(id, customer_id, account_no, balance, currency)transactions(id, account_id, amount, kind ['credit'|'debit'], created_at)
Tất cả các khối chỉ dùng SELECT và chỉ chạm vào các bảng trên.
Ví dụ 1 — Tổng số dư & số tài khoản theo loại tiền tệ
SELECT
currency,
COUNT(*) AS so_tai_khoan,
SUM(balance) AS tong_so_du
FROM accounts
GROUP BY currency
ORDER BY tong_so_du DESC;
▶ Chạy được trong SQL Builder
Ý nghĩa nghiệp vụ: đây là phép tính "huy động theo loại tiền" ở dạng đơn giản nhất. Trong thực tế bạn sẽ thấy VND chiếm phần lớn, USD và các ngoại tệ khác chiếm phần nhỏ. Báo cáo huy động luôn phải tách theo currency vì không thể cộng VND với USD.
Ví dụ 2 — Top khách hàng theo tổng số dư
SELECT
c.id,
c.full_name,
c.city,
SUM(a.balance) AS tong_so_du
FROM customers c
JOIN accounts a ON a.customer_id = c.id
GROUP BY c.id, c.full_name, c.city
ORDER BY tong_so_du DESC
LIMIT 10;
▶ Chạy được trong SQL Builder
Ý nghĩa nghiệp vụ: đây là logic xếp hạng khách hàng VIP/ưu tiên (priority banking). Ngân hàng dựa vào tổng số dư để phân tầng khách hàng, gán chính sách chăm sóc, hạn mức ưu đãi. Lưu ý phép JOIN nối accounts về customers theo đúng khoá customer_id — đúng quan hệ 1 khách hàng → nhiều tài khoản.
Ví dụ 3 — Dòng tiền ra/vào theo tài khoản
SELECT
a.account_no,
a.currency,
SUM(CASE WHEN t.kind = 'credit' THEN t.amount ELSE 0 END) AS tien_vao,
SUM(CASE WHEN t.kind = 'debit' THEN t.amount ELSE 0 END) AS tien_ra,
SUM(CASE WHEN t.kind = 'credit' THEN t.amount ELSE 0 END)
- SUM(CASE WHEN t.kind = 'debit' THEN t.amount ELSE 0 END) AS dong_tien_rong
FROM accounts a
JOIN transactions t ON t.account_id = a.id
GROUP BY a.account_no, a.currency
ORDER BY dong_tien_rong DESC;
▶ Chạy được trong SQL Builder
Ý nghĩa nghiệp vụ: đây là kỹ thuật pivot có điều kiện — tách một cột kind thành hai cột tiền vào/tiền ra bằng SUM(CASE WHEN ...). Dòng tiền ròng (net cash flow) cho biết tài khoản đang tích lũy hay rút ròng. Đây là nền tảng của báo cáo dòng tiền và mô hình cảnh báo sớm (tài khoản rút ròng lớn bất thường có thể là dấu hiệu churn hoặc rủi ro).
Ví dụ 4 — Phân bố khách hàng theo thành phố
SELECT
city,
COUNT(*) AS so_khach_hang
FROM customers
GROUP BY city
ORDER BY so_khach_hang DESC;
▶ Chạy được trong SQL Builder
Ý nghĩa nghiệp vụ: phân bố địa lý khách hàng giúp khối Bán lẻ quyết định mở/đóng phòng giao dịch, phân bổ nguồn lực marketing. Đây cũng là dimension điển hình (dim_customer.city) để cắt lát mọi báo cáo khác.
Ví dụ 5 (Nâng cao) — Xếp hạng tài khoản theo số dư trong từng loại tiền tệ
SELECT
account_no,
currency,
balance,
RANK() OVER (PARTITION BY currency ORDER BY balance DESC) AS hang_trong_loai_tien
FROM accounts
ORDER BY currency, hang_trong_loai_tien;
▶ Chạy được trong SQL Builder
Ý nghĩa nghiệp vụ: đây là window function — sức mạnh thật sự của SQL phân tích. PARTITION BY currency chia dữ liệu thành các nhóm theo loại tiền, rồi RANK() xếp hạng số dư trong từng nhóm. Khác với GROUP BY (gộp dòng lại), window function giữ nguyên từng dòng nhưng thêm cột xếp hạng. Trong nghiệp vụ, đây là cách tìm "tài khoản lớn nhất trong từng loại tiền", hoặc làm top-N theo từng phân khúc. Nếu muốn lấy top 3 mỗi loại tiền, bạn bọc truy vấn này trong một subquery rồi lọc WHERE hang_trong_loai_tien <= 3.
Gợi ý mở rộng cho người muốn đi xa hơn
Khi đã quen, hãy thử ghép nhiều kỹ thuật: ví dụ tính số dư trung bình theo thành phố và so sánh với trung bình toàn hệ thống bằng window function, hoặc kết hợp transactions với customers qua hai lần JOIN để xem dòng tiền theo thành phố. Mọi báo cáo ngân hàng phức tạp đều xây từ chính những viên gạch JOIN / GROUP BY / CASE WHEN / window function này.
Tóm tắt
Dữ liệu ngân hàng chảy theo một đường ống có kỷ luật: Core Banking → EOD extract → ODS → Data Warehouse → Data Mart/BI → báo cáo quản trị & tuân thủ NHNN. Bạn không chạy báo cáo trên core mà trên kho dữ liệu được mô hình hoá theo Kimball, với hai loại bảng cốt lõi: dimension (cắt lát: ai, đâu, khi nào) và fact (con số đo lường). Các thực thể xương sống là Customer (CIF) → Account → Transaction, cùng Loan và GL Balance, nối với nhau theo quan hệ một-nhiều. Snapshot số dư cuối ngày cho phép tái lập lịch sử và đối soát. Phần lớn công việc thực tế là đảm bảo chất lượng và đối soát core ↔ DWH ↔ GL. Về SQL, mọi báo cáo (huy động, dư nợ, CASA, NPL, churn) đều quy về aggregate measure theo dimension bằng JOIN / GROUP BY / CASE WHEN / window function.
Tự kiểm tra
- Vì sao Data team gần như không bao giờ chạy báo cáo trực tiếp trên Core Banking?
- Phân biệt ODS và DWH: mỗi tầng phục vụ mục đích gì và khác nhau ở điểm nào?
- Bảng dimension và bảng fact khác nhau ra sao? Một báo cáo điển hình lấy gì từ fact và group by gì từ dimension?
- Tại sao phải có snapshot số dư cuối ngày thay vì đọc số dư trực tiếp? Cái giá phải trả là gì?
- Viết câu SQL (chỉ dùng schema sandbox) tính dòng tiền ròng cho mỗi tài khoản — gợi ý dùng
SUM(CASE WHEN kind = ...). - Window function khác
GROUP BYở điểm cốt lõi nào, và khi nào bạn bắt buộc phải dùng window function?
Đọc tiếp
- Quay lại nền tảng: Bài 1 — Ngân hàng vận hành thế nào
- Luyện thêm SQL: hãy mở chuyên mục SQL Builder và chạy lại từng khối ví dụ ở trên, sau đó tự biến tấu thêm điều kiện
WHERE, đổi cộtGROUP BY, hoặc thử ghép window function vào báo cáo của riêng bạn.
Bài viết liên quan
Mô hình dữ liệu giao dịch thẻ, các KPI thẻ, phân tích chi tiêu & phát hiện gian lận, kèm ví dụ SQL chạy được trên sandbox.
Bản chất kinh doanh của ngân hàng: trung gian tài chính, bảng cân đối, NIM và vì sao dữ liệu quan trọng.
Kiến trúc core banking, CIF, các phân hệ và xử lý online vs batch/EOD.
Nguyên lý hạch toán kép, Nợ/Có, hệ thống tài khoản và cách mọi giao dịch ngân hàng luôn cân sổ.