clickhouse--特殊子句


1. WITH子句

1.1 语法

WITH <expression> AS <identifier>
-- or
WITH <identifier> AS <subquery expression>

1.2 将常量表达式作为“变量”使用

WITH '2019-08-01 15:23:00' as ts_upper_bound
SELECT *
FROM hits
WHERE
    EventDate = toDate(ts_upper_bound) AND
    EventTime <= ts_upper_bound;

1.3 将sum(col)提取出来

WITH sum(bytes) as s
SELECT
    formatReadableSize(s),
    table
FROM system.parts
GROUP BY table
ORDER BY s

formatReadableSize用来将数据转为可读单位。sum(bytes)不需要用于显示,但需要用来进行排序,提取出来可以使代码更简洁。

1.4 使用标量子查询的结果

/* this example would return TOP 10 of most huge tables */
WITH
    (SELECT sum(bytes) FROM system.parts WHERE active) AS total_disk_usage
SELECT
    (sum(bytes) / total_disk_usage) * 100 AS table_disk_usage,
    table
FROM system.parts
GROUP BY table
ORDER BY table_disk_usage DESC
LIMIT 10;

1.5 在子查询中重用表达式

WITH test1 AS (SELECT i + 1, j + 1 FROM test1)
SELECT * FROM test1;

with test1 as (select  * FROM hits_v1 limit 10)
select * from test1;

2. ALL 子句

SELECT ALLSELECT 不带 DISTINCT 是一样的。

SELECT sum(ALL number) FROM numbers(10);
--等价于
SELECT sum(number) FROM numbers(10);

3. ARRAY JOIN子句

3.1 语法

SELECT <expr_list>
FROM <left_subquery>
[LEFT] ARRAY JOIN <array>
[WHERE|PREWHERE <expr>]
...

3.2 单层数组

3.2.1 数据准备

准备一个数据表,其中包含一个数组列。

CREATE TABLE test.arrays_test
(
    s String,
    arr Array(UInt8)
) ENGINE = Memory;

INSERT INTO test.arrays_test
VALUES ('Hello', [1,2]), ('World', [3,4,5]), ('Goodbye', []);

--查询数据
select * from test.arrays_test;
/*
┌─s───────┬─arr─────┐
│ Hello   │ [1,2]   │
│ World   │ [3,4,5] │
│ Goodbye │ []      │
└─────────┴─────────┘
*/

3.2.2 常规array join

常规的array join,使用数组列作为array join的对象,结果会将数组列展开成单个元素单独成一行,其他的列重复显示,空数组行被丢弃。

SELECT s, arr FROM test.arrays_test ARRAY JOIN arr;
/*
┌─s─────┬─arr─┐
│ Hello │   1 │
│ Hello │   2 │
│ World │   3 │
│ World │   4 │
│ World │   5 │
└───────┴─────┘
*/

LEFT ARRAY JOIN同样可以执行,结果与array join类似。不同之处是空数组列也可以保留,会填充默认数值。

SELECT s, arr FROM test.arrays_test LEFT ARRAY JOIN arr;
/*
┌─s───────┬─arr─┐
│ Hello   │   1 │
│ Hello   │   2 │
│ World   │   3 │
│ World   │   4 │
│ World   │   5 │
│ Goodbye │   0 │
└─────────┴─────┘
*/

3.2.3 取别名

在取别名的情况下,数组中的元素可以通过别名访问,但数组本身则通过原始名称访问。
例如在本例中,ARRAY JOIN arr AS a,对arr取了别名,因此arr数组列展开以后重命名为a列,可以看到结果中a列就是展开的数组元素。

SELECT s, arr, a FROM test.arrays_test
ARRAY JOIN arr AS a;
/*
┌─s─────┬─arr─────┬─a─┐
│ Hello │ [1,2]   │ 1 │
│ Hello │ [1,2]   │ 2 │
│ World │ [3,4,5] │ 3 │
│ World │ [3,4,5] │ 4 │
│ World │ [3,4,5] │ 5 │
└───────┴─────────┴───┘
*/

3.2.4 外部数组array join

也可以对外部数组执行array join,其他列从数据表中获取。本例中对外部数组[1, 2, 3]执行array join,并取别名arr_external,数组列同样可以展开。

SELECT s, arr_external FROM test.arrays_test
ARRAY JOIN [1, 2, 3] AS arr_external;
/*
┌─s───────┬─arr_external─┐
│ Hello   │            1 │
│ Hello   │            2 │
│ Hello   │            3 │
│ World   │            1 │
│ World   │            2 │
│ World   │            3 │
│ Goodbye │            1 │
│ Goodbye │            2 │
│ Goodbye │            3 │
└─────────┴──────────────┘
*/

3.2.5 多个数组

多个数组可以并列同时执行,用逗号隔开,且数组长度应一致。本例中,将arr列展开并取别名为a,同时获取数组元素的索引并取别名为num,arrayMap(x -> x + 1, arr)对数组中每个元素进行加1操作,并重命名为mapped。

SELECT s, arr, a, num, mapped FROM test.arrays_test
ARRAY JOIN arr AS a, arrayEnumerate(arr) AS num, arrayMap(x -> x + 1, arr) AS mapped;
/*
┌─s─────┬─arr─────┬─a─┬─num─┬─mapped─┐
│ Hello │ [1,2]   │ 1 │   1 │      2 │
│ Hello │ [1,2]   │ 2 │   2 │      3 │
│ World │ [3,4,5] │ 3 │   1 │      4 │
│ World │ [3,4,5] │ 4 │   2 │      5 │
│ World │ [3,4,5] │ 5 │   3 │      6 │
└───────┴─────────┴───┴─────┴────────┘
*/

