
SQL สำหรับคลังข้อมูลการเงิน
ค้นหา รวมข้อมูล และวิเคราะห์ข้อมูลการเงินที่มีโครงสร้าง
ทำไม SQL คือภาษาของข้อมูลการเงิน
ทุกการเทรด ทุก tick ทุกการอัปเดต order book — ตลาดการเงินสร้างข้อมูลที่มีโครงสร้างในปริมาณที่น่าทึ่ง และมากว่าสี่ทศวรรษแล้วที่ SQL (Structured Query Language) เป็นเครื่องมือหลักในการค้นหา แปลง และวิเคราะห์ข้อมูลนั้น มันไม่ได้ดูหรูหรา มันไม่ได้ทันสมัย แต่เมื่อผู้จัดการพอร์ตถามว่า "ราคา fill เฉลี่ยของการเทรด ETH ของฉันเมื่อวันอังคารที่แล้วระหว่างเวลา 14:00 ถึง 15:00 UTC คือเท่าไร?" คำตอบมาจาก SQL
ข้อมูลการเงินมีลักษณะเป็น เชิงสัมพันธ์ (relational) โดยพื้นฐาน การเทรดหนึ่งอ้างอิงไปยังคำสั่งซื้อขาย ซึ่งอ้างอิงไปยังบัญชี ซึ่งเป็นของผู้ใช้ สถานะการเทรดเชื่อมโยงกับตราสาร ตราสารเชื่อมโยงกับตลาด โมเดลเชิงสัมพันธ์ — ตารางที่มีแถวและคอลัมน์ เชื่อมต่อกันด้วย foreign key — สอดคล้องโดยธรรมชาติกับความสัมพันธ์เหล่านี้ นี่คือเหตุผลที่ฐานข้อมูลเชิงสัมพันธ์อย่าง PostgreSQL, MySQL และ SQL Server ยังคงเป็นกระดูกสันหลังของโครงสร้างพื้นฐานทางการเงิน ตั้งแต่ Goldman Sachs ไปจนถึง GaiaEx
พลังของ SQL อยู่ที่ธรรมชาติแบบ declarative ของมัน: คุณอธิบายสิ่งที่คุณต้องการ ไม่ใช่วิธีที่จะได้มัน เอนจินฐานข้อมูลจะคิดหาแผนการทำงานที่เหมาะสมที่สุดเอง เรื่องนี้สำคัญมากเมื่อตารางประวัติการเทรดของคุณมี 500 ล้านแถวและคุณต้องการผลลัพธ์ภายในเวลาไม่ถึงวินาที เขียน query ให้ถูก สร้าง index ให้ถูก แล้ว SQL ก็จะส่งมอบให้ — ไม่ว่าคุณจะรันมันบน PostgreSQL instance ในเครื่องหรือคลังข้อมูลระดับคลาวด์
SELECT, JOIN และ WHERE: การค้นหาข้อมูลการเทรด
เริ่มต้นด้วย schema ที่เป็นรูปธรรม ลองนึกภาพตาราง trades บนแพลตฟอร์มอย่าง 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
);
การปฏิบัติการพื้นฐานที่สุดคือคำสั่ง SELECT พร้อมการกรอง เพื่อค้นหาการเทรดซื้อทั้งหมดของ ETH-USD ที่มีราคาสูงกว่า $3,000 ในช่วง 24 ชั่วโมงที่ผ่านมา:
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 เชื่อมต่อตารางที่เกี่ยวข้องกัน สมมติว่าคุณมีตาราง accounts และต้องการดูปริมาณการเทรดต่อระดับบัญชี (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;
Query เหล่านี้คืองานประจำวันของนักวิเคราะห์การเงิน ทีมความเสี่ยง และเจ้าหน้าที่กำกับดูแลการปฏิบัติตามกฎ เชี่ยวชาญมันแล้วคุณจะตอบคำถามใดก็ได้ที่ข้อมูลของคุณมีคำตอบอยู่
Window Function: อาวุธลับของนักวิเคราะห์
Window function คือสิ่งที่แยกมือใหม่ SQL ออกจากผู้ปฏิบัติงาน SQL จริงในสายการเงิน มันให้คุณคำนวณข้ามชุดของแถวที่เกี่ยวข้องกับแถวปัจจุบัน — โดยไม่บีบผลลัพธ์ให้เหลือค่าเดียวที่รวมกัน ให้นึกว่ามันคือ "การคำนวณแบบไหลต่อเนื่อง" ข้ามข้อมูลของคุณ
Window function ที่ใช้บ่อยที่สุดในการวิเคราะห์ทางการเงิน:
- ROW_NUMBER() — กำหนดจำนวนเต็มลำดับให้แต่ละแถวภายใน partition มีประโยชน์สำหรับการลบข้อมูลซ้ำหรือเลือกการเทรดที่ N ต่อสัญลักษณ์
- LAG() และ LEAD() — เข้าถึงค่าของแถวก่อนหน้าหรือถัดไป จำเป็นสำหรับการคำนวณผลตอบแทนระหว่างการเทรดหรือตรวจจับช่องว่างในข้อมูลชุดเวลา
- SUM() OVER () — ยอดรวมสะสม คำนวณปริมาณสะสม P&L สะสม หรือขนาดสถานะที่ไหลต่อเนื่อง
- AVG() OVER (ROWS BETWEEN) — ค่าเฉลี่ยเคลื่อนที่โดยตรงใน SQL ไม่ต้องใช้ Python
นี่คือตัวอย่างเชิงปฏิบัติ — คำนวณการเปลี่ยนแปลงราคาระหว่างการเทรดและสถานะที่ไหลต่อเนื่องสำหรับ 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 ไม่ลดจำนวนแถวของคุณ — ต่างจาก GROUP BY แถวนำเข้าทุกแถวสร้างแถวผลลัพธ์หนึ่งแถว สิ่งนี้ทำให้มันเหมาะสำหรับการเพิ่มคอลัมน์เชิงวิเคราะห์ลงใน trade log แบบละเอียดโดยไม่สูญเสียความละเอียด ให้นึกว่ามันคือการ "เสริมความสมบูรณ์" ให้ข้อมูลของคุณ ไม่ใช่ "สรุป" มัน
สำหรับการรวมข้อมูลแบบ OHLCV — พื้นฐานของกราฟแท่งเทียน — คุณสามารถผสม GROUP BY กับการแบ่งช่วงเวลาและฟังก์ชันรวมมาตรฐานได้:
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: การประกอบ Query เชิงวิเคราะห์ที่ซับซ้อน
Common Table Expression (CTE) ให้คุณแบ่ง query ที่ซับซ้อนออกเป็นขั้นตอนที่มีชื่อและอ่านเข้าใจง่าย — เหมือนฟังก์ชันในการเขียนโปรแกรม มันเริ่มด้วยคำสั่ง WITH และสามารถอ้างอิงกันตามลำดับได้
สมมติว่าคุณต้องการระบุ 10 วันที่ทำกำไรได้มากที่สุดในการเทรดของคุณ แต่กำไรต้องคำนวณ P&L สุทธิต่อวันรวมค่าธรรมเนียมด้วย ด้วย CTE คุณสามารถสร้างมันขึ้นเป็นขั้นเป็นตอนได้:
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;
CTE แต่ละอันอ่านได้เหมือนหนึ่งพารากราฟ daily_trades รวมการเทรดดิบให้เป็นสรุปรายวัน daily_pnl คำนวณ P&L สุทธิและยอดรวมที่ไหลต่อเนื่อง SELECT สุดท้ายเลือก 10 วันที่ดีที่สุด เทียบกับ subquery แบบก้อนเดียวขนาดใหญ่ เวอร์ชัน CTE นั้นดูแลรักษาง่ายกว่า ทดสอบได้ง่ายกว่า และอธิบายตัวเองได้ในตัว
ในระบบการเงินที่ใช้งานจริง CTE ถูกใช้กับทุกอย่างตั้งแต่การรายงานเชิงกำกับดูแล (การรวมความต้องการมาร์จิ้นข้ามบัญชี) ไปจนถึง query แดชบอร์ดแบบเรียลไทม์ (คำนวณปริมาณ 24 ชั่วโมงที่ไหลต่อเนื่องบน GaiaEx) พวกมันประกอบกันในแบบเดียวกันที่โค้ดที่แยกส่วนได้ดีประกอบกัน — แต่ละชั้นสร้างต่อจากชั้นก่อนหน้า
การทำ Index และ Partitioning เพื่อประสิทธิภาพของข้อมูลชุดเวลา
Query จะเร็วได้เท่ากับ index ที่รองรับมันเท่านั้น หากไม่มี index ที่ถูกต้อง แม้แต่ WHERE clause ง่าย ๆ ก็จะบังคับให้เกิด sequential scan — อ่านทุกแถวในตาราง ที่ 500 ล้านแถว นั่นคือหลักนาทีแทนที่จะเป็นมิลลิวินาที
สำหรับข้อมูลชุดเวลาทางการเงิน รูปแบบ index ที่สำคัญที่สุดคือ composite B-tree index บน (symbol, executed_at):
CREATE INDEX idx_trades_symbol_time
ON trades (symbol, executed_at DESC);
Index เดียวนี้เร่งความเร็ว query เชิงวิเคราะห์ส่วนใหญ่: "การเทรด ETH ทั้งหมดในชั่วโมงที่แล้ว," "การเทรด BTC ระหว่างสอง timestamp," หรือ "การเทรดล่าสุดต่อสัญลักษณ์" ลำดับของคอลัมน์สำคัญมาก — symbol อยู่ก่อนเปิดใช้งานการกรองแบบเท่ากัน จากนั้น executed_at เปิดใช้งานการสแกนช่วงที่มีประสิทธิภาพภายใน partition นั้นของ index
สำหรับตารางที่เติบโตเกินหลายร้อยล้านแถว table partitioning เป็นสิ่งจำเป็น PostgreSQL รองรับการ partition แบบ declarative ตามช่วง — เหมาะสำหรับข้อมูลชุดเวลา:
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');
ด้วย partitioning query สำหรับข้อมูลเดือนมีนาคม 2026 จะสแกนเฉพาะ partition ของ Q1 เท่านั้น — เอนจินข้าม Q2, Q3 และ Q4 ไปทั้งหมด เทคนิคนี้เรียกว่า partition pruning มันมอบความเร็วที่เพิ่มขึ้นในระดับหลักขนาด (order of magnitude) บนชุดข้อมูลประวัติศาสตร์ขนาดใหญ่
PostgreSQL เทียบกับ ClickHouse เทียบกับ BigQuery: การเลือกเอนจินที่ถูกต้อง
ฐานข้อมูลไม่ทั้งหมดจัดการกับ analytics ทางการเงินได้เท่ากัน การเลือกขึ้นอยู่กับรูปแบบ query ปริมาณข้อมูล และความต้องการด้าน latency ของคุณ
PostgreSQL คือเครื่องทำงานหลัก มันโดดเด่นในงานเชิงทรานแซกชัน (บันทึกการเทรดแบบเรียลไทม์) รองรับการันตี ACID จัดการ JOIN ที่ซับซ้อนได้อย่างราบรื่น และขยายไปได้ถึงหลายร้อยล้านแถวด้วยการทำ index และ partitioning ที่เหมาะสม มันคือตัวเลือกที่ถูกต้องสำหรับฐานข้อมูลเชิงปฏิบัติการ — ระบบบันทึกหลัก ตัวอย่างเช่น GaiaEx พึ่งพาโครงสร้างพื้นฐานที่เข้ากันได้กับ PostgreSQL สำหรับข้อมูลการเทรดหลักของมัน พร้อมกับการันตีความน่าเชื่อถือที่ระบบการเงินต้องการ
ClickHouse คือฐานข้อมูลแบบคอลัมนาร์ (columnar) ที่ออกแบบมาสำหรับ query เชิงวิเคราะห์บนพันล้านแถว ในขณะที่ PostgreSQL เก็บข้อมูลแบบแถวต่อแถว (ดีสำหรับการแทรกการเทรดแต่ละรายการ) ClickHouse เก็บข้อมูลแบบคอลัมน์ต่อคอลัมน์ (ดีสำหรับการรวมคอลัมน์เดียวข้ามหลายล้านแถว) query เช่น "ปริมาณเฉลี่ยต่อชั่วโมงของ BTC ตลอด 3 ปี" ที่ใช้เวลา 30 วินาทีใน PostgreSQL อาจเสร็จภายใน 200 มิลลิวินาทีใน ClickHouse ข้อแลกเปลี่ยน: ClickHouse ไม่รองรับ UPDATE หรือ DELETE ได้อย่างมีประสิทธิภาพ — มันถูกออกแบบให้เป็นแบบ append-only เท่านั้น
BigQuery (Google Cloud) คือคลังข้อมูลแบบคอลัมนาร์แบบ serverless ไม่มีโครงสร้างพื้นฐานให้ดูแล ค่าใช้จ่ายแบบจ่ายตาม query และความสามารถระดับ petabyte มันเหมาะสำหรับ analytics แบบเฉพาะกิจ การวิจัย และการสำรวจ — แต่ latency ของ query วัดเป็นวินาที ไม่ใช่มิลลิวินาที ทำให้ไม่เหมาะกับแอปพลิเคชันแบบเรียลไทม์
- การบันทึกการเทรดแบบเรียลไทม์และการจัดการคำสั่งซื้อขาย → PostgreSQL
- Analytics เชิงประวัติศาสตร์และ backtesting บนพันล้านแถว → ClickHouse
- การสำรวจแบบเฉพาะกิจและการแบ่งปันข้อมูลข้ามทีม → BigQuery
บริษัทเทรดมืออาชีพหลายแห่งใช้ทั้งสามอย่างในสถาปัตยกรรมแบบเป็นชั้น: PostgreSQL เป็นที่จัดเก็บเชิงปฏิบัติการที่ "ร้อน" ClickHouse เป็นชั้นเชิงวิเคราะห์ที่ "อุ่น" และ BigQuery เป็นที่เก็บถาวรที่ "เย็น" ทักษะการเขียน SQL ที่ดีถ่ายโอนข้ามทั้งสามได้ — ไวยากรณ์เหมือนกันถึง 90% และความคิดเชิงวิเคราะห์เหมือนกันถึง 100%