clickhouse学习笔记(五)SQL操作

目录

一、增

二、删改

三、查询以及各种子句

1、with子句

a、表达式为常量

b、表达式为函数调用

c、表达式为子查询

2、from子句

[3、array join子句](#3、array join子句)

[a、INNER ARRAY JOIN](#a、INNER ARRAY JOIN)

[b、LEFT ARRAY JOIN](#b、LEFT ARRAY JOIN)

c、数组的一些函数

groupArray

groupUniqArray

arrayFlatten

splitByChar

arrayJoin

arrayMap

嵌套类型

[4、 join子句](#4、 join子句)

连接精度

连接类型

注意事项

[5、WHERE 与 PREWHERE 子句](#5、WHERE 与 PREWHERE 子句)

[6,GROUP BY 子句](#6,GROUP BY 子句)

[WITH ROLLUP](#WITH ROLLUP)

[WITH CUBE](#WITH CUBE)

[WITH TOTALS](#WITH TOTALS)

7、having子句

[8、ORDER BY子句](#8、ORDER BY子句)

[NULLS LAST](#NULLS LAST)

[NULLS FIRST](#NULLS FIRST)

[9、limit by 子句](#9、limit by 子句)

[10、 limit 子句](#10、 limit 子句)

11、select和distinct子句


一、增

INSERT语句支持三种语法范式

第一种使用values格式

sql 复制代码
-- 中括号表示里面的内容可以省略
INSERT INTO [db.]table_name [(col1, col2, col3...)] VALUES (val1, val2, val3, ...), (val1, val2, val3, ...), ...

使用 VALUES 格式的语法写入数据时,还支持加入表达式或函数,例如:

sql 复制代码
INSERT INTO partizion_v2 VALUES('matsuri', toString(1+2), now())

第二种使用自定格式的语法:

sql 复制代码
INSERT INTO [db.]table_name [(col1, col2, col3...)] FORMAT format_name data_set

例子如下

sql 复制代码
INSERT INTO partition_v2 FORMAT CSV \
	'mea', 'www.mea.com', '2019-01-01'
	'nana', 'www.nana.com', '2019-02-01'
	'matsuri', 'www.matsuri.com', '2019-03-01'

第三种使用select子句形式的语法:

sql 复制代码
INSERT INTO [db.]table_name [(col1, col2, col3...)] SELECT ...

二、删改

ClickHouse 不是以事务为中心的数据库系统,它主要设计用于在线分析处理(OLAP)场景,强调的是高性能的读取和聚合查询,而不是复杂的事务处理。因此,ClickHouse 不支持传统意义上的 DELETE 和 UPDATE 操作,也不支持事务特性

然而,ClickHouse 提供了一种称为 Mutation 的机制,允许用户进行类似 DELETE 和 UPDATE 的操作。Mutation 语句的执行是一个异步的后台过程,语句被提交之后就会立即返回。所以这并不代表具体逻辑已经执行完毕,它的具体执行进度需要通过 system.mutations 系统表查询

Mutation 是通过 ALTER TABLE 语句实现的,使用方法例如:

DELETE 语句的完整语法如下所示:

highlighter-hljs 复制代码
ALTER TABLE [db_name.]table_name DELETE WHERE filter_expr
案例如下:删除id=xxx的
ALTER TABLE partition_v2 DELETE WHERE ID ='xxx'

UPDATE 支持在一条语句中同时定义多个修改字段,但是分区键和主键不能作为修改字段。修改语句如下:

highlighter-hljs 复制代码
ALTER TABLE [db_name.]table_name UPDATE column1 = expr1 [, ...] WHERE filter_expr

三、查询以及各种子句

注意:clickhouse对于sql语句的解析是大小写敏感的,ClickHouse 的类型也大小写敏感,比如:UInt8 不可以写成 uint8,String 不可以写成 string; 但关键字大小写不敏感,例如select a 和select A 意义不同,但是min max等大小写不敏感

1、with子句

格式为:with 表达式 as var

作用就是增加可读性和可维护性

一个子句可以为多个表达式起名例如:WITH 1 AS a, 2 AS b SELECT a + b;

a、表达式为常量

常量可以是整数,字符串,浮点数,甚至数组,都可以

使用方法例如:

sql 复制代码
with 10 as start

select number from system.numbers  where number>start limit 7
b、表达式为函数调用
sql 复制代码
WITH SUM(data_uncompressed_bytes) AS bytes
SELECT database, formatReadableSize(bytes) AS format
FROM system.columns
GROUP BY database
ORDER BY bytes DESC

/*
┌─database─┬─format───┐
│ system   │ 5.32 GiB │
│ default  │ 0.00 B   │
└──────────┴──────────┘
*/

如果不使用 WITH 子句,那么 SELECT 里面出现的就是 formatReadableSize(SUM(data_uncompressed_bytes)),这样读起来不是很方便,所以使用 WITH 子句将里面的聚合函数调用起一个名字叫 bytes,那么后面的查询直接使用 bytes 即可。

c、表达式为子查询
sql 复制代码
-- SELECT sum(data_uncompressed_bytes) FROM system.columns 会得到一个数值
-- 因此本质上和表达式为常量是类似的,只不过多了一个计算的过程
WITH (SELECT sum(data_uncompressed_bytes) FROM system.columns) AS total_bytes
SELECT database, 
       (sum(data_uncompressed_bytes) / total_bytes) * 100 AS database_disk_usage
FROM system.columns
GROUP BY database
ORDER BY database_disk_usage DESC
/*
┌─database─┬─database_disk_usage─┐
│ system   │                 100 │
│ default  │                   0 │
└──────────┴─────────────────────┘
*/

注意表达式只能返回的数据不能超过 1 行,否则会抛出异常,且不可以放在from后面作为临时表使用,如果需要多个值可以放在一个容器(列表、集合、字典等等)里面

而postgresql是可以返回任何数据,行数不限,并且可以放在from后面当临时表,

与postgresql的命名也不同,clickhouse别名在as后面,postgresql在as前面

2、from子句

from子句表示从何处读取数据,目前支持3种形式

sql 复制代码
从数据表中读取

SELECT name FROM people

从子查询中读取

SELECT max_id FROM (SELECT max(id) AS max_id FROM people)

从表函数中读取

SELECT number FROM numbers(N) -- 会返回 0 到 N - 1

3、array join子句

首先造一个包含array数组字段的测试表

sql 复制代码
CREATE TABLE t1 (
    title String,
    value Array(UInt8)
) ENGINE = Memory();

-- 然后写入数据
INSERT INTO t1 VALUES ('food', [1, 2, 3]), ('fruit', [3, 4]), ('meat', []);

-- 查询
SELECT * FROM t1;
/*
┌─title─┬─value───┐
│ food  │ [1,2,3] │
│ fruit │ [3,4]   │
│ meat  │ []      │
└───────┴─────────┘
*/

在一条 SELECT 语句中,只能存在一个 ARRAY JOIN(使用子查询除外),目前支持 INNER 和 LEFT 两种 JOIN 策略:

a、INNER ARRAY JOIN

ARRAY JOIN 在默认情况下使用的是 INNER JOIN 策略,例如下面的语句:

sql 复制代码
SELECT title, value FROM t1 ARRAY JOIN value;
/*
┌─title─┬─value─┐
│ food  │     1 │
│ food  │     2 │
│ food  │     3 │
│ fruit │     3 │
│ fruit │     4 │
└───────┴───────┘
*/

从查询结果可以发现,最终的数据基于 value 数组被展开成了多行,并且排除掉了空数组,同时会自动和其它字段进行组合(相当于按行合并)。在使用 ARRAY JOIN 时,如果还想访问展开前的数组字段,那么只需为原有的数组字段添加一个别名即可,例如:

sql 复制代码
 -- 如果不给 ARRAY JOIN 后面的 value 起一个别名,那么 value 就是展开后的结果
-- 如果给 ARRAY JOIN 后面的 value 起一个别名 val,那么 value 就还是展开前的数组字段
-- 而 val 才是展开后的结果,所以再反过来,让 val 出现在 SELECT 中即可
SELECT title, value, val FROM t1 ARRAY JOIN value AS val;
/*
┌─title─┬─value───┬─val─┐
│ food  │ [1,2,3] │   1 │
│ food  │ [1,2,3] │   2 │
│ food  │ [1,2,3] │   3 │
│ fruit │ [3,4]   │   3 │
│ fruit │ [3,4]   │   4 │
└───────┴─────────┴─────┘
*/

我们看到 ClickHouse 的确是当之无愧的最强 OLAP 数据库,不单单是速度快,最重要的是,它提供的查询语法也很方便。如果你用过 Hive 的话,会发现这里特别像里面的 lateral view explode 语法。

b、LEFT ARRAY JOIN

ARRAY JOIN 子句支持 LEFT 连接策略,例如执行下面的语句:

sql 复制代码
SELECT title, value, val FROM t1 LEFT ARRAY JOIN value AS val;
/*
┌─title─┬─value───┬─val─┐
│ food  │ [1,2,3] │   1 │
│ food  │ [1,2,3] │   2 │
│ food  │ [1,2,3] │   3 │
│ fruit │ [3,4]   │   3 │
│ fruit │ [3,4]   │   4 │
│ meat  │ []      │   0 │
└───────┴─────────┴─────┘
*/

在改为 LEFT 连接查询后,可以发现,在 INNER JOIN 中被排除掉的空数组出现在了返回的结果集中。但此时的 val 是零值。

c、数组的一些函数

有如下表

sql 复制代码
SELECT * FROM t2;
/*
┌─────────dt─┬─cash───────┐
│ 2020-01-01 │ [10,10,10] │
│ 2020-01-02 │ [20,20,20] │
│ 2020-01-01 │ [10,10,10] │
│ 2020-01-02 │ [20,20]    │
│ 2020-01-03 │ []         │
│ 2020-01-03 │ [30,30,30] │
└────────────┴────────────┘
*/
groupArray

它是把多行数据合并成一个数组,相当于是聚合函数的一种

sql 复制代码
SELECT dt, groupArray(cash) FROM t2 GROUP BY dt;
/*
┌─────────dt─┬─groupArray(cash)────────┐
│ 2020-01-01 │ [[10,10,10],[10,10,10]] │
│ 2020-01-02 │ [[20,20,20],[20,20]]    │
│ 2020-01-03 │ [[],[30,30,30]]         │
└────────────┴─────────────────────────┘
*/
groupUniqArray

在组合的时候会对元素进行去重

sql 复制代码
SELECT dt, groupUniqArray(cash) FROM t2 GROUP BY dt;
/*
┌─────────dt─┬─groupUniqArray(cash)─┐
│ 2020-01-01 │ [[10,10,10]]         │
│ 2020-01-02 │ [[20,20],[20,20,20]] │
│ 2020-01-03 │ [[],[30,30,30]]      │
└────────────┴──────────────────────┘
*/
arrayFlatten

类似于flatmap,将数组扁平化

sql 复制代码
SELECT dt, 
       groupArray(cash),
       arrayFlatten(groupArray(cash)) FROM t2 GROUP BY dt;
SELECT dt, groupUniqArray(cash) FROM t2 GROUP BY dt;
/*
┌─────────dt─┬─groupUniqArray(cash)─┐─arrayFlatten(groupArray(cash))─┐
│ 2020-01-01 │ [[10,10,10],[10,10,10]]         │ [10,10,10,10,10,10]         │
│ 2020-01-02 │ [[20,20],[20,20,20]]             │ [20,2020,20,20]    │
│ 2020-01-03 │ [[],[30,30,30]]                  │ [30,30,30]         │
└────────────┴──────────────────────------------┘──────────────────────┘
*/
splitByChar

将字符串按照指定字符分割成数组:

sql 复制代码
SELECT splitByChar('^', 'komeiji^koishi');
/*
┌─splitByChar('^', 'komeiji^koishi')─┐
│ ['komeiji','koishi']               │
└────────────────────────────────────┘
*/
arrayJoin

该函数和 ARRAY JOIN 子句的作用非常类似:

sql 复制代码
 SELECT * FROM t1;
/*
┌─title─┬─value───┐
│ food  │ [1,2,3] │
│ fruit │ [3,4]   │
│ meat  │ []      │
└───────┴─────────┘
*/

select title ,arrayjoin(value) from t1;

/*
┌─title─┬─arrayjoin(value)─┐
│ food  │     1 │
│ food  │     2 │
│ food  │     3 │
│ fruit │     3 │
│ fruit │     4 │
└───────┴───────┘
*/
arrayMap

对数组中的每一个元素都以相同的规则进行映射:

sql 复制代码
-- arrayMap(x -> x * 2, value) 表示将 value 中的每一个元素都乘以 2,然后返回一个新数组
-- 而 mapV 就是变换过后的新数组,直接拿来用即可
SELECT title, arrayMap(x -> x * 2, value) AS mapV, v
FROM t1 LEFT ARRAY JOIN mapV as v
/*
┌─title─┬─mapV────┬─v─┐
│ food  │ [2,4,6] │ 2 │
│ food  │ [2,4,6] │ 4 │
│ food  │ [2,4,6] │ 6 │
│ fruit │ [6,8]   │ 6 │
│ fruit │ [6,8]   │ 8 │
│ meat  │ []      │ 0 │
└───────┴─────────┴───┘
*/


-- 另外展开的字段也可以不止一个
SELECT title, 
       arrayMap(x -> x * 2, value) AS mapV, v,
       value, v_1
FROM t1 LEFT ARRAY JOIN mapV as v, value AS v_1
/*
┌─title─┬─mapV────┬─v─┬─value───┬─v_1─┐
│ food  │ [2,4,6] │ 2 │ [1,2,3] │   1 │
│ food  │ [2,4,6] │ 4 │ [1,2,3] │   2 │
│ food  │ [2,4,6] │ 6 │ [1,2,3] │   3 │
│ fruit │ [6,8]   │ 6 │ [3,4]   │   3 │
│ fruit │ [6,8]   │ 8 │ [3,4]   │   4 │
│ meat  │ []      │ 0 │ []      │   0 │
└───────┴─────────┴───┴─────────┴─────┘
*/
嵌套类型

在写入嵌套数据类型时,记得同一行数据中各个数组的长度需要对齐,而对多行数据之间的数组长度没有限制,否则会报错:如下

sql 复制代码
CREATE TABLE t3(
    title String,
    nested Nested
    (
        v1 UInt32,
        v2 UInt64
    )
) ENGINE = Log();

-- 接着写入测试数据
-- 在写入嵌套数据类型时,记得同一行数据中各个数组的长度需要对齐,而对多行数据之间的数组长度没有限制
INSERT INTO t3
VALUES ('food', [1, 2, 3], [10, 20, 30]),
       ('fruit', [4, 5], [40, 50]),
       ('meat', [], [])
sql 复制代码
INSERT INTO t3
VALUES ('food', [1, 2, 3], [10, 20, 30,40]),
       ('fruit', [4, 5], [40, 50]),
       ('meat', [], [])

当数组大小不同时如上,会报错,当然不同行的数组大小可以不同,例如food和fruit
SQL 错误 [190]: ClickHouse exception, code: 190, host: 192.168.81.15, port: 8123; Code: 190. DB::Exception: Elements 'nested.v1' and 'nested.v2' of Nested data structure 'nested' (Array columns) have different array sizes. (SIZES_OF_ARRAYS_DOESNT_MATCH) (version 22.1.3.7 (official build))

对嵌套类型数据的访问,ARRAY JOIN 既可以直接使用字段列名:

sql 复制代码
-- nested 只有 v1 和 v2
-- 所以 ARRAY JOIN nested.v1, nested.v2 等价于 ARRAY JOIN nested
SELECT title, nested.v1, nested.v2 FROM t3 ARRAY JOIN nested.v1, nested.v2
/*
┌─title─┬─nested.v1─┬─nested.v2─┐
│ food  │         1 │        10 │
│ food  │         2 │        20 │
│ food  │         3 │        30 │
│ fruit │         4 │        40 │
│ fruit │         5 │        50 │
└───────┴───────────┴───────────┘
*/

嵌套类型也支持 ARRAY JOIN 部分嵌套字段,可以看到,在这种情形下,只有被 ARRAY JOIN 的数组才会展开。

sql 复制代码
SELECT title, nested.v1, nested.v2 FROM t3 ARRAY JOIN nested.v1
/*
┌─title─┬─nested.v1─┬─nested.v2──┐
│ food  │         1 │ [10,20,30] │
│ food  │         2 │ [10,20,30] │
│ food  │         3 │ [10,20,30] │
│ fruit │         4 │ [40,50]    │
│ fruit │         5 │ [40,50]    │
└───────┴───────────┴────────────┘
*/

在查询嵌套类型时也能够通过别名的形式访问原始数组:

sql 复制代码
SELECT title, 
       nested.v1, nested.v2, 
       n.v1, n.v2  
from t3 ARRAY JOIN nested AS n;
/*
┌─title─┬─nested.v1─┬─nested.v2──┬─n.v1─┬─n.v2─┐
│ food  │ [1,2,3]   │ [10,20,30] │    1 │   10 │
│ food  │ [1,2,3]   │ [10,20,30] │    2 │   20 │
│ food  │ [1,2,3]   │ [10,20,30] │    3 │   30 │
│ fruit │ [4,5]     │ [40,50]    │    4 │   40 │
│ fruit │ [4,5]     │ [40,50]    │    5 │   50 │
└───────┴───────────┴────────────┴──────┴──────┘
*/

4、 join子句

JOIN 子句可以对左右两张表的数据进行连接,它的语法包含连接精度和连接类型两部分。

连接精度

连接精度决定了 JOIN 查询在连接数据时所使用的策略,目前支持 ALL、ANY 和 ASOF 三种类型(还有两种类型SEMI 和 ANTI只能用在left join和right join上面)。如果不主动声明,则默认是 ALL

举个例子有如下表数据:

SELECT * FROM tbl_1;
/*
┌─id─┬─code1─┬─count─┐
│ 1 │ A001 │ 30 │
│ 2 │ A002 │ 28 │
│ 3 │ A003 │ 32 │
└────┴───────┴───────┘
*/

SELECT * FROM tbl_2;
/*
┌─id─┬─code2─┬─count─┐
│ 1 │ B001 │ 35 │
│ 1 │ B001 │ 29 │
│ 3 │ B003 │ 31 │
│ 4 │ B004 │ 38 │
└────┴───────┴───────┘
*/

下面进行测试

sql 复制代码
SELECT t1.id, t1.code1, t2.code2 
FROM tbl_1 AS t1 
ALL INNER JOIN tbl_2 AS t2
ON t1.id = t2.id;
/*
┌─id─┬─code1─┬─code2─┐
│  1 │ A001  │ B001  │
│  1 │ A001  │ B001  │
│  3 │ A003  │ B003  │
└────┴───────┴───────┘
*/
-- 一切正常,跟一般的关系型数据库是类似的,但如果将 ALL 改成 ANY
SELECT t1.id, t1.code1, t2.code2 
FROM tbl_1 AS t1 
ANY INNER JOIN tbl_2 AS t2
ON t1.id = t2.id;
/*
┌─id─┬─code1─┬─code2─┐
│  1 │ A001  │ B001  │
│  3 │ A003  │ B003  │
└────┴───────┴───────┘
*/

除了 ALL 和 ANY 之外还有一个 ASOF,ALL 还是 ANY,在连接的时候必须是等值连接。但 ASOF 表示模糊连接,例如 t1.id >= t2.id 例子如下

sql 复制代码
SELECT t1.id, t1.code1, t2.code2, t1.count AS count1, t2.count AS count2
FROM tbl_1 AS t1 
ASOF INNER JOIN tbl_2 AS t2
ON t1.id = t2.id AND t1.count > t2.count;
/*
┌─id─┬─code1─┬─code2─┬─count1─┬─count2─┐
│  1 │ A001  │ B001  │     30 │     29 │
│  3 │ A003  │ B003  │     32 │     31 │
└────┴───────┴───────┴────────┴────────┘
*/

SEMI 和 ANTI

我们之前说连接精度不止 ALL、ANY、ASOF 三种,还有 SEMI 和 ANTI,只不过这两个比较特殊,因为它们只能用在 LEFT JOIN 和 RIGHT JOIN 上面,所以我们单独介绍。

  • t1 SEMI LEFT JOIN t2 USING(id):遍历 t1 中的 id,如果存在于 t2 中,则输出
  • t1 SEMI RIGHT JOIN t2 USING(id):遍历 t2 中的 id,如果存在于 t1 中,则输出
  • t1 ANTI LEFT JOIN t2 USING(id):遍历 t1 中的 id,如果不存在于 t2 中,则输出
  • t1 ANTI RIGHT JOIN t2 USING(id):遍历 t2 中的 id,如果不存在于 t1 中,则输出

这个 SEMI 的功能貌似有些重复了,因为我们使用 ALL 和 ANY 完全可以取代。其实如果你用过 hive 的话,会发现 SEMI LEFT JOIN 和 ANTI LEFT JOIN 是 IN/EXISTS 的一种更加高效的实现:

结论:

1,如果左表内的一行数据,在右表中有多行数据与之连接匹配,那么当连接精度为 ALL,会返回右表中全部连接的数据;

2,当连接精度为 ANY,会仅返回右表中第一行连接的数据

3,如果连接精度为 ASOF,那么允许在等值连接条件后面追加一个非等值连接,所以上面的 t1.id = t2.id 是等值连接,t1.count > t2.count 是非等值连接。但需要注意的是:使用非等值连接时,这个非等值可以是 >、>=、<、<=,但不能是 !=;并且对于 ASOF 而言,连接条件必须是等值连接和非等值连接的组合,两者缺一不可。

连接类型
sql 复制代码
-- 省略连接精度,默认为 ALL
-- 左连接
SELECT t1.id, t1.code1, t2.code2 
FROM tbl_1 t1 LEFT JOIN tbl_2 t2 
USING(id); -- 等价于 t1.id = t2.id
/*
┌─id─┬─code1─┬─code2─┐
│  1 │ A001  │ B001  │
│  1 │ A001  │ B001  │
│  2 │ A002  │       │
│  3 │ A003  │ B003  │
└────┴───────┴───────┘
*/

-- 右连接
SELECT t1.id, t1.code1, t2.code2 
FROM tbl_1 t1 RIGHT JOIN tbl_2 t2 
USING(id);
/*
┌─id─┬─code1─┬─code2─┐
│  1 │ A001  │ B001  │
│  1 │ A001  │ B001  │
│  3 │ A003  │ B003  │
└────┴───────┴───────┘
┌─id─┬─code1─┬─code2─┐
│  4 │       │ B004  │
└────┴───────┴───────┘
*/

-- 全连接
SELECT t1.id, t1.code1, t2.code2 
FROM tbl_1 t1 FULL JOIN tbl_2 t2 
USING(id);
/*
┌─id─┬─code1─┬─code2─┐
│  1 │ A001  │ B001  │
│  1 │ A001  │ B001  │
│  2 │ A002  │       │
│  3 │ A003  │ B003  │
└────┴───────┴───────┘
┌─id─┬─code1─┬─code2─┐
│  4 │       │ B004  │
└────┴───────┴───────┘
*/

和关系型数据库类似,但有一点区别,就是当没有与之匹配的记录时,会使用对应类型的空值进行补全,而不是 Null。这里没有指定连接精度,默认为 ALL

注意事项

最后,还有两个关于 JOIN 查询的注意事项。

1. 关于性能

最后,还有两个关于 JOIN 查询的注意事项。为了能够优化 JOIN 查询性能,首先应该遵循左大右小的原则,即数据量小的表要放在右侧。这是因为在执行 JOIN 查询时,无论使用的是哪种连接方式,右表都会被全部加载到内存中与左表进行比较。

其次,JOIN 查询目前没有缓存的支持,这意味着每一次 JOIN 查询,即便是连续执行相同的 SQL,也都会生成一次全新的执行计划。如果应用程序会大量使用 JOIN 查询,则需要进一步考虑借助上层应用侧的缓存服务或使用 JOIN 表引擎来改善性能。

最后,如果是在大量维度属性补全的查询场景中,则建议使用字典代替 JOIN 查询。因为在进行多表的连接查询时,查询会转换成两两连接的形式,而这种滚雪球式的查询很可能带来性能问题。

2. 空值策略

在之前的介绍中,连接查询的空值(那些未被连接的数据)是由默认值填充的,这与其他数据库所采取的策略不同(由Null 填充)。连接查询的空值策略通过 join_use_nulls 参数指定的,默认为 0。当参数值为 0 时,空值由数据类型的默认值填充;而当参数值为 1 时,空值由 Null 填充。

5、WHERE 与 PREWHERE 子句

除了 WHERE,ClickHouse 还支持全新的 PREWHERE 子句,PREWHERE 目前只能用于 MegeTee 系列的表引擎,它可以看作对是 WHERE 的一种优化,其作用与 WHERE 相同,均是用来过滤数据。但它们的不同之处在于。使用 PREWHERE 时,首先只会读取 PREWHERE 指定的列字段数据,用于数据过滤的条件判断。待数据过滤之后再读取 SELECT 声明的列字段以补全其余属性。所以在一些场合下,PREWHERE 相比 WHERE 而言,处理的数据量更少,性能更高。

既然 WHERE 子句性能更优,那么是否需要将所有的 WHERE 子句都替换成 PREWHERE 子句呢?其实大可不必,因为 ClickHouse 实现了自我优化的功能,会在条件合适的情况下将 WHERE 替换为 PREWHERE。如果想开启这项特性,只需要将 optimize_move_to_prewhere 设置为 1 即可,当然默认就为 1,即开启状态。

6,GROUP BY 子句

聚合查询能配合 WITH ROLLUP、WITH CUBE、WITH TOTALS 三种修饰符获取额外的汇总信息

造测试表

sql 复制代码
CREATE TABLE sales_data(
    product String,
    channel String,
    amount int
    
) ENGINE = Log();

-- 接着写入测试数据
INSERT INTO sales_data
VALUES ('桔子', '淘宝', 248175),
       ('香蕉', '淘宝', 252148),
        ('苹果', '店面', 246198),
         ('香蕉', '店面', 256602),
          ('桔子', '店面', 245029),
           ('苹果', '淘宝', 252908),
            ('苹果', '京东', 252057),
             ('桔子', '京东', 251795),
              ('香蕉', '京东', 245904)
     
WITH ROLLUP

GROUP BY 子句加上 WITH ROLLUP 选项时,首先按照全部的分组字段进行分组汇总;然后从右往左依次去掉一个分组字段再进行分组汇总,被去掉的字段显示为零值;最后,将所有的数据进行一次汇总,所有的分组字段都显示为零值。

sql 复制代码
select product,channel,sum(amount)
from  sales_data
group by product,channel 
with rollup

查询结果如下

桔子	淘宝	248175
香蕉	淘宝	252148
苹果	店面	246198
香蕉	店面	256602
桔子	店面	245029
苹果	淘宝	252908
苹果	京东	252057
桔子	京东	251795
香蕉	京东	245904
香蕉		    754654
桔子		    744999
苹果		    751163
		        2250816

我们注意到,多了四条数据,上面三条,就是按照 product、channel 汇总之后,再单独按 product 汇总,而此时会给对应的 channel 设为零值(这里是空字符串,关系型数据库中为 Null)。同理最后一条数据是全量汇总,不需要指定 product 和 channel,所以显示为 product 和 channel 都显示为零值。我们看到这就相当于按照 product 单独聚合然后再自动拼接在上面了,排好序,并且自动将 channel 赋值为零值,同理最后一条数据也是如此

WITH CUBE

CUBE 代表立方体,它用于对分组字段进行各种可能的组合,能够产生多维度的交叉统计结果,CUBE 通常用于数据仓库中的交叉报表分析

sql 复制代码
select product,channel,sum(amount)
from  sales_data
group by product,channel 
with CUBE 



桔子	淘宝	248175
香蕉	淘宝	252148
苹果	店面	246198
香蕉	店面	256602
桔子	店面	245029
苹果	淘宝	252908
苹果	京东	252057
桔子	京东	251795
香蕉	京东	245904
香蕉		    754654
桔子		    744999
苹果		    751163
	    淘宝	753231
	    京东	749756
	    店面	747829
		        2250816

CUBE 返回了更多的分组数据,其中不仅包含了 ROLLUP 汇总的结果,还包含了相当于按照 channel 进行聚合的记录。因此随着分组字段的增加,CUBE 产生的组合将会呈指数级增长

WITH TOTALS

只包含一个全局汇总的结果

sql 复制代码
select product,channel,sum(amount)
from  sales_data
group by product,channel 
with TOTALS


桔子	淘宝	248175
香蕉	淘宝	252148
苹果	店面	246198
香蕉	店面	256602
桔子	店面	245029
苹果	淘宝	252908
苹果	京东	252057
桔子	京东	251795
香蕉	京东	245904
		        2250816

7、having子句

HAVING 子句要和 GROUP BY 子句同时出现,不能单独使用

sql 复制代码
select product,channel,sum(amount) as cnt 
from  sales_data
group by product,channel 
having  cnt>250000


香蕉	淘宝	252148
香蕉	店面	256602
苹果	淘宝	252908
苹果	京东	252057
桔子	京东	251795

8、ORDER BY子句

在 MergeTree 表引擎中也有 ORDER BY 参数用于指定排序键,这个的作用域是分区内,所以当查询时如果有多个分区就不保证顺序了,所以需要order by。在使用时可以定义多个排序键,每个排序键后需紧跟ASC 或者DESC,不写默认为asc

例如

sql 复制代码
SELECT * FROM tbl ORDER BY v1 ASC, v2 DESC;
SELECT * FROM tbl ORDER BY v1, v2 DESC;
NULLS LAST

null值排在最后,无论升序还是降序

写法如下

sql 复制代码
select arrayJoin([1,22,NULL,3,-1]) as v order by v asc

 -1
1
3
22
NULL
NULLS FIRST

null值排在第一,无论升序还是降序,写法如上

9、limit by 子句

LIMIT BY 运行于 ORDER BY 之后和 LIMIT 之前,它能够按照指定分组,最多返回前 n 行数据(少于 n 行则按照实际数量返回),常用于 分组TOP N 的查询场景。LIMIT BY 语法规则如下:

sql 复制代码
表数据
香蕉	店面	256602
苹果	淘宝	252908
香蕉	淘宝	252148
苹果	京东	252057
桔子	京东	251795
桔子	淘宝	248175
苹果	店面	246198
香蕉	京东	245904
桔子	店面	245029
sql 复制代码
select product,channel,sum(amount) as cnt 
from  sales_data
group by product,channel 
order by cnt desc 
limit 1 by channel

香蕉	店面	256602
苹果	淘宝	252908
苹果	京东	252057

LIMIT BY 也可以指定偏移量,因为不一定从一条开始选择,而指定偏移量有两种方式:

  • 一种方式如上:limit 条数 by 维度
  • 另一种方式:limit 条数,偏移量(从第几条开始)by 维度 案例如下
sql 复制代码
select product,channel,sum(amount) as cnt 
from  sales_data
group by product,channel 
order by cnt desc 
limit 1 ,1 by channel

香蕉	淘宝	252148
桔子	京东	251795
苹果	店面	246198

10、 limit 子句

用法有三种如下

sql 复制代码
LIMIT N
LIMIT N OFFSET M
LIMIT M, N

limit 与limit by的区别

11、select和distinct子句

支持正则查询,例如下面会选择以 n 开头和包含字母 p 的字段:

sql 复制代码
SELECT COLUMNS('^n'), COLUMNS('p') FROM system.databases

distinct和group by 执行后虽然结果相同,但是distinct的执行计划更简单,而且在不使用order by子句时,distinct在limit n满足条件时立刻结束查询,后面的就不查了,group by会分组执行完后在limit

相关推荐
金星娃儿20 分钟前
MATLAB基础知识笔记——(矩阵的运算)
笔记·matlab·矩阵
一只特立独行的程序猿22 分钟前
关于GCC内联汇编(也可以叫内嵌汇编)的简单学习
汇编·学习·gcc
虾球xz28 分钟前
游戏引擎学习第10天
学习·游戏引擎
Chef_Chen31 分钟前
从0开始学习机器学习--Day25--SVM作业
学习·机器学习·支持向量机
L_cl38 分钟前
Python学习从0到1 day28 Python 高阶技巧 ⑧ 递归
学习
vortex51 小时前
Vim 编辑器学习笔记
学习·编辑器·vim
源于花海1 小时前
论文学习(四) | 基于数据驱动的锂离子电池健康状态估计和剩余使用寿命预测
论文阅读·人工智能·学习·论文笔记
心怀梦想的咸鱼1 小时前
Ue5 umg学习(一)
学习·ue5
楚疏笃1 小时前
鸿蒙学习生态应用开发能力全景图-开发者支持平台(5)
学习·华为·harmonyos
4v1d1 小时前
边缘计算的学习
人工智能·学习·边缘计算