在下面的例子中,arrayEnumerate(arr) AS num是获取数组元素的索引值数组(从1开始),将其展开并重命名为num列。而元素的数组列对应的索引数组则直接通过arrayEnumerate(arr)获取。

SELECT s, arr, a, num, arrayEnumerate(arr) FROM test.arrays_test
ARRAY JOIN arr AS a, arrayEnumerate(arr) AS num;
/*
┌─s─────┬─arr─────┬─a─┬─num─┬─arrayEnumerate(arr)─┐
│ Hello │ [1,2]   │ 1 │   1 │ [1,2]               │
│ Hello │ [1,2]   │ 2 │   2 │ [1,2]               │
│ World │ [3,4,5] │ 3 │   1 │ [1,2,3]             │
│ World │ [3,4,5] │ 4 │   2 │ [1,2,3]             │
│ World │ [3,4,5] │ 5 │   3 │ [1,2,3]             │
└───────┴─────────┴───┴─────┴─────────────────────┘
*/

3.3 嵌套数据结构

3.3.1 数据准备

准备一个数据表,包含一个嵌套数据结构(多维数组),嵌套数据结构可以当做是所有列都是相同长度的多列数组,其中每个字段都是一个数组,并且行与行之间的数组长度无须对齐。

参考:https://clickhouse.com/docs/zh/sql-reference/data-types/nested-data-structures/nested/

CREATE TABLE test.nested_test
(
    s String,
    nest Nested(
    x UInt8,
    y UInt32)
) ENGINE = Memory;

INSERT INTO test.nested_test
VALUES ('Hello', [1,2], [10,20]), ('World', [3,4,5], [30,40,50]), ('Goodbye', [], []);

--查看数据
select * from test.nested_test;
/*
┌─s───────┬─nest.x──┬─nest.y─────┐
│ Hello   │ [1,2]   │ [10,20]    │
│ World   │ [3,4,5] │ [30,40,50] │
│ Goodbye │ []      │ []         │
└─────────┴─────────┴────────────┘
*/

3.3.2 直接array join

SELECT 查询只有在使用 ARRAY JOIN 的时候才可以指定整个嵌套数据结构的名称,结果嵌套结构中每个数组都被展开,且一一对应。

SELECT s, `nest.x`, `nest.y` FROM test.nested_test 
ARRAY JOIN nest;
/*
┌─s─────┬─nest.x─┬─nest.y─┐
│ Hello │      1 │     10 │
│ Hello │      2 │     20 │
│ World │      3 │     30 │
│ World │      4 │     40 │
│ World │      5 │     50 │
└───────┴────────┴────────┘
*/

也可以直接指定嵌套数据结构中的列进行array join,结果相同。

SELECT s, `nest.x`, `nest.y` FROM test.nested_test 
ARRAY JOIN `nest.x`, `nest.y`;
/*
┌─s─────┬─nest.x─┬─nest.y─┐
│ Hello │      1 │     10 │
│ Hello │      2 │     20 │
│ World │      3 │     30 │
│ World │      4 │     40 │
│ World │      5 │     50 │
└───────┴────────┴────────┘
*/

3.3.3 部分展开

如果在array join中仅指定嵌套数组的部分列,则数组仅针对这部分列展开,余下的不变。

SELECT s, `nest.x`, `nest.y` FROM test.nested_test
ARRAY JOIN `nest.x`;
/*
┌─s─────┬─nest.x─┬─nest.y─────┐
│ Hello │      1 │ [10,20]    │
│ Hello │      2 │ [10,20]    │
│ World │      3 │ [30,40,50] │
│ World │      4 │ [30,40,50] │
│ World │      5 │ [30,40,50] │
└───────┴────────┴────────────┘
*/

3.3.4 取别名

如果对嵌套类型数据进行array join之后取别名,则select的时候只能通过别名的引用获取展开后的数组,原来名称的引用获取的仍然是数组。

SELECT s, `n.x`, `n.y`, `nest.x`, `nest.y` FROM test.nested_test
ARRAY JOIN nest AS n;
/*
┌─s─────┬─n.x─┬─n.y─┬─nest.x──┬─nest.y─────┐
│ Hello │   1 │  10 │ [1,2]   │ [10,20]    │
│ Hello │   2 │  20 │ [1,2]   │ [10,20]    │
│ World │   3 │  30 │ [3,4,5] │ [30,40,50] │
│ World │   4 │  40 │ [3,4,5] │ [30,40,50] │
│ World │   5 │  50 │ [3,4,5] │ [30,40,50] │
└───────┴─────┴─────┴─────────┴────────────┘
*/

3.3.5 获取索引

嵌套类型数据的索引需要通过单个数组列来获取,下面的例子中,通过nest.x来获取数组元素的索引。

SELECT s, `n.x`, `n.y`, `nest.x`, `nest.y`, num FROM test.nested_test
ARRAY JOIN nest AS n, arrayEnumerate(`nest.x`) AS num;
/*
┌─s─────┬─n.x─┬─n.y─┬─nest.x──┬─nest.y─────┬─num─┐
│ Hello │   1 │  10 │ [1,2]   │ [10,20]    │   1 │
│ Hello │   2 │  20 │ [1,2]   │ [10,20]    │   2 │
│ World │   3 │  30 │ [3,4,5] │ [30,40,50] │   1 │
│ World │   4 │  40 │ [3,4,5] │ [30,40,50] │   2 │
│ World │   5 │  50 │ [3,4,5] │ [30,40,50] │   3 │
└───────┴─────┴─────┴─────────┴────────────┴─────┘
*/

下面的例子中同样可以获取索引:

SELECT *, num FROM test.nested_test
ARRAY JOIN nest, arrayEnumerate(`nest.x`) as num;
/*
┌─s─────┬─nest.x─┬─nest.y─┬─num─┐
│ Hello │      1 │     10 │   1 │
│ Hello │      2 │     20 │   2 │
│ World │      3 │     30 │   1 │
│ World │      4 │     40 │   2 │
│ World │      5 │     50 │   3 │
└───────┴────────┴────────┴─────┘
*/

