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 支持常见的比较运算符:=!=><INLIKE 等等。但有个关键点:尽量用主键或分区键做过滤。因为 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 截断返回。每一步都有它的作用和注意事项。

ClickHouse 查询执行流程 SELECT 选择列 / 表达式 WHERE 过滤条件 GROUP BY 分组 + 聚合函数 ORDER BY 排序 LIMIT 截断返回 最终结果集 注意:WHERE 在 GROUP BY 之前执行,HAVING 在 GROUP BY 之后执行(本章未涉及)

综合示例:把知识点串起来

最后,咱们来个综合例子。假设你有一张股票交易表 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 虽然快,但也不是无脑快。把基础打扎实了,后面玩高级功能才顺手。


专注资料整理