Real-Time Market AnalyticsClickHouse Workshops

04 รันคิวรีวิเคราะห์ตลาด

เจ็ดคิวรีบน tick 26.5 ล้านรายการ แต่ละคิวรีแทน SQL ทั้งหน้า: การนับแบบประมาณ, topK, แท่งเทียน OHLC, เปอร์เซ็นไทล์, คอมบิเนเตอร์ -If, argMax และบทสรุปเรื่อง sort key

นี่คือ "ฟังก์ชันที่แทน SQL ได้ทั้งหน้า" ของ ClickHouse วางแต่ละบล็อก รันมัน แล้วอ่าน กล่องสีเขียวเพื่อดูว่าควรได้ผลอะไร ทั้งหมดรันบนตาราง forex ที่คุณเพิ่งโหลดเข้าไป

4.1 ประมาณ vs แม่นยำ — ไม้เด็ดของบทนี้

ใน tick 26.5 ล้านรายการมี timestamp ของ quote ที่ ไม่ซ้ำกัน อยู่กี่ค่า? สามฟังก์ชันตอบคำถาม เดียวกันด้วย trade-off ที่ต่างกัน รันทีละอัน และดูตัวจับเวลาในสถิติของคิวรี — การรันแยกกันคือประเด็นสำคัญทั้งหมด

-- Approximate count of distinct timestamps (HyperLogLog)
SELECT uniq(datetime) AS distinct_ts FROM forex;
-- Exact count — precise, but scans every value
SELECT uniqExact(datetime) AS distinct_ts FROM forex;
-- Adaptive + tunable accuracy vs memory
SELECT uniqCombined(datetime) AS distinct_ts FROM forex;

สิ่งที่คุณควรเห็น

timestamp ที่ไม่ซ้ำกันประมาณ 24.6 ล้าน ค่าจากทั้งสามฟังก์ชัน แต่ uniq คืนค่าในเวลาประมาณ 0.2 วินาที ขณะที่ uniqExact ใช้เวลาประมาณ 3 วินาที (ช้ากว่าประมาณ 17 เท่า) เพื่อให้ได้ตัวเลขที่แม่นยำ และ uniqCombined ให้ค่าที่คลาดเคลื่อนไม่เกินประมาณ 0.4% ในเวลาต่ำกว่าหนึ่งวินาทีมาก ที่ระดับพันล้านแถว ช่องว่างนี้คือความต่าง ระหว่างได้คำตอบทันทีกับได้ออกไปกินกาแฟ

4.2 Top-N ในฟังก์ชันเดียว

คู่สกุลเงินที่มีการเสนอราคาคึกคักที่สุด — ไม่ต้องใช้ GROUP BY / ORDER BY / LIMIT เลย:

-- One row: an array of the 8 most actively quoted pairs (by tick count)
SELECT topK(8)(pair) AS most_active FROM forex;

สิ่งที่คุณควรเห็น

เซลล์เดียวที่บรรจุ อาร์เรย์ของ 8 คู่สกุลเงิน เรียงจากที่ถูกเสนอราคามากที่สุดก่อน (ทองคำ XAU/USD อยู่อันดับหนึ่ง) topK ย่อทั้งตารางให้เป็นลิสต์ที่จัดอันดับแล้วในการสแกนรอบเดียว — มันใช้แทน GROUP BY … ORDER BY count() DESC LIMIT 8

4.3 Time-series ในรอบเดียว — แท่งเทียน OHLC

ราคา open / high / low / close รายวันของทองคำ — คิวรีตลาดพื้นฐานที่ใช้กันประจำ:

-- One row per day: gold (XAU/USD) open, high, low, close — a candlestick chart
SELECT
    toDate(datetime)      AS day,
    argMin(bid, datetime) AS open,    -- bid at the day's first tick
    max(bid)              AS high,
    min(bid)              AS low,
    argMax(bid, datetime) AS close    -- bid at the day's last tick
FROM forex
WHERE base = 'XAU' AND quote = 'USD'
GROUP BY day
ORDER BY day;

สิ่งที่คุณควรเห็น

หนึ่งแถวต่อหนึ่งวันของเดือนมกราคม 2020 argMin(bid, datetime) คือ bid ณ tick แรก ของวัน (ราคาเปิด) และ argMax คือ tick สุดท้าย (ราคาปิด) — ไม่ต้องใช้ window function ไม่ต้อง self-join ดูราคาทองคำไต่จากประมาณ 1,520 ไปถึงประมาณ 1,610 ภายในเดือนเดียว

4.4 เปอร์เซ็นไทล์โดยไม่ต้องเล่นกายกรรมกับ window

bid/ask spread คือมาตรวัดสภาพคล่อง — และค่าเฉลี่ยซ่อนหางของการแจกแจงไว้ จึงควรใช้เปอร์เซ็นไทล์:

-- One row per pair: median and 99th-percentile bid/ask spread (tighter = more liquid)
SELECT
    pair,
    round(quantile(0.5)(ask - bid), 6)  AS median_spread,
    round(quantile(0.99)(ask - bid), 6) AS p99_spread
FROM forex
GROUP BY pair
ORDER BY median_spread ASC;

สิ่งที่คุณควรเห็น