4. DISTINCT子句

4.1 语法

SELECT DISTINCT [ON (column1, column2,...)] | [*] FROM ...

4.2 数据准备

create table test.t_distinct(
    a UInt8,
    b UInt8,
    c UInt8
) ENGINE = Memory;

insert into test.t_distinct
values (1, 1, 1), (1, 1, 1), (2, 2, 2), (2, 2, 2), (1, 1, 2), (1, 2, 2);

select * from test.t_distinct;
/*
┌─a─┬─b─┬─c─┐
│ 1 │ 1 │ 1 │
│ 1 │ 1 │ 1 │
│ 2 │ 2 │ 2 │
│ 2 │ 2 │ 2 │
│ 1 │ 1 │ 2 │
│ 1 │ 2 │ 2 │
└───┴───┴───┘
*/

4.3 对所有列去重

SELECT DISTINCT * FROM test.t_distinct;
/*
┌─a─┬─b─┬─c─┐
│ 1 │ 1 │ 1 │
│ 2 │ 2 │ 2 │
│ 1 │ 1 │ 2 │
│ 1 │ 2 │ 2 │
└───┴───┴───┘
*/

4.4 对部分列去重

可以使用DISTINCT ON (a,b)来对部分列进行去重。

本例子执行报错了,可以去官网执行第二条语句 https://play.clickhouse.com/

--执行报错,不知道是不是版本的原因,结果供参考
SELECT DISTINCT ON (a,b) * FROM test.t_distinct;
/*
┌─a─┬─b─┬─c─┐
│ 1 │ 1 │ 1 │
│ 2 │ 2 │ 2 │
│ 1 │ 2 │ 2 │
└───┴───┴───┘
*/
-- 官方网站可以执行
SELECT DISTINCT ON (RegionID, UserID) * FROM hits_v1;

4.5 DISTINCT 结合 ORDER BY

官网介绍,distinct可以与order by结合使用,且二者可以为不同的列,顺序是先执行distinct,然后执行order by。

需要注意的是,假设我们在源表操作仅对a列去重,结果应为:

┌─a─┬─b─┬─c─┐
│ 1 │ 1 │ 1 │
│ 2 │ 2 │ 2 │
└───┴───┴───┘

如果我们对a执行distinct,然后按b进行排序,则先得到上面的结果,然后进行排序,结果如下:

SELECT DISTINCT a FROM test.t_distinct ORDER BY b ASC;
/*
┌─a─┐
│ 1 │
│ 2 │
└───┘
*/

5. EXCEPT子句

except子句获取两个查询的差集,即将第二个查询语句结果从第一个查询中去掉,必须保证列、顺序和类型都一致。

5.1 语法

SELECT column1 [, column2 ]
FROM table1
[WHERE condition]

EXCEPT

SELECT column1 [, column2 ]
FROM table2
[WHERE condition]

5.2 案例

SELECT number FROM numbers(1,10) 
EXCEPT 
SELECT number FROM numbers(3,6);
/*
┌─number─┐
│      1 │
│      2 │
│      9 │
│     10 │
└────────┘
*/

6. FORMAT子句

如果 FORMAT 被省略则使用默认格式,这取决于用于访问ClickHouse服务器的设置和接口。 为 HTTP接口 和 命令行客户端 在批处理模式下,默认格式为 TabSeparated. 对于交互模式下的命令行客户端,默认格式为 PrettyCompact (它生成紧凑的人类可读表)

具体的格式参考:https://www.bookstack.cn/read/clickhouse-21.2-zh/75cf7dc2071b0ed5.md#9w63zl

6.1 案例

本例中介绍常用的几种在命令行显示的格式。

6.1.1 Pretty

将数据以表格形式输出,也可以使用ANSI转义字符在终端中设置颜色。
它会绘制一个完整的表格,每行数据在终端中占用两行。

SELECT number FROM numbers(1,3)
format Pretty;
/*
┏━━━━━━━━┓
┃ number ┃
┡━━━━━━━━┩
│      1 │
├────────┤
│      2 │
├────────┤
│      3 │
└────────┘
*/

6.1.2 PrettyCompact

与Pretty格式不一样的是PrettyCompact去掉了行之间的表格分割线,这样使得结果更加紧凑。
这种格式会在交互命令行客户端下默认使用

SELECT number FROM numbers(1,3)
format  PrettyCompact;
/*
┌─number─┐
│      1 │
│      2 │
│      3 │
└────────┘
*/

6.1.3 PrettyCompactMonoBlock

与PrettyCompact格式不一样的是,它支持10,000行数据缓冲,然后输出在一个表格中,不会按照块来区分。

通常用来将命令行显示中不同分区数据合到一起显示。

SELECT number FROM numbers(1,3)
format  PrettyCompactMonoBlock;
/*
┌─number─┐
│      1 │
│      2 │
│      3 │
└────────┘
*/

6.1.4 PrettySpace

与PrettyCompact格式不一样的是,它使用空格来代替网格来显示数据。

SELECT number FROM numbers(1,3)
format PrettySpace;
/*
-- 显示结果
number

      1
      2
      3
*/

7. GROUP BY子句

聚合是面向列的 DBMS 最重要的功能之一,因此它的实现是ClickHouse中最优化的部分之一。 默认情况下,聚合使用哈希表在内存中完成。 它有 40+ 的特殊化自动选择取决于 “grouping key” 数据类型。

ClickHouse解释 NULL 作为一个值,并且 NULL==NULL。

7.1 案例

假设我们的数据表如下:

create table test.t_groupby(
	year UInt16,
    month UInt8,
    day UInt8
) ENGINE = Memory;

