
SQL para sa Financial Data Warehouses
Query, aggregate, at analisahin ang structured financial data
Bakit SQL ang Wika ng Financial Data
Bawat trade, bawat tick, bawat order book update — gumagawa ang financial markets ng nakamamanghang volume ng structured data. At sa loob ng mahigit apat na dekada, ang SQL (Structured Query Language) ang naging dominanteng tool para sa pag-query, pag-transform, at pag-analisa ng datos na iyon. Hindi ito flashy. Hindi ito trendy. Pero kapag tinanong ng isang portfolio manager na “Ano ang average fill price ko sa ETH trades noong huling Tuesday sa pagitan ng 2 PM at 3 PM UTC?”, mula sa SQL ang sagot.
Likas na relational ang financial data. Tumutukoy ang isang trade sa isang order, na tumutukoy sa isang account, na pag-aari ng isang user. Nauugnay ang positions sa instruments; nauugnay ang instruments sa markets. Ang relational model — mga tables na may rows at columns, na nag-uugnay gamit ang foreign keys — natural na nag-map sa mga relasyon na ito. Ito ang dahilan kung bakit ang mga relational database gaya ng PostgreSQL, MySQL, at SQL Server ang nananatiling backbone ng financial infrastructure, mula sa Goldman Sachs hanggang GaiaEx.
Ang kapangyarihan ng SQL ay nakasalalay sa declarative na kalikasan nito: inilalarawan mo ano ang gusto mo, hindi paano ito makukuha. Alamin na ng database engine ang optimal na execution plan. Sobrang mahalaga ito kapag ang trade history table mo ay may 500 milyong rows at kailangan mo ng resulta sa mababa sa isang segundo. Isulat ang tamang query, buuin ang tamang index, at maghahatid ang SQL — kahit patakbo ka man ito sa isang local PostgreSQL instance o sa isang cloud-scale data warehouse.
SELECT, JOIN, at WHERE: Pag-query ng Trade Data
Magsimula tayo sa isang concrete schema. Isipin mo ang isang trades table sa isang platform gaya ng 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
);
Ang pinaka-pundamental na operasyon ay ang SELECT statement na may filtering. Para hanapin ang lahat ng ETH-USD buy trades na higit sa $3,000 sa nakaraang 24 oras:
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;
Kumokonekta ang JOINs sa mga related tables. Ipagpalagay na may accounts table ka at gustong makita ang trading volume kada account 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;
Ang mga query na ito ang pang-araw-araw na tinapay ng financial analysts, risk teams, at compliance officers. Bihasahin ang mga ito at masasagot mo ang kahit anong tanong na nasa datos mo.
Window Functions: Ang Sikretong Sandata ng Analyst
Ang Window functions ang naghihiwalay sa SQL beginners mula sa SQL practitioners sa finance. Pinapayagan ka nitong magsagawa ng mga kalkulasyon sa isang set ng rows na nauugnay sa kasalukuyang row — nang hindi bumagsak ang resulta sa isang solong aggregated na value. Isipin mo itong parang “running computations” sa iyong datos.
Ang pinaka-karaniwang gamit na window functions sa financial analysis:
- ROW_NUMBER() — Nagbibigay ng sunod-sunod na integer sa bawat row sa loob ng isang partition. Kapaki-pakinabang para sa deduplication o sa pagpili ng Nth trade kada symbol.
- LAG() at LEAD() — Kunin ang value ng nakaraang o susunod na row. Mahalaga para sa pag-compute ng trade-to-trade returns o pag-detect ng gaps sa time-series data.
- SUM() OVER () — Running totals. I-compute ang cumulative volume, cumulative P&L, o running position size.
- AVG() OVER (ROWS BETWEEN) — Moving averages direkta sa SQL. Hindi na kailangan ng Python.
Ito ang isang praktikal na halimbawa — pag-compute ng trade-to-trade price change at running position para sa 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;
Hindi binabawasan ng window functions ang bilang ng iyong rows — hindi tulad ng GROUP BY, ang bawat input row ay gumagawa ng isang output row. Ito ang gumagawa sa mga ito na perpekto para magdagdag ng analytical columns sa detalyadong trade logs nang hindi nawawala ang granularity. Isipin mo itong “pinayayaman” ang iyong datos kaysa “pinagbubuod” ito.
Para sa OHLCV aggregation — ang pundasyon ng candlestick charts — puwede mong pagsamahin ang GROUP BY sa time-bucketing at standard aggregates:
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;CTEs: Pagbuo ng Kumplikadong Analytical Queries
Pinapayagan ka ng Common Table Expressions (CTEs) na hatiin ang isang kumplikadong query sa mga pinangalanan, madaling-basahing stages — tulad ng functions sa programming. Ipinapakilala ang mga ito gamit ang WITH keyword at puwedeng mag-reference sa isa't isa nang sunod-sunod.
Ipagpalagay na gusto mong tukuyin ang iyong top 10 pinaka-kumikitang araw ng trading, pero kinakailangan ng profitability ang pag-compute ng net P&L kada araw kasama ang fees. Sa CTEs, puwede mong buuin ito step by step:
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;
Ang bawat CTE ay nababasa gaya ng isang paragraph. Ang daily_trades ay nag-aggregate ng raw trades sa daily summaries. Ang daily_pnl ay nag-compute ng net P&L at running total. Ang final SELECT ay pumili ng top 10 araw. Ihambing ito sa isang solo, monolithic na subquery — ang CTE version ay maintainable, testable, at self-documenting.
Sa production financial systems, ginagamit ang CTEs para sa lahat mula sa regulatory reporting (pag-aggregate ng margin requirements sa lahat ng accounts) hanggang sa real-time dashboard queries (pag-compute ng rolling 24-oras na volume sa GaiaEx). Nagkakasama sila nang tulad ng pagsasama ng magandang-factored na code — ang bawat layer ay itinatayo sa ibabaw ng nauna.
Indexing at Partitioning para sa Time-Series Performance
Ang isang query ay kasing-bilis lang ng index na nagsusuporta dito. Kung wala ang tamang index, kahit isang simpleng WHERE clause ay pipilitin ang isang sequential scan — pagbabasa ng bawat row sa table. Sa 500 milyong rows, minuto ito sa halip na milliseconds.
Para sa financial time-series data, ang pinaka-kritikal na index pattern ay ang isang composite B-tree index sa (symbol, executed_at):
CREATE INDEX idx_trades_symbol_time
ON trades (symbol, executed_at DESC);
Ang solong index na ito ay pinabilis ang karamihan sa mga analytical queries: “lahat ng ETH trades sa nakaraang oras,” “BTC trades sa pagitan ng dalawang timestamps,” o “pinakahuling trade kada symbol.” Mahalaga ang order ng columns — ang symbol muna ang nagbibigay-daan sa equality filtering, tapos ang executed_at ang nagbibigay-daan sa episyenteng range scans sa loob ng partition na iyon ng index.
Para sa mga tables na lumalaki nang lampas sa daan-daang milyong rows, mahalaga ang table partitioning. Sinusuportahan ng PostgreSQL ang declarative partitioning ayon sa range — perpekto para sa time-series:
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');
Sa partitioning, ang isang query para sa datos ng March 2026 ay iiskan lang ang Q1 partition — ganap na nililaktawan ng engine ang Q2, Q3, at Q4. Ang teknik na ito, na tinatawag na partition pruning, ay naghahatid ng order-of-magnitude na pagbilis sa malaking historical datasets.
PostgreSQL kumpara sa ClickHouse kumpara sa BigQuery: Pagpili ng Tamang Engine
Hindi pantay ang paghawak ng lahat ng databases sa financial analytics. Nakasalalay ang pagpili sa iyong query patterns, data volume, at latency requirements.
Ang PostgreSQL ang workhorse. Napakahusay ito sa transactional workloads (pagrekord ng trades sa real time), sinusuportahan ang ACID guarantees, mahusay na hinahawakan ang mga kumplikadong JOINs, at nag-scale sa daan-daang milyong rows na may tamang indexing at partitioning. Ito ang tamang pagpili para sa operational databases — ang system of record. Ang GaiaEx, halimbawa, ay umaasa sa PostgreSQL-compatible na infrastructure para sa core trading data nito na may reliability guarantees na kinakailangan ng financial systems.
Ang ClickHouse ay isang columnar database na idinisenyo para sa analytical queries sa bilyon-bilyong rows. Kung saan ang PostgreSQL ay nag-iimbak ng datos row by row (mahusay para sa pag-insert ng indibidwal na trades), ang ClickHouse ay nag-iimbak ng datos column by column (mahusay para sa pag-aggregate ng isang column sa milyon-milyong rows). Ang isang query gaya ng “average hourly volume para sa BTC sa loob ng 3 taon” na tumatagal ng 30 segundo sa PostgreSQL ay puwedeng matapos sa 200 milliseconds sa ClickHouse. Ang trade-off: hindi episyente ang UPDATE o DELETE sa ClickHouse — append-only ito ayon sa disenyo.
Ang BigQuery (Google Cloud) ay isang serverless na columnar warehouse. Walang infrastructure na kailangang pamahalaan, pay-per-query pricing, at petabyte-scale na kapasidad. Ideal ito para sa ad-hoc analytics, research, at exploration — pero ang query latency ay sinusukat sa segundo, hindi millisecond, na gumagawa nito na hindi angkop para sa real-time applications.
- Real-time trade recording at order management → PostgreSQL
- Historical analytics at backtesting sa bilyon-bilyong rows → ClickHouse
- Ad-hoc exploration at cross-team na data sharing → BigQuery
Ginagamit ng maraming propesyonal na trading firms ang tatlo sa isang tiered architecture: PostgreSQL bilang hot operational store, ClickHouse bilang warm analytical layer, at BigQuery bilang cold archive. Ang kahusayan sa pagsulat ng magandang SQL ay applicable sa lahat ng ito — halos 90% pareho ang syntax, at 100% pareho ang analytical thinking.