前段时间在实际工作中,用户的一条SQL引发了我一些思考。写一篇简单的博文来记录下。实际表的列名等已替换。
SELECT
*
FROM
db1.table1 as t1
LEFT JOIN db2.table2 as t2 ON
t1.col1 = t2.col2
WHERE
t1.time >= '2020-01-01 00:00:00'
AND t1.time < '2020-01-31 00:00:00'
AND t1.col2 in ('5K')
这条SQL语句在clickhouse19.18.1.1版本会报如下异常(这是clickhouse自身的一个bug,在20.3.2.1版本中已修复,2020-03-12)
Code: 48. DB::Exception: Received from localhost:10115. DB::Exception:
Method createColumn() is not implemented for data type Set.
当面对这样一个报错,我们如何着手解决?
先看异常提示:字面意思是某个数据类型集没有实现createColumn。那我们先去掉一些过滤条件看下:
SELECT
*
FROM
db1.table1 as t1
LEFT JOIN db2.table2 as t2 ON
t1.col1 = t2.col2
WHERE
t1.time >= '2020-01-01 00:00:00'
AND t1.time < '2020-01-31 00:00:00'
SELECT
*
FROM
db1.table1 as t1
LEFT JOIN db2.table2 as t2 ON
t1.col1 = t2.col2
WHERE
t1.col2 in ('5K')
无论上面那种情况都不再报错了。再结合下异常错误提示,我们可以推测,一定是JOIN时,对字段的推导有问题了。那么重点来了,在不升级版本或者干脆这个bug就没修复,客户这边也不想大幅度修改SQL,那我们又该怎么处理?
通过分析,这条SQL就是一条JOIN语句,并且过滤条件全部是一张表的列,那么我们是否可以先利用过滤字段查出一张自表之后,再去JOIN呢?带着这样的思路,我们修改SQL如下:
SELECT
*
FROM (select * from db1.table1 where col2 in ('5K')) as t1
LEFT JOIN db2.table2 as t2 ON
t1.col1 = t2.col2
WHERE
t1.time >= '2020-01-01 00:00:00'
AND t1.time < '2020-01-31 00:00:00'
这次我们发现SQL不再报错了。接着对于clickhouse来说,不要使用select * 这样的影响性能的查询(虽然这样也很快),我们把过滤条件中用到的列查出来即可。SQL改动如下:
SELECT
*
FROM (select col1,col2,time from db1.table1 where col2 in ('5K')) as t1
LEFT JOIN db2.table2 as t2 ON
t1.col1 = t2.col2
WHERE
t1.time >= '2020-01-01 00:00:00'
AND t1.time < '2020-01-31 00:00:00'
到这里我们已经利用子查询,解决了之前的问题。但我们还想再改,我们希望能尽可能跟客户原先的SQL一致,于是我们这次把子查询中的条件再次拿到外层,如下:
SELECT
*
FROM (select col1,col2,time from db1.table1) as t1
LEFT JOIN db2.table2 as t2 ON
t1.col1 = t2.col2
WHERE
t1.time >= '2020-01-01 00:00:00'
AND t1.time < '2020-01-31 00:00:00'
AND t1.col2 in ('5K')
至此,我们在没有更新版本,没有解决bug的情况下,仅仅调整SQL语句就达到了用户想要的目的。具体底层的实现,我们后期可以写一篇博文来了解。
新年第一篇文章,祝各位Happy 牛 year!!!