insert into test.t_groupby
values (2019, 1, 5), (2019, 1, 15), (2020, 1, 5), (2020, 1, 15), (2020, 10, 5), (2020, 10, 15);

select * from test.t_groupby;
/*
┌─year─┬─month─┬─day─┐
│ 2019 │     1 │   5 │
│ 2019 │     1 │  15 │
│ 2020 │     1 │   5 │
│ 2020 │     1 │  15 │
│ 2020 │    10 │   5 │
│ 2020 │    10 │  15 │
└──────┴───────┴─────┘
*/

7.1.1 ROLLUP修饰符

rollup修饰符将使得group by分组中的year, month, day进行向上的不同组合,组合情况为:

GROUP BY year, month, day;
GROUP BY year, month (and day column is filled with zeros);
GROUP BY year (now month, day columns are both filled with zeros);
and totals (and all three key expression columns are zeros).

执行结果如下所示,默认用0代替未参与分组。

SELECT year, month, day, count(*) FROM test.t_groupby GROUP BY year, month, day WITH ROLLUP;

/*
┌─year─┬─month─┬─day─┬─count()─┐
│ 2020 │    10 │  15 │       1 │
│ 2020 │     1 │   5 │       1 │
│ 2019 │     1 │   5 │       1 │
│ 2020 │     1 │  15 │       1 │
│ 2019 │     1 │  15 │       1 │
│ 2020 │    10 │   5 │       1 │
└──────┴───────┴─────┴─────────┘
┌─year─┬─month─┬─day─┬─count()─┐
│ 2019 │     1 │   0 │       2 │
│ 2020 │     1 │   0 │       2 │
│ 2020 │    10 │   0 │       2 │
└──────┴───────┴─────┴─────────┘
┌─year─┬─month─┬─day─┬─count()─┐
│ 2019 │     0 │   0 │       2 │
│ 2020 │     0 │   0 │       4 │
└──────┴───────┴─────┴─────────┘
┌─year─┬─month─┬─day─┬─count()─┐
│    0 │     0 │   0 │       6 │
└──────┴───────┴─────┴─────────┘
*/

7.1.2 CUBE修饰符

CUBE修饰符将对group by中的列进行各个维度的组合,组合情况如下:

GROUP BY year, month, day
GROUP BY year, month
GROUP BY year, day
GROUP BY year
GROUP BY month, day
GROUP BY month
GROUP BY day
and totals

执行结果如下:

SELECT year, month, day, count(*) FROM test.t_groupby GROUP BY year, month, day WITH CUBE;
/*
┌─year─┬─month─┬─day─┬─count()─┐
│ 2020 │    10 │  15 │       1 │
│ 2020 │     1 │   5 │       1 │
│ 2019 │     1 │   5 │       1 │
│ 2020 │     1 │  15 │       1 │
│ 2019 │     1 │  15 │       1 │
│ 2020 │    10 │   5 │       1 │
└──────┴───────┴─────┴─────────┘
┌─year─┬─month─┬─day─┬─count()─┐
│ 2019 │     1 │   0 │       2 │
│ 2020 │     1 │   0 │       2 │
│ 2020 │    10 │   0 │       2 │
└──────┴───────┴─────┴─────────┘
┌─year─┬─month─┬─day─┬─count()─┐
│ 2020 │     0 │   5 │       2 │
│ 2019 │     0 │   5 │       1 │
│ 2020 │     0 │  15 │       2 │
│ 2019 │     0 │  15 │       1 │
└──────┴───────┴─────┴─────────┘
┌─year─┬─month─┬─day─┬─count()─┐
│ 2019 │     0 │   0 │       2 │
│ 2020 │     0 │   0 │       4 │
└──────┴───────┴─────┴─────────┘
┌─year─┬─month─┬─day─┬─count()─┐
│    0 │     1 │   5 │       2 │
│    0 │    10 │  15 │       1 │
│    0 │    10 │   5 │       1 │
│    0 │     1 │  15 │       2 │
└──────┴───────┴─────┴─────────┘
┌─year─┬─month─┬─day─┬─count()─┐
│    0 │     1 │   0 │       4 │
│    0 │    10 │   0 │       2 │
└──────┴───────┴─────┴─────────┘
┌─year─┬─month─┬─day─┬─count()─┐
│    0 │     0 │   5 │       3 │
│    0 │     0 │  15 │       3 │
└──────┴───────┴─────┴─────────┘
┌─year─┬─month─┬─day─┬─count()─┐
│    0 │     0 │   0 │       6 │
└──────┴───────┴─────┴─────────┘
*/

7.1.3 TOTALS修饰符

TOTALS修饰符在常规的聚合之外,增加一个按照所有列分组的总聚合。

SELECT year, month, day, count(*) FROM test.t_groupby GROUP BY year, month, day WITH TOTALS;
/*
┌─year─┬─month─┬─day─┬─count()─┐
│ 2020 │    10 │  15 │       1 │
│ 2020 │     1 │   5 │       1 │
│ 2019 │     1 │   5 │       1 │
│ 2020 │     1 │  15 │       1 │
│ 2019 │     1 │  15 │       1 │
│ 2020 │    10 │   5 │       1 │
└──────┴───────┴─────┴─────────┘

Totals:
┌─year─┬─month─┬─day─┬─count()─┐
│    0 │     0 │   0 │       6 │
└──────┴───────┴─────┴─────────┘
*/

8. INTERSECT子句

INTERSECT子句获取两个查询的交集。

8.1 语法

SELECT column1 [, column2 ]
FROM table1
[WHERE condition]

INTERSECT

SELECT column1 [, column2 ]
FROM table2
[WHERE condition]

8.2 案例

