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.
03 Nạp dữ liệu — hai cách
Cùng 26.5M tick được nạp hai lần: ClickPipes, pipeline được quản lý mà bạn sẽ dùng trong production, rồi câu one-liner s3() — và khi nào nên chọn cách nào.
05 Nhờ AI sửa một truy vấn
Đưa truy vấn quét toàn bảng từ module 04 cho ClickHouse Assistant và xem nó chẩn đoán sort key còn thiếu, rồi đề xuất một skip index và một projection kèm SQL chạy được ngay.