
SQL cho kho dữ liệu tài chính
Truy vấn, tổng hợp và phân tích dữ liệu tài chính có cấu trúc
Vì sao SQL là ngôn ngữ của dữ liệu tài chính
Mỗi giao dịch, mỗi tick, mỗi cập nhật sổ lệnh — thị trường tài chính tạo ra lượng dữ liệu có cấu trúc khổng lồ. Và trong suốt hơn bốn thập kỷ, SQL (Structured Query Language — ngôn ngữ truy vấn có cấu trúc) vẫn là công cụ chủ đạo để truy vấn, chuyển đổi, và phân tích dữ liệu đó. Nó không hào nhoáng. Nó không thời thượng. Nhưng khi một nhà quản lý danh mục hỏi “giá khớp trung bình của tôi cho các giao dịch ETH thứ Ba tuần trước giữa 2 giờ và 3 giờ chiều UTC là bao nhiêu?”, câu trả lời đến từ SQL.
Dữ liệu tài chính về bản chất là quan hệ (relational). Một giao dịch tham chiếu đến một lệnh, lệnh đó tham chiếu đến một tài khoản, tài khoản đó thuộc về một người dùng. Vị thế liên quan đến công cụ tài chính; công cụ tài chính liên quan đến thị trường. Mô hình quan hệ — các bảng gồm hàng và cột, liên kết bằng khóa ngoại — khớp một cách tự nhiên với các mối quan hệ này. Đây là lý do các cơ sở dữ liệu quan hệ như PostgreSQL, MySQL, và SQL Server vẫn là xương sống của hạ tầng tài chính, từ Goldman Sachs đến GaiaEx.
Sức mạnh của SQL nằm ở tính khai báo (declarative): bạn mô tả điều bạn muốn, không phải cách để đạt được nó. Cơ chế của cơ sở dữ liệu tự tìm ra kế hoạch thực thi tối ưu. Điều này quan trọng vô cùng khi bảng lịch sử giao dịch của bạn có 500 triệu hàng và bạn cần kết quả trong chưa đầy một giây. Viết đúng câu truy vấn, xây đúng chỉ mục (index), và SQL sẽ đáp ứng — bất kể bạn đang chạy nó trên một instance PostgreSQL cục bộ hay một data warehouse quy mô đám mây.
SELECT, JOIN, và WHERE: truy vấn dữ liệu giao dịch
Hãy bắt đầu với một schema cụ thể. Hãy tưởng tượng một bảng trades trên một nền tảng như GaiaEx:
CREATE TABLE trades (
trade_id BIGINT PRIMARY KEY,
user_id INT NOT NULL,
symbol VARCHAR(20) NOT NULL,
side VARCHAR(4) NOT NULL, -- 'buy' or 'sell'
price NUMERIC(18,8) NOT NULL,
quantity NUMERIC(18,8) NOT NULL,
fee NUMERIC(18,8) DEFAULT 0,
executed_at TIMESTAMPTZ NOT NULL
);
Thao tác cơ bản nhất là câu lệnh SELECT kèm điều kiện lọc. Để tìm tất cả các giao dịch mua ETH-USD trên 3.000 đô la trong 24 giờ qua:
SELECT trade_id, price, quantity, executed_at
FROM trades
WHERE symbol = 'ETH-USD'
AND side = 'buy'
AND price > 3000
AND executed_at > NOW() - INTERVAL '24 hours'
ORDER BY executed_at DESC;
JOIN kết nối các bảng có liên quan. Giả sử bạn có một bảng accounts và muốn xem khối lượng giao dịch theo từng hạng tài khoản (tier):
SELECT a.tier, COUNT(*) AS trade_count,
SUM(t.price * t.quantity) AS total_volume
FROM trades t
JOIN accounts a ON t.user_id = a.user_id
WHERE t.executed_at > NOW() - INTERVAL '30 days'
GROUP BY a.tier
ORDER BY total_volume DESC;
Những câu truy vấn này là công việc thường ngày của các nhà phân tích tài chính, các đội rủi ro, và các cán bộ tuân thủ. Nắm vững chúng và bạn có thể trả lời bất kỳ câu hỏi nào dữ liệu của bạn chứa đựng.
Window function: vũ khí bí mật của nhà phân tích
Window function là điều phân biệt người mới học SQL với người thực hành SQL chuyên sâu trong tài chính. Chúng cho phép bạn thực hiện các phép tính trên một tập hàng liên quan đến hàng hiện tại — mà không thu gọn kết quả thành một giá trị tổng hợp duy nhất. Hãy nghĩ về chúng như “các phép tính chạy liên tục” trên dữ liệu của bạn.
Các window function được dùng phổ biến nhất trong phân tích tài chính:
- ROW_NUMBER() — Gán một số nguyên tuần tự cho mỗi hàng trong một phân vùng (partition). Hữu ích cho việc khử trùng lặp hoặc chọn giao dịch thứ N cho mỗi symbol.
- LAG() và LEAD() — Truy cập giá trị của hàng trước hoặc hàng sau. Thiết yếu để tính lợi nhuận giữa các giao dịch liên tiếp hoặc phát hiện khoảng trống trong dữ liệu chuỗi thời gian.
- SUM() OVER () — Tổng chạy (running total). Tính khối lượng lũy kế, lãi/lỗ (P&L) lũy kế, hoặc kích thước vị thế chạy.
- AVG() OVER (ROWS BETWEEN) — Trung bình động ngay trong SQL. Không cần Python.
Đây là một ví dụ thực tế — tính sự thay đổi giá giữa các giao dịch liên tiếp và vị thế chạy cho BTC:
SELECT executed_at, price, quantity, side,
price - LAG(price) OVER (ORDER BY executed_at) AS price_change,
SUM(CASE WHEN side = 'buy' THEN quantity ELSE -quantity END)
OVER (ORDER BY executed_at) AS running_position
FROM trades
WHERE symbol = 'BTC-USD'
ORDER BY executed_at;
Window function không làm giảm số hàng của bạn — không giống GROUP BY, mỗi hàng đầu vào tạo ra một hàng đầu ra. Điều này khiến chúng hoàn hảo để thêm các cột phân tích vào log giao dịch chi tiết mà không làm mất đi độ chi tiết. Hãy nghĩ về chúng như đang “làm giàu” dữ liệu của bạn hơn là “tóm tắt” nó.
Đối với việc tổng hợp OHLCV — nền tảng của biểu đồ nến — bạn có thể kết hợp GROUP BY với việc chia khối theo thời gian (time-bucketing) và các hàm tổng hợp chuẩn:
SELECT date_trunc('hour', executed_at) AS bucket,
(ARRAY_AGG(price ORDER BY executed_at))[1] AS open,
MAX(price) AS high,
MIN(price) AS low,
(ARRAY_AGG(price ORDER BY executed_at DESC))[1] AS close,
SUM(quantity) AS volume
FROM trades
WHERE symbol = 'ETH-USD'
GROUP BY bucket
ORDER BY bucket;CTE: kết hợp các câu truy vấn phân tích phức tạp
Common Table Expression (CTE — biểu thức bảng chung) cho phép bạn tách một câu truy vấn phức tạp thành các bước có tên, dễ đọc — giống như các hàm trong lập trình. Chúng được khai báo bằng từ khóa WITH và có thể tham chiếu lẫn nhau theo trình tự.
Giả sử bạn muốn xác định 10 ngày giao dịch có lợi nhuận cao nhất của mình, nhưng lợi nhuận đòi hỏi phải tính lãi/lỗ ròng mỗi ngày bao gồm cả phí. Với CTE, bạn có thể xây dựng điều này từng bước:
WITH daily_trades AS (
SELECT DATE(executed_at) AS trade_date,
SUM(CASE WHEN side = 'sell' THEN price * quantity
ELSE -price * quantity END) AS gross_pnl,
SUM(fee) AS total_fees
FROM trades
WHERE user_id = 42
GROUP BY DATE(executed_at)
),
daily_pnl AS (
SELECT trade_date,
gross_pnl - total_fees AS net_pnl,
SUM(gross_pnl - total_fees) OVER (ORDER BY trade_date) AS cumulative_pnl
FROM daily_trades
)
SELECT trade_date, net_pnl, cumulative_pnl
FROM daily_pnl
ORDER BY net_pnl DESC
LIMIT 10;
Mỗi CTE đọc giống như một đoạn văn. daily_trades tổng hợp các giao dịch thô thành các bản tóm tắt theo ngày. daily_pnl tính lãi/lỗ ròng và tổng chạy. Câu SELECT cuối cùng chọn ra 10 ngày hàng đầu. So sánh điều này với một subquery nguyên khối duy nhất — phiên bản dùng CTE dễ bảo trì, dễ kiểm thử, và tự giải thích bản thân.
Trong các hệ thống tài chính thực tế đang chạy sản xuất, CTE được dùng cho mọi thứ từ báo cáo tuân thủ quy định (tổng hợp yêu cầu ký quỹ trên các tài khoản) đến các câu truy vấn dashboard thời gian thực (tính khối lượng cuộn 24 giờ trên GaiaEx). Chúng kết hợp theo cách mà mã nguồn được tổ chức tốt vẫn kết hợp — mỗi lớp xây dựng dựa trên lớp trước.
Đánh chỉ mục và phân vùng cho hiệu năng chuỗi thời gian
Một câu truy vấn chỉ nhanh bằng đúng chỉ mục hỗ trợ nó. Nếu không có chỉ mục phù hợp, ngay cả một mệnh đề WHERE đơn giản cũng buộc phải thực hiện một quét tuần tự (sequential scan) — đọc từng hàng trong bảng. Với 500 triệu hàng, đó là vài phút thay vì vài mili giây.
Đối với dữ liệu chuỗi thời gian tài chính, mẫu chỉ mục quan trọng nhất là một chỉ mục B-tree kết hợp (composite) trên (symbol, executed_at):
CREATE INDEX idx_trades_symbol_time
ON trades (symbol, executed_at DESC);
Chỉ một chỉ mục này đã tăng tốc phần lớn các câu truy vấn phân tích: “tất cả giao dịch ETH trong giờ qua,” “giao dịch BTC giữa hai mốc thời gian,” hay “giao dịch gần nhất cho mỗi symbol.” Thứ tự cột quan trọng — symbol đứng trước cho phép lọc theo điều kiện bằng, sau đó executed_at cho phép quét theo khoảng hiệu quả trong phân vùng đó của chỉ mục.
Đối với các bảng phát triển vượt quá hàng trăm triệu hàng, phân vùng bảng (table partitioning) là điều thiết yếu. PostgreSQL hỗ trợ phân vùng khai báo theo khoảng (range) — hoàn hảo cho dữ liệu chuỗi thời gian:
CREATE TABLE trades (
trade_id BIGINT, symbol VARCHAR(20),
price NUMERIC(18,8), executed_at TIMESTAMPTZ
) PARTITION BY RANGE (executed_at);
CREATE TABLE trades_2026_q1 PARTITION OF trades
FOR VALUES FROM ('2026-01-01') TO ('2026-04-01');
CREATE TABLE trades_2026_q2 PARTITION OF trades
FOR VALUES FROM ('2026-04-01') TO ('2026-07-01');
Với phân vùng, một câu truy vấn cho dữ liệu tháng 3 năm 2026 chỉ quét phân vùng Q1 — bộ máy hoàn toàn bỏ qua Q2, Q3, và Q4. Kỹ thuật này, được gọi là partition pruning (loại bỏ phân vùng), mang lại tốc độ tăng theo cấp số nhân trên các tập dữ liệu lịch sử lớn.
PostgreSQL, ClickHouse, và BigQuery: chọn đúng bộ máy
Không phải cơ sở dữ liệu nào cũng xử lý phân tích tài chính giống nhau. Lựa chọn phụ thuộc vào mẫu truy vấn của bạn, khối lượng dữ liệu, và yêu cầu về độ trễ.
PostgreSQL là con ngựa cày. Nó xuất sắc ở các workload giao dịch (ghi lại các giao dịch theo thời gian thực), hỗ trợ đảm bảo ACID, xử lý các JOIN phức tạp một cách trơn tru, và mở rộng đến hàng trăm triệu hàng với việc đánh chỉ mục và phân vùng phù hợp. Đây là lựa chọn đúng cho các cơ sở dữ liệu vận hành — hệ thống ghi nhận chính (system of record). GaiaEx, chẳng hạn, dựa vào hạ tầng tương thích PostgreSQL cho dữ liệu giao dịch cốt lõi của mình với các đảm bảo về độ tin cậy mà hệ thống tài chính yêu cầu.
ClickHouse là một cơ sở dữ liệu dạng cột (columnar) được thiết kế cho các câu truy vấn phân tích trên hàng tỷ hàng. Trong khi PostgreSQL lưu dữ liệu theo từng hàng (rất tốt cho việc chèn từng giao dịch riêng lẻ), ClickHouse lưu dữ liệu theo từng cột (rất tốt cho việc tổng hợp một cột duy nhất trên hàng triệu hàng). Một câu truy vấn như “khối lượng trung bình theo giờ cho BTC trong 3 năm” mất 30 giây trong PostgreSQL có thể hoàn thành trong 200 mili giây ở ClickHouse. Sự đánh đổi: ClickHouse không hỗ trợ UPDATE hoặc DELETE hiệu quả — nó được thiết kế chỉ để nối thêm (append-only).
BigQuery (Google Cloud) là một warehouse dạng cột không cần quản lý server (serverless). Không cần quản lý hạ tầng, tính phí theo truy vấn, và khả năng ở quy mô petabyte. Nó lý tưởng cho phân tích tùy ý (ad-hoc), nghiên cứu, và khai phá — nhưng độ trễ truy vấn được đo bằng giây, không phải mili giây, khiến nó không phù hợp cho các ứng dụng thời gian thực.
- Ghi nhận giao dịch thời gian thực và quản lý lệnh → PostgreSQL
- Phân tích lịch sử và backtest trên hàng tỷ hàng → ClickHouse
- Khai phá tùy ý và chia sẻ dữ liệu giữa các đội → BigQuery
Nhiều công ty giao dịch chuyên nghiệp sử dụng cả ba trong một kiến trúc theo tầng: PostgreSQL là kho lưu trữ vận hành nóng, ClickHouse là lớp phân tích ấm, và BigQuery là lưu trữ lạnh. Kỹ năng viết SQL tốt có thể áp dụng trên cả ba — cú pháp giống nhau 90%, và cách suy nghĩ phân tích giống nhau 100%.