SELECT number FROM numbers(1,10) INTERSECT SELECT number FROM numbers(3,6);
/*
┌─number─┐
│      3 │
│      4 │
│      5 │
│      6 │
│      7 │
│      8 │
└────────┘
*/

9. INTO OUTFILE 子句

输出重定向到客户端上的指定文件。

SELECT <expr_list> INTO OUTFILE file_name [COMPRESSION type]

9.1 案例

clickhouse-client --query="SELECT 1,'ABC' INTO OUTFILE 'select.gz' FORMAT CSV;"
zcat select.gz 

--1,"ABC"

10. JOIN子句

参考:https://cloud.tencent.com/developer/article/1831229

10.1 语法

SELECT <expr_list>
FROM <left_table>
[GLOBAL] [INNER|LEFT|RIGHT|FULL|CROSS] [OUTER|SEMI|ANTI|ANY|ASOF] JOIN <right_table>
(ON <expr_list>)|(USING <column_list>) ...

支持的join方式如下:

join方式含义
INNER JOIN只返回匹配的行
LEFT OUTER JOIN除了匹配的行之外,还返回左表中的非匹配行
RIGHT OUTER JOIN除了匹配的行之外,还返回右表中的非匹配行
FULL OUTER JOIN除了匹配的行之外,还会返回两个表中的非匹配行
CROSS JOIN产生整个表的笛卡尔积,
LEFT SEMI JOIN / RIGHT SEMI JOIN白名单 “join keys”,而不产生笛卡尔积
LEFT ANTI JOIN / RIGHT ANTI JOIN黑名单 “join keys”,而不产生笛卡尔积
LEFT ANY JOIN / RIGHT ANY JOIN / INNER ANY JOIN部分(left/right)或完全(inner/full)禁用标准"JOIN"类型的笛卡尔积
ASOF JOIN / LEFT ASOF JOIN非完全匹配

10.2 案例

10.2.1 数据准备

create table test.t_join_1(
	id UInt8,
    name String
) ENGINE = Memory;

insert into test.t_join_1
values (1, 'A'), (2, 'B'), (3, 'C'), (4, 'D');

create table test.t_join_2(
	id UInt8,
    subject String,
    scores UInt8
) ENGINE = Memory;

insert into test.t_join_2
values (1, 'Chinese', 80), (1, 'Math', 90), (2, 'Chinese',90), (3, 'Chinese', 100), (5, 'Math', 100);

/*
--两表的数据
┌─id─┬─name─┐  ┌─id─┬─subject─┬─scores─┐
│  1 │ A    │  │  1 │ Chinese │     80 │
│  2 │ B    │  │  1 │ Math    │     90 │
│  3 │ C    │  │  2 │ Chinese │     90 │
│  4 │ D    │  │  3 │ Chinese │    100 │
└────┴──────┘  │  5 │ Math    │    100 │
               └────┴─────────┴────────┘
*/

10.2.2 INNER JOIN

inner join通常可以简写为join。

select * from test.t_join_1 t1
inner join test.t_join_2 t2
on t1.id = t2.id;
/*
┌─id─┬─name─┬─t2.id─┬─subject─┬─scores─┐
│  1 │ A    │     1 │ Chinese │     80 │
│  1 │ A    │     1 │ Math    │     90 │
│  2 │ B    │     2 │ Chinese │     90 │
│  3 │ C    │     3 │ Chinese │    100 │
└────┴──────┴───────┴─────────┴────────┘
*/

10.2.3 LEFT OUTER JOIN

left outer join可以省略为left join。

左表中未匹配到的行会显示出来,右表与之对应的行用默认值填充(值用0填充,字符串用空串填充)。

select * from test.t_join_1 t1
left outer join test.t_join_2 t2
on t1.id = t2.id;
/*
┌─id─┬─name─┬─t2.id─┬─subject─┬─scores─┐
│  1 │ A    │     1 │ Chinese │     80 │
│  1 │ A    │     1 │ Math    │     90 │
│  2 │ B    │     2 │ Chinese │     90 │
│  3 │ C    │     3 │ Chinese │    100 │
│  4 │ D    │     0 │         │      0 │
└────┴──────┴───────┴─────────┴────────┘
*/

10.2.4 RIGHT OUTER JOIN

right outer join可以简写未right join。

右表中未匹配到的行会显示出来,左表中对应位置用默认值填充(值用0填充,字符串用空串填充)。

select * from test.t_join_1 t1
right outer join test.t_join_2 t2
on t1.id = t2.id;
/*
┌─id─┬─name─┬─t2.id─┬─subject─┬─scores─┐
│  1 │ A    │     1 │ Chinese │     80 │
│  1 │ A    │     1 │ Math    │     90 │
│  2 │ B    │     2 │ Chinese │     90 │
│  3 │ C    │     3 │ Chinese │    100 │
└────┴──────┴───────┴─────────┴────────┘
┌─id─┬─name─┬─t2.id─┬─subject─┬─scores─┐
│  0 │      │     5 │ Math    │    100 │
└────┴──────┴───────┴─────────┴────────┘
*/

10.2.5 FULL OUTER JOIN

full outer join可以简写为full join。

两表中未匹配的行都会显示出来,对应位置用默认值填充(值用0填充,字符串用空串填充)。

select * from test.t_join_1 t1
full outer join test.t_join_2 t2
on t1.id = t2.id
format PrettyCompactMonoBlock;  --使数据合并显示
/*
┌─id─┬─name─┬─t2.id─┬─subject─┬─scores─┐
│  1 │ A    │     1 │ Chinese │     80 │
│  1 │ A    │     1 │ Math    │     90 │
│  2 │ B    │     2 │ Chinese │     90 │
│  3 │ C    │     3 │ Chinese │    100 │
│  4 │ D    │     0 │         │      0 │
│  0 │      │     5 │ Math    │    100 │
└────┴──────┴───────┴─────────┴────────┘
*/

