GaiaEx AcademyGaiaEx Academy
SQL สำหรับคลังข้อมูลการเงิน
นักพัฒนาการเขียนโปรแกรม9 min read

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 ในเครื่องหรือคลังข้อมูลระดับคลาวด์

Relational model (simplified) users user_id PK accounts user_id FK → users tier, limits … trades user_id FK → accounts symbol, price, time … JOINs follow foreign keys — the shape of finance data is already relational.
การเทรดเชื่อมโยงกับบัญชีและผู้ใช้: เป็นความเชื่อมโยงเดียวกันที่ JOIN ใน SQL ของคุณไล่ตาม

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) บนชุดข้อมูลประวัติศาสตร์ขนาดใหญ่

Time-range partition pruning Q1 2026 Jan–Mar rows Q2 2026 skipped Q3 2026 Q4 2026 Query: WHERE executed_at in March → planner reads only Q1 Composite index (symbol, executed_at) still helps within each partition
Partition กำหนดขอบช่วงเวลา; การ pruning จะข้ามชิ้นข้อมูลทั้งหมดที่ไม่สามารถตรงกันได้

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%