หนึ่งแถวต่อหนึ่งคู่สกุลเงิน เรียงจาก spread แคบสุดก่อน EUR/USD ออกมาเป็นคู่ที่มีสภาพคล่องสูงสุด (ประมาณ 0.00002 ต่ำกว่าหนึ่ง pip) ส่วนทองคำกว้างที่สุด quantile คำนวณแบบประมาณในรอบเดียว — ไม่ต้องเรียงลำดับข้อมูลทั้งคอลัมน์

4.5 คอมบิเนเตอร์ — สแกนรอบเดียว ได้สองคำตอบ

spread เฉลี่ยช่วงเวลาซื้อขายคึกคักเทียบกับช่วงเงียบ วางเทียบกัน ในการสแกนรอบเดียว:

-- One row per pair: avg spread during the active window vs quiet hours
SELECT
    pair,
    count()                                                           AS ticks,
    round(avgIf(ask - bid, toHour(datetime) BETWEEN 7 AND 20), 6)     AS spread_active,
    round(avgIf(ask - bid, toHour(datetime) NOT BETWEEN 7 AND 20), 6) AS spread_quiet
FROM forex
GROUP BY pair
ORDER BY ticks DESC;

สิ่งที่คุณควรเห็น

spread ของช่วงคึกคักและช่วงเงียบอยู่ในผลลัพธ์ชุดเดียวกัน คอมบิเนเตอร์ -If แปะเงื่อนไข เข้ากับฟังก์ชัน aggregate ใด ๆ ได้: avgIf(x, cond) หาค่าเฉลี่ยของ x เฉพาะที่ cond เป็นจริง — ไม่ต้อง ทำคิวรีสองรอบ ไม่ต้องใช้ CASE WHEN และฟังก์ชัน aggregate แทบทุกตัวรับมันได้ (countIf, sumIf, quantileIf, …)

4.6 ค่าล่าสุด — argMax

quote ล่าสุดของทุกคู่สกุลเงิน ในการสแกนรอบเดียว:

-- One row per pair: the latest quoted bid and the timestamp it was seen
SELECT
    pair,
    argMax(bid, datetime) AS last_bid,
    max(datetime)         AS as_of
FROM forex
GROUP BY pair
ORDER BY pair;

สิ่งที่คุณควรเห็น

bid ล่าสุดของแต่ละคู่สกุลเงิน argMax(bid, datetime) คืนค่า bid จากแถวที่มี datetime มากที่สุด — คิวรี "ราคาล่าสุด / สถานะล่าสุด" ที่ใช้กันทุกวัน โดยไม่ต้องใช้ window function หรือ self-join

4.7 บทสรุป — มี sort key กับไม่มี sort key

คิวรีรูปแบบเดียวกัน — นับจำนวน tick ที่ตรงเงื่อนไข — แต่ตัวกรองหนึ่งตรงกับ sort key และอีกตัวไม่ตรง เริ่มด้วยการรันการนับทั้งสองแบบ:

-- Optimized: base+quote ARE the leading sort key — the index skips to that pair
SELECT count() FROM forex WHERE base = 'XAU' AND quote = 'USD';

-- Non-optimized: bid is NOT in the sort key — no index, scans all 26.5M ticks.
-- (cache off so the full scan shows on every run)
SELECT count() FROM forex WHERE bid > 1.5
SETTINGS use_query_condition_cache = 0;

มองความต่างไม่ออก?

การนับทั้งสองแบบกลับมาในไม่กี่มิลลิวินาที ดังนั้นดูแค่เวลาแทบไม่ต่างกัน — count() เร็วอยู่แล้ว ทั้งสองทาง ความต่างจริง ๆ อยู่ที่ แต่ละคิวรีต้องแตะข้อมูลมากแค่ไหน ถ้าจะเห็นให้ชัด ให้สั่ง ClickHouse แสดงแผนการทำงานด้วย EXPLAIN indexes = 1 ซึ่งจะรายงานว่ามันจะอ่าน granule (บล็อกละประมาณ 8,192 แถว) ไปกี่บล็อก

-- Optimized: the primary index skips straight to the XAU/USD rows
EXPLAIN indexes = 1
SELECT count() FROM forex WHERE base = 'XAU' AND quote = 'USD';

-- Non-optimized: bid isn't in the sort key, so nothing can be skipped
EXPLAIN indexes = 1
SELECT count() FROM forex WHERE bid > 1.5;

สิ่งที่คุณควรเห็น

ในแผนแบบ optimized ส่วน Indexes → PrimaryKey จะแสดงว่ามี granule ถูกเลือกแค่ส่วนเดียว ประมาณ 492 / 3,234 (ราว 16K แถว) ในแผนแบบ non-optimized ไม่มี index ให้ใช้ จึงต้องอ่าน 3,234 / 3,234 granule — ทุกแถวจริง ๆ คิวรีรูปแบบเดียวกัน แต่ปริมาณงาน ต่างกันมหาศาล บทเรียนคือ เอาคอลัมน์ที่คุณใช้กรองบ่อยที่สุดไปไว้หน้าสุดของ ORDER BY

เก็บคิวรีนี้ไว้ในคลิปบอร์ด

คุณจะส่งคิวรี WHERE bid > 1.5 แบบ non-optimized นั้นให้ AI ใน โมดูลถัดไป

ในหน้านี้

Track your progress?

Optional. We email a link to confirm your address; progress records once you open it.

Please use your work email address, not a personal one.

Progress tracking also requires accepting the current Terms of Service in Privacy settings.

TH