10.2.6 CROSS JOIN

cross join产生整个表的笛卡尔积,右表中的每一行都会与左表中的每一行进行连接,最终得到交叉连接的结果,数据行数是两个表行数相乘的结果。

select * from test.t_join_1 t1
cross join test.t_join_2 t2;
/*
┌─id─┬─name─┬─t2.id─┬─subject─┬─scores─┐
│  1 │ A    │     1 │ Chinese │     80 │
│  1 │ A    │     1 │ Math    │     90 │
│  1 │ A    │     2 │ Chinese │     90 │
│  1 │ A    │     3 │ Chinese │    100 │
│  1 │ A    │     5 │ Math    │    100 │
│  2 │ B    │     1 │ Chinese │     80 │
│  2 │ B    │     1 │ Math    │     90 │
│  2 │ B    │     2 │ Chinese │     90 │
│  2 │ B    │     3 │ Chinese │    100 │
│  2 │ B    │     5 │ Math    │    100 │
│  3 │ C    │     1 │ Chinese │     80 │
│  3 │ C    │     1 │ Math    │     90 │
│  3 │ C    │     2 │ Chinese │     90 │
│  3 │ C    │     3 │ Chinese │    100 │
│  3 │ C    │     5 │ Math    │    100 │
│  4 │ D    │     1 │ Chinese │     80 │
│  4 │ D    │     1 │ Math    │     90 │
│  4 │ D    │     2 │ Chinese │     90 │
│  4 │ D    │     3 │ Chinese │    100 │
│  4 │ D    │     5 │ Math    │    100 │
└────┴──────┴───────┴─────────┴────────┘
*/

11. LIMIT子句

LIMIT m 选择结果中起始的 m 行。

LIMIT n, m 跳过结果的前n行,然后选择m行数据。与LIMIT m OFFSET n含义一致。

SELECT * FROM (
    SELECT number%50 AS n FROM numbers(100)
) ORDER BY n LIMIT 1,5;
/*
┌─n─┐
│ 0 │
│ 1 │
│ 1 │
│ 2 │
│ 2 │
└───┘
*/

LIMIT n, m WITH TIES 跳过结果的前n行,然后选择m行数据,如果后续还有与第m+n行数据相同的行也会返回。看下面的例子,虽然只限制了返回5行,但是因为后续还有3行与第5行数值相同,因此也被返回。

SELECT * FROM (
    SELECT number%25 AS n FROM numbers(100)
) ORDER BY n LIMIT 0,5 WITH TIES;
/*
┌─n─┐
│ 0 │
│ 0 │
│ 0 │
│ 0 │
│ 1 │
│ 1 │
│ 1 │
│ 1 │
└───┘
*/

12. LIMIT BY子句

12.1 语法

LIMIT [offset_value, ]n BY expressions
LIMIT n OFFSET offset_value BY expressions

通常与order by 子句结合使用。例如 ORDER BY id, val LIMIT 2 BY id,相当于按照id列排序后分组,然后每个组获取前2行数据。

如果指定了offset_value,则会先跳过offset_value行。例如 LIMIT 1, 2 BY id,会先跳过每组的第一行。

12.2 案例

12.2.1 数据准备

CREATE TABLE test.t_limit_by(id Int, val Int) ENGINE = Memory;

INSERT INTO test.t_limit_by 
VALUES (1, 10), (1, 11), (1, 12), (1, 13), (2, 20), (2, 21), (2, 22), (2, 23), (3, 30), (3, 31);

select * from test.t_limit_by;
/*
┌─id─┬─val─┐
│  1 │  10 │
│  1 │  11 │
│  1 │  12 │
│  1 │  13 │
│  2 │  20 │
│  2 │  21 │
│  2 │  22 │
│  2 │  23 │
│  3 │  30 │
│  3 │  31 │
└────┴─────┘
*/

12.2.2 不带offset_value

依据id排序后,每个不同的id值只取前2行。有点类似按照id开窗分组,然后对val排序取前两行。

SELECT * FROM test.t_limit_by ORDER BY id, val LIMIT 2 BY id;
/*
┌─id─┬─val─┐
│  1 │  10 │
│  1 │  11 │
│  2 │  20 │
│  2 │  21 │
│  3 │  30 │
│  3 │  31 │
└────┴─────┘
*/

12.2.3 带offset_value

带了offset_value之后,先按照id和val排序,然后把每组id中的第一行去掉,之后再选取前2行。

SELECT * FROM test.t_limit_by ORDER BY id, val LIMIT 1, 2 BY id;
/*
┌─id─┬─val─┐
│  1 │  11 │
│  1 │  12 │
│  2 │  21 │
│  2 │  22 │
│  3 │  31 │
└────┴─────┘
*/

13. OFFSET FETCH子句

13.1 语法

OFFSET offset_row_count {ROW | ROWS}] [FETCH {FIRST | NEXT} fetch_row_count {ROW | ROWS} {ONLY | WITH TIES}]

