Real-Time Market AnalyticsClickHouse Workshops

04 Chạy các truy vấn thị trường

Bảy truy vấn trên 26.5M tick, mỗi truy vấn thay thế cả một trang SQL: đếm gần đúng, topK, nến OHLC, phân vị, combinator -If, argMax, và màn kết về sort key.

Đây là những "hàm thay thế cả một trang SQL" của ClickHouse. Hãy dán từng khối, chạy nó, và đọc ô màu xanh để biết kết quả mong đợi. Tất cả đều chạy trên bảng forex bạn vừa nạp.

4.1 Gần đúng so với chính xác — mẹo nổi bật nhất

Có bao nhiêu timestamp báo giá khác nhau trong 26.5M tick? Ba hàm trả lời cùng một câu hỏi với những đánh đổi khác nhau. Hãy chạy từng hàm một và để ý bộ đếm thời gian trong phần thống kê truy vấn — chạy riêng lẻ chính là điểm mấu chốt.

-- 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;

Bạn sẽ thấy

Khoảng 24.6 triệu timestamp khác nhau từ cả ba hàm. Nhưng uniq trả về trong ~0.2 s, uniqExact mất ~3 s (chậm hơn khoảng 17×) để có con số chính xác, còn uniqCombined cho kết quả lệch trong khoảng ~0.4% và mất chưa tới một giây. Ở quy mô hàng tỷ dòng, khoảng cách đó là khác biệt giữa tức thời và một lần đi uống cà phê.

4.2 Top-N trong một hàm duy nhất

Những cặp tiền được báo giá sôi động nhất — không cầ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;

Bạn sẽ thấy

Một ô duy nhất chứa một mảng gồm 8 cặp, cặp được báo giá nhiều nhất đứng đầu (vàng, XAU/USD, dẫn đầu). topK gộp cả bảng thành danh sách xếp hạng đó chỉ trong một lượt quét — nó thay cho một câu GROUP BY … ORDER BY count() DESC LIMIT 8.

4.3 Time-series trong một lượt quét — nến OHLC

Giá mở / cao nhất / thấp nhất / đóng theo ngày của vàng — truy vấn thị trường cơ bản nhất:

-- 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;

Bạn sẽ thấy

Một dòng cho mỗi ngày của tháng 1 năm 2020. argMin(bid, datetime) là giá bid tại tick đầu tiên của ngày (giá mở) và argMax là tick cuối cùng (giá đóng) — không cần window function, không cần self-join. Hãy để ý vàng leo từ ~1,520 lên ~1,610 trong tháng đó.

4.4 Phân vị mà không cần xoay xở với window function

Chênh lệch bid/ask là thước đo thanh khoản — và giá trị trung bình che mất phần đuôi, nên hãy dùng phân vị:

-- 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;

Bạn sẽ thấy

Một dòng cho mỗi cặp, chênh lệch hẹp nhất đứng đầu. EUR/USD ra kết quả thanh khoản nhất (~0.00002, dưới một pip); vàng rộng nhất. quantile được tính gần đúng trong một lượt quét — không phải sắp xếp cả cột.

4.5 Combinator — một lượt quét, hai câu trả lời

Chênh lệch trung bình trong giờ giao dịch sôi động so với giờ trầm lắng, đặt cạnh nhau, chỉ trong một lượt quét:

-- 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;

Bạn sẽ thấy

Chênh lệch trong khung giờ sôi động và giờ trầm lắng nằm trong cùng một kết quả. Combinator -If gắn thêm một điều kiện vào bất kỳ hàm tổng hợp nào: avgIf(x, cond) chỉ tính trung bình x ở những chỗ cond đúng — không cần truy vấn hai lượt, không cần CASE WHEN. Gần như mọi hàm tổng hợp đều nhận nó (countIf, sumIf, quantileIf, …).

4.6 Giá trị mới nhất — argMax

Báo giá gần nhất cho mỗi cặp, trong một lượt quét:

-- 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;

Bạn sẽ thấy

Giá bid mới nhất theo từng cặp. argMax(bid, datetime) trả về bid từ dòng có datetime lớn nhất — chính là truy vấn "giá cuối / trạng thái mới nhất" thường ngày, mà không cần window function hay self-join.

4.7 Màn kết — có sort key so với không có sort key

Cùng một dạng truy vấn — đếm số tick khớp một điều kiện — nhưng một bộ lọc chạm vào sort key còn bộ lọc kia thì không. Trước tiên, hãy chạy cả hai câu đếm:

-- 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;

Khó thấy khác biệt?

Cả hai câu đếm đều trả về trong vài millisecond, nên riêng thời gian chạy gần như không đổi — count() nhanh theo cả hai cách. Khác biệt thực sự là mỗi câu phải chạm vào bao nhiêu dữ liệu. Để thấy rõ, hãy yêu cầu ClickHouse cho xem kế hoạch thực thi bằng EXPLAIN indexes = 1, nó báo cáo số granule (khối ~8,192 dòng) mà truy vấn sẽ đọc.

-- 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;

Bạn sẽ thấy

Trong kế hoạch được tối ưu, phần Indexes → PrimaryKey chỉ cho thấy một phần nhỏ granule được chọn — khoảng 492 / 3,234 (~16K dòng). Trong kế hoạch không được tối ưu thì không có index nào áp dụng được, nên nó đọc 3,234 / 3,234 granule — tức mọi dòng. Cùng một dạng truy vấn, khối lượng công việc khác nhau một trời một vực. Bài học: đặt những cột bạn lọc nhiều nhất lên đầu ORDER BY.

Hãy giữ câu này trong clipboard

Bạn sẽ đưa câu truy vấn không được tối ưu WHERE bid > 1.5 đó cho AI ở module tiếp theo.

Trên trang này

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.

VI