4、查询基础:SELECT语法、WHERE过滤、GROUP BY与聚合函数、ORDER BY与LIMIT
好,咱们今天来啃查询基础这块硬骨头。说实话,我见过不少新手,一上来就写复杂的 JOIN 和子查询,结果连最基本的 SELECT 都写不利索。其实啊,ClickHouse 的查询语法跟标准 SQL 很像,但细节上有很多「坑」。我个人习惯是,先把基础打牢,再玩花的。
4.1 SELECT 语法:从哪来到哪去
先看最基础的 SELECT。说白了就是「从哪张表里拿哪些列」。
SELECT column1, column2, ...
FROM table_name
[WHERE ...]
[GROUP BY ...]
[ORDER BY ...]
[LIMIT ...]
嗯,这里要注意:ClickHouse 的 SELECT 支持 * 通配符,但我不建议在生产环境这么干。为什么?因为表结构一变,你的查询结果就跟着变,容易出 bug。我在项目中遇到过,有人用 SELECT * 导出数据,结果某天加了个大字段,直接把下游系统搞崩了。
另外,ClickHouse 支持 SELECT expr AS alias 这种别名写法。我个人习惯给复杂表达式起个短别名,这样后面 ORDER BY 或者 GROUP BY 的时候可以直接用别名,省事。
SELECT
count() AS cnt,
sum(amount) AS total_amount
FROM orders
4.2 WHERE 过滤:别让数据白跑
WHERE 子句是用来过滤数据的。你想想看,如果一张表有 10 亿行,你不加 WHERE 直接 SELECT,那 ClickHouse 就得全表扫描,性能直接拉胯。
ClickHouse 的 WHERE 支持常见的比较运算符:=、!=、>、<、IN、LIKE 等等。但有个关键点:尽量用主键或分区键做过滤。因为 ClickHouse 的索引机制决定了,只有过滤条件命中主键时,才能做到「只读需要的数据」。
我曾经踩过的坑: 有一次我写了个 WHERE 条件 WHERE toDate(timestamp) = '2024-01-01',结果查询跑了 30 秒。后来发现,toDate() 函数导致索引失效,改写成 WHERE timestamp >= '2024-01-01 00:00:00' AND timestamp < '2024-01-02 00:00:00' 后,秒级返回。
所以记住:WHERE 条件里尽量用原始列,别套函数。除非你建了物化列或者跳数索引。
4.3 GROUP BY 与聚合函数:分组统计的利器
GROUP BY 是量化分析里最常用的操作。你想统计每个股票每天的平均价格?每个用户的总交易额?都得靠它。
ClickHouse 支持的聚合函数很多,常用的有:
count()— 计数sum()— 求和avg()— 平均值min()/max()— 最小值/最大值uniq()— 近似去重计数(性能好)quantile()— 分位数(量化分析常用)
来看个例子:
SELECT
symbol,
toDate(timestamp) AS trade_date,
count() AS trade_count,
avg(price) AS avg_price,
sum(volume) AS total_volume
FROM trades
WHERE timestamp >= '2024-01-01'
GROUP BY symbol, trade_date
ORDER BY trade_date, symbol
这里有个细节:GROUP BY 的列顺序会影响内存使用。我建议把基数高的列放前面,基数低的放后面。比如 symbol 可能有几千种,trade_date 只有几百天,那先 GROUP BY symbol 再 trade_date 会更高效。
小技巧: 如果你只需要去重计数,别用 count(DISTINCT col),那玩意儿在 ClickHouse 里性能很差。用 uniq() 或者 uniqExact() 代替。前者是近似值,但快;后者精确,但慢一点。看你的业务容忍度。
4.4 ORDER BY 与 LIMIT:排序和截断
ORDER BY 用来排序,LIMIT 用来限制返回行数。这两个经常一起用,比如「取交易量最大的前 10 只股票」。
SELECT
symbol,
sum(volume) AS total_volume
FROM trades
WHERE timestamp >= '2024-01-01'
GROUP BY symbol
ORDER BY total_volume DESC
LIMIT 10
嗯,这里要注意:ORDER BY 在 ClickHouse 里是内存操作。如果数据量很大,排序会吃掉大量内存。我建议:
- 尽量配合 LIMIT 使用,减少排序数据量
- 如果必须全量排序,考虑用
ORDER BY ... LIMIT n WITH TIES语法,可以处理并列情况 - 排序字段尽量用数值类型,别用字符串,否则性能差很多
我曾经犯过的错: 有一次我写了个 ORDER BY rand() LIMIT 100 想随机抽样,结果跑了 5 分钟没出来。后来才知道,rand() 会导致全表排序,正确的做法是用 SAMPLE 子句或者 SELECT ... ORDER BY rand() LIMIT 100 配合 SETTINGS max_bytes_before_external_sort = 0 来优化。
知识体系总览
下面这张图把本章的核心逻辑串起来了。你可以看到,一个完整的查询从 SELECT 出发,经过 WHERE 过滤、GROUP BY 分组聚合、ORDER BY 排序,最后用 LIMIT 截断返回。每一步都有它的作用和注意事项。
综合示例:把知识点串起来
最后,咱们来个综合例子。假设你有一张股票交易表 stock_trades,你想找出 2024 年 1 月交易量最大的前 5 只股票,并且只看那些交易次数超过 1000 次的股票。
SELECT
symbol,
count() AS trade_count,
sum(volume) AS total_volume,
avg(price) AS avg_price
FROM stock_trades
WHERE timestamp >= '2024-01-01'
AND timestamp < '2024-02-01'
GROUP BY symbol
HAVING trade_count > 1000
ORDER BY total_volume DESC
LIMIT 5
这个查询把今天讲的内容全用上了:
- SELECT 选了 4 个字段,其中 3 个是聚合结果
- WHERE 用时间范围过滤,注意我用了
>=和<而不是BETWEEN,这样能精确控制边界 - GROUP BY 按股票代码分组
- HAVING 过滤掉交易次数少的组(这个知识点虽然没单独讲,但它是 GROUP BY 的好搭档)
- ORDER BY 按总交易量降序排列
- LIMIT 只取前 5 名
好了,查询基础就这些。记住:写查询的时候,多想想数据是怎么流动的,每一步会消耗多少资源。ClickHouse 虽然快,但也不是无脑快。把基础打扎实了,后面玩高级功能才顺手。