offset_row_count或``fetch_row_count值可以是数字或文本常量,默认值等于 1。 OFFSET 指定在开始从查询结果集中返回行之前要跳过的行数。FETCH 指定结果中包含的最大行数。ONLY选项用于返回紧跟OFFSET` 省略的行之后的行。

WITH TIES选项用于根据 ORDER BY 子句返回并列结果集中最后一个位置的任何其他行。例如,如果fetch_row_count设置为 5,但另外两行与第五行中的 ORDER BY 列的值匹配,则结果集将包含七行。

13.2 案例

13.2.1 数据准备

CREATE TABLE test.t_offset(a Int, b Int) ENGINE = Memory;

INSERT INTO test.t_offset 
VALUES (1, 1), (2, 1), (3, 4), (1, 3), (5, 4), (0, 6), (5, 7);

select * from test.t_offset order by a;
/*
┌─a─┬─b─┐
│ 0 │ 6 │
│ 1 │ 1 │
│ 1 │ 3 │
│ 2 │ 1 │
│ 3 │ 4 │
│ 5 │ 4 │
│ 5 │ 7 │
└───┴───┘
*/

13.2.2 ONLY的使用

本例中排序后先跳过结果中的前3行,然后取接下来的3行。与LIMIT 3 OFFSET 3的作用相同。

SELECT * FROM test.t_offset ORDER BY a OFFSET 3 ROW FETCH FIRST 3 ROWS ONLY;
/*
┌─a─┬─b─┐
│ 2 │ 1 │
│ 3 │ 4 │
│ 5 │ 7 │
└───┴───┘
*/
SELECT * FROM test.t_offset ORDER BY a LIMIT 3 OFFSET 3;
/*
┌─a─┬─b─┐
│ 2 │ 1 │
│ 3 │ 4 │
│ 5 │ 7 │
└───┴───┘
*/

13.2.3 WITH TIES的使用

WITH TIES用于保留与结果集中最后一行的order by字段的值相同的行。

SELECT * FROM test.t_offset ORDER BY a OFFSET 3 ROW FETCH FIRST 3 ROWS WITH TIES;
/*
┌─a─┬─b─┐
│ 2 │ 1 │
│ 3 │ 4 │
│ 5 │ 4 │
│ 5 │ 7 │
└───┴───┘
*/

14. ORDER BY子句

14.1 数据准备

CREATE TABLE test.t_orderby(x Int, s String) ENGINE = Memory;

INSERT INTO test.t_orderby 
VALUES (1, 'bca'), (2, 'ABC'), (3, '123a'), (4, 'abc'), (5, 'BCA');

select * from test.t_orderby order by s;
/*
┌─x─┬─s────┐
│ 3 │ 123a │
│ 2 │ ABC  │
│ 5 │ BCA  │
│ 4 │ abc  │
│ 1 │ bca  │
└───┴──────┘
*/

14.2 指定Collation

按字符串值排序,可以指定排序规则。使用COLLATE来指定,对大小写不敏感。

观察可以发现,不指定COLLATE时,默认是按照字符的ASCII码排序的。而指定了COLLATE 'en’时,按照英语字母表排序,不区分大小写。若指定“tr”,则使用土耳其字母表排序。

SELECT * FROM test.t_orderby ORDER BY s ASC COLLATE 'en';
/*
┌─x─┬─s────┐
│ 3 │ 123a │
│ 4 │ abc  │
│ 2 │ ABC  │
│ 1 │ bca  │
│ 5 │ BCA  │
└───┴──────┘
*/

14.3 WITH FILL修饰器

用于填充指定列的值,用在order by指定的列后,对其进行指定值的填充。

WITH FILL 仅适用于具有数字(所有类型的浮点,小数,整数)或日期/日期时间类型的字段。
当未定义 FROM const_expr 填充顺序时,则使用 ORDER BY 中的最小 expr 字段值。
如果未定义 TO const_expr 填充顺序,则使用 ORDER BY 中的最大expr字段值。
当定义了 STEP const_numeric_expr 时,对于数字类型,const_numeric_expras is 解释为 days 作为日期类型,将 seconds 解释为DateTime类型。
如果省略了 STEP const_numeric_expr,则填充顺序使用 1.0 表示数字类型,1 day表示日期类型,1 second 表示日期时间类型。

ORDER BY expr [WITH FILL] [FROM const_expr] [TO const_expr] [STEP const_numeric_expr], ... exprN [WITH FILL] [FROM expr] [TO expr] [STEP numeric_expr]

假设我们有如下查询:

SELECT n, source FROM (
   SELECT toFloat32(number % 10) AS n, 'original' AS source
   FROM numbers(10) WHERE number % 3 = 1
) ORDER BY n;
/*
┌─n─┬─source───┐
│ 1 │ original │
│ 4 │ original │
│ 7 │ original │
└───┴──────────┘
*/

当我们加上with fill修饰器时,看如下查询。填充列为n,填充值范围为0-5.51,步长为0.5。

SELECT n, source FROM (
   SELECT toFloat32(number % 10) AS n, 'original' AS source
   FROM numbers(10) WHERE number % 3 = 1
) ORDER BY n WITH FILL FROM 0 TO 5.51 STEP 0.5;
/*
┌───n─┬─source───┐
│   0 │          │
│ 0.5 │          │
│   1 │ original │
│ 1.5 │          │
│   2 │          │
│ 2.5 │          │
│   3 │          │
│ 3.5 │          │
│   4 │ original │
│ 4.5 │          │
│   5 │          │
│ 5.5 │          │
│   7 │ original │
└─────┴──────────┘
*/

再来看看如下的对多列进行填充。先对d2列用默认值1天进行填充,字段 d1 没有填充并使用默认值,因为我们没有 d2 值的重复值,并且无法正确计算 d1 的顺序。

--无填充
SELECT
    toDate((number * 10) * 86400) AS d1,
    toDate(number * 86400) AS d2,
    'original' AS source
FROM numbers(10)
WHERE (number % 3) = 1
ORDER BY
    d2 ASC,
    d1 ASC
/*
┌─────────d1─┬─────────d2─┬─source───┐
│ 1970-01-11 │ 1970-01-02 │ original │
│ 1970-02-10 │ 1970-01-05 │ original │
│ 1970-03-12 │ 1970-01-08 │ original │
└────────────┴────────────┴──────────┘
*/
--有填充
SELECT
    toDate((number * 10) * 86400) AS d1,
    toDate(number * 86400) AS d2,
    'original' AS source
FROM numbers(10)
WHERE (number % 3) = 1
ORDER BY
    d2 WITH FILL,
    d1 WITH FILL STEP 5;
/*
┌─────────d1─┬─────────d2─┬─source───┐
│ 1970-01-11 │ 1970-01-02 │ original │
│ 1970-01-01 │ 1970-01-03 │          │
│ 1970-01-01 │ 1970-01-04 │          │
│ 1970-02-10 │ 1970-01-05 │ original │
│ 1970-01-01 │ 1970-01-06 │          │
│ 1970-01-01 │ 1970-01-07 │          │
│ 1970-03-12 │ 1970-01-08 │ original │
└────────────┴────────────┴──────────┘
*/

在下面这个例子中,先对d1列进行填充,之后填充d2列的时候用默认日期填充。

SELECT
    toDate((number * 10) * 86400) AS d1,
    toDate(number * 86400) AS d2,
    'original' AS source
FROM numbers(10)
WHERE (number % 3) = 1
ORDER BY
    d1 WITH FILL STEP 10,
    d2 WITH FILL STEP 1;
/*
┌─────────d1─┬─────────d2─┬─source───┐
│ 1970-01-11 │ 1970-01-02 │ original │
│ 1970-01-16 │ 1970-01-01 │          │
│ 1970-01-21 │ 1970-01-01 │          │
│ 1970-01-26 │ 1970-01-01 │          │
│ 1970-01-31 │ 1970-01-01 │          │
│ 1970-02-05 │ 1970-01-01 │          │
│ 1970-02-10 │ 1970-01-05 │ original │
│ 1970-02-15 │ 1970-01-01 │          │
│ 1970-02-20 │ 1970-01-01 │          │
│ 1970-02-25 │ 1970-01-01 │          │
│ 1970-03-02 │ 1970-01-01 │          │
│ 1970-03-07 │ 1970-01-01 │          │
│ 1970-03-12 │ 1970-01-08 │ original │
└────────────┴────────────┴──────────┘
*/

15. PREWHERE子句

prewhere是更有效地进行过滤的优化。 默认情况下,即使在 PREWHERE 子句未显式指定。 它也会自动移动 WHERE 条件到prewhere阶段。

如果 optimize_move_to_prewhere 设置为0, PREWHERE自动优化 被禁用。PREWHERE 只有支持 *MergeTree 族系列引擎的表。

16. SAMPLE子句

参考链接:https://play.clickhouse.com/

16.1 SAMPLE K

k 从0到1的数字(支持小数和小数表示法),如, SAMPLE 1/2SAMPLE 0.5

下面的例子中,从hits_v1表中对10%的采样数据计算每个ID的次数。 聚合函数的值不会自动修正,因此要获得近似结果,值 count() 手动乘以10。

SELECT
    UserID,
    count() * 10 AS PageViews
FROM hits_v1
SAMPLE 0.1
GROUP BY UserID
ORDER BY PageViews DESC LIMIT 10

16.2 SAMPLE N

查询在至少一个样本上执行 n 行(但不超过这个)。 例如, SAMPLE 10000000 在至少10,000,000行上运行查询。

使用时,你不知道处理数据的相对百分比,所以不知道聚合函数应该乘以的系数。 使用 _sample_factor 可以得到聚合函数应该乘以的系数, _sample_factor 列包含动态计算的相对系数。

计算页面浏览量的例子:

SELECT sum(PageViews * _sample_factor)
FROM visits_v1
SAMPLE 10000;

计算总访问次数:

SELECT sum(_sample_factor)
FROM visits_v1
SAMPLE 10000;

计算平均会话持续时间,不需要乘以系数:

SELECT avg(Duration)
FROM visits_v1
SAMPLE 10000;

16.3 SAMPLE K OFFSET M

km 是从0到1的数字。SAMPLE 1/10是从数据的开始部分直接抽样1/10,而加上offset则是从数据中间部分开始抽样1/10。可以用[------++------]来表示。

SAMPLE 1/10 OFFSET 1/2

17. UNION子句

将具有相同列的查询合并成一个结果。

如果在合并的时候指定 DISTINCT 关键字,则结果会进行去重操作。

SET union_default_mode = 'DISTINCT';
SELECT 1 UNION SELECT 2 UNION SELECT 3 UNION SELECT 2;

SELECT 1 UNION DISTINCT SELECT 2 UNION DISTINCT SELECT 3 UNION DISTINCT SELECT 2;

/*
--两种查询结果相同
┌─1─┐
│ 1 │
└───┘
┌─1─┐
│ 3 │
└───┘
┌─1─┐
│ 2 │
└───┘
*/

如果在合并的时候指定 ALL关键字,则结果会直接进行连接,不会进行去重操作。

SET union_default_mode = 'ALL';
SELECT 1 UNION SELECT 2 UNION SELECT 3 UNION SELECT 2;

SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3 UNION ALL SELECT 2;

/*
--两种查询结果相同
┌─1─┐
│ 1 │
└───┘
┌─1─┐
│ 3 │
└───┘
┌─1─┐
│ 2 │
└───┘
┌─1─┐
│ 2 │
└───┘
*/

18. WHERE子句

如果需要测试一个 NULL 值,请使用 IS NULL and IS NOT NULL 运算符或 isNull 和 isNotNull 函数。否则带有 NULL 的表达式永远不会通过。

CREATE TABLE test.t_null(x Int8, y Nullable(Int8)) ENGINE=MergeTree() ORDER BY x;
INSERT INTO test.t_null VALUES (1, NULL), (2, 3);

SELECT * FROM test.t_null WHERE y IS NULL;
/*
┌─x─┬────y─┐
│ 1 │ ᴺᵁᴸᴸ │
└───┴──────┘
*/
SELECT * FROM test.t_null WHERE y != 0;
/*
┌─x─┬─y─┐
│ 2 │ 3 │
└───┴───┘
*/
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值