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 ใน
โมดูลถัดไป
03 โหลดข้อมูล — สองวิธี
tick 26.5 ล้านรายการชุดเดียวกันโหลดสองครั้ง: ClickPipes ซึ่งเป็นไปป์ไลน์แบบจัดการให้ที่คุณจะใช้บนโปรดักชัน แล้วต่อด้วย s3() แบบบรรทัดเดียว — และควรเลือกใช้อันไหนเมื่อไหร่
05 ให้ AI แก้คิวรีให้
ส่งคิวรีที่สแกนทั้งตารางจากโมดูล 04 ให้ ClickHouse Assistant แล้วดูมันวินิจฉัยว่า sort key ขาดอะไร ก่อนจะเสนอ skip index และ projection พร้อม SQL ที่รันได้ทันที