WITH <expression> AS <identifier>
-- or
WITH <identifier> AS <subquery expression>
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;
WITH sum(bytes) as s
SELECT
formatReadableSize(s),
table
FROM system.parts
GROUP BY table
ORDER BY s
formatReadableSize用来将数据转为可读单位。sum(bytes)不需要用于显示,但需要用来进行排序,提取出来可以使代码更简洁。
/* 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;
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;
SELECT ALL
和 SELECT
不带 DISTINCT
是一样的。
SELECT sum(ALL number) FROM numbers(10);
--等价于
SELECT sum(number) FROM numbers(10);
SELECT <expr_list>
FROM <left_subquery>
[LEFT] ARRAY JOIN <array>
[WHERE|PREWHERE <expr>]
...
准备一个数据表,其中包含一个数组列。
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 │ [] │
└─────────┴─────────┘
*/
常规的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 │
└─────────┴─────┘
*/
在取别名的情况下,数组中的元素可以通过别名访问,但数组本身则通过原始名称访问。
例如在本例中,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 │
└───────┴─────────┴───┘
*/
也可以对外部数组执行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 │
└─────────┴──────────────┘
*/
多个数组可以并列同时执行,用逗号隔开,且数组长度应一致。本例中,将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] │
└───────┴─────────┴───┴─────┴─────────────────────┘
*/
准备一个数据表,包含一个嵌套数据结构(多维数组),嵌套数据结构可以当做是所有列都是相同长度的多列数组,其中每个字段都是一个数组,并且行与行之间的数组长度无须对齐。
参考: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 │ [] │ [] │
└─────────┴─────────┴────────────┘
*/
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 │
└───────┴────────┴────────┘
*/
如果在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] │
└───────┴────────┴────────────┘
*/
如果对嵌套类型数据进行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] │
└───────┴─────┴─────┴─────────┴────────────┘
*/
嵌套类型数据的索引需要通过单个数组列来获取,下面的例子中,通过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 │
└───────┴────────┴────────┴─────┘
*/
SELECT DISTINCT [ON (column1, column2,...)] | [*] FROM ...
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 │
└───┴───┴───┘
*/
SELECT DISTINCT * FROM test.t_distinct;
/*
┌─a─┬─b─┬─c─┐
│ 1 │ 1 │ 1 │
│ 2 │ 2 │ 2 │
│ 1 │ 1 │ 2 │
│ 1 │ 2 │ 2 │
└───┴───┴───┘
*/
可以使用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;
官网介绍,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 │
└───┘
*/
except子句获取两个查询的差集,即将第二个查询语句结果从第一个查询中去掉,必须保证列、顺序和类型都一致。
SELECT column1 [, column2 ]
FROM table1
[WHERE condition]
EXCEPT
SELECT column1 [, column2 ]
FROM table2
[WHERE condition]
SELECT number FROM numbers(1,10)
EXCEPT
SELECT number FROM numbers(3,6);
/*
┌─number─┐
│ 1 │
│ 2 │
│ 9 │
│ 10 │
└────────┘
*/
如果 FORMAT 被省略则使用默认格式,这取决于用于访问ClickHouse服务器的设置和接口。 为 HTTP接口 和 命令行客户端 在批处理模式下,默认格式为 TabSeparated. 对于交互模式下的命令行客户端,默认格式为 PrettyCompact (它生成紧凑的人类可读表)
具体的格式参考:https://www.bookstack.cn/read/clickhouse-21.2-zh/75cf7dc2071b0ed5.md#9w63zl
本例中介绍常用的几种在命令行显示的格式。
将数据以表格形式输出,也可以使用ANSI转义字符在终端中设置颜色。
它会绘制一个完整的表格,每行数据在终端中占用两行。
SELECT number FROM numbers(1,3)
format Pretty;
/*
┏━━━━━━━━┓
┃ number ┃
┡━━━━━━━━┩
│ 1 │
├────────┤
│ 2 │
├────────┤
│ 3 │
└────────┘
*/
与Pretty格式不一样的是PrettyCompact去掉了行之间的表格分割线,这样使得结果更加紧凑。
这种格式会在交互命令行客户端下默认使用。
SELECT number FROM numbers(1,3)
format PrettyCompact;
/*
┌─number─┐
│ 1 │
│ 2 │
│ 3 │
└────────┘
*/
与PrettyCompact格式不一样的是,它支持10,000行数据缓冲,然后输出在一个表格中,不会按照块来区分。
通常用来将命令行显示中不同分区数据合到一起显示。
SELECT number FROM numbers(1,3)
format PrettyCompactMonoBlock;
/*
┌─number─┐
│ 1 │
│ 2 │
│ 3 │
└────────┘
*/
与PrettyCompact格式不一样的是,它使用空格来代替网格来显示数据。
SELECT number FROM numbers(1,3)
format PrettySpace;
/*
-- 显示结果
number
1
2
3
*/
聚合是面向列的 DBMS 最重要的功能之一,因此它的实现是ClickHouse中最优化的部分之一。 默认情况下,聚合使用哈希表在内存中完成。 它有 40+ 的特殊化自动选择取决于 “grouping key” 数据类型。
ClickHouse解释 NULL 作为一个值,并且 NULL==NULL。
假设我们的数据表如下:
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 │
└──────┴───────┴─────┘
*/
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 │
└──────┴───────┴─────┴─────────┘
*/
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 │
└──────┴───────┴─────┴─────────┘
*/
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 │
└──────┴───────┴─────┴─────────┘
*/
INTERSECT子句获取两个查询的交集。
SELECT column1 [, column2 ]
FROM table1
[WHERE condition]
INTERSECT
SELECT column1 [, column2 ]
FROM table2
[WHERE condition]
SELECT number FROM numbers(1,10) INTERSECT SELECT number FROM numbers(3,6);
/*
┌─number─┐
│ 3 │
│ 4 │
│ 5 │
│ 6 │
│ 7 │
│ 8 │
└────────┘
*/
输出重定向到客户端上的指定文件。
SELECT <expr_list> INTO OUTFILE file_name [COMPRESSION type]
clickhouse-client --query="SELECT 1,'ABC' INTO OUTFILE 'select.gz' FORMAT CSV;"
zcat select.gz
--1,"ABC"
参考:https://cloud.tencent.com/developer/article/1831229
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 | 非完全匹配 |
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 │
└────┴─────────┴────────┘
*/
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 │
└────┴──────┴───────┴─────────┴────────┘
*/
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 │
└────┴──────┴───────┴─────────┴────────┘
*/
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 │
└────┴──────┴───────┴─────────┴────────┘
*/
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 │
└────┴──────┴───────┴─────────┴────────┘
*/
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 │
└────┴──────┴───────┴─────────┴────────┘
*/
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 │
└───┘
*/
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,会先跳过每组的第一行。
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 │
└────┴─────┘
*/
依据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 │
└────┴─────┘
*/
带了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 │
└────┴─────┘
*/
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 列的值匹配,则结果集将包含七行。
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 │
└───┴───┘
*/
本例中排序后先跳过结果中的前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 │
└───┴───┘
*/
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 │
└───┴───┘
*/
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 │
└───┴──────┘
*/
按字符串值排序,可以指定排序规则。使用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 │
└───┴──────┘
*/
用于填充指定列的值,用在order by指定的列后,对其进行指定值的填充。
WITH FILL
仅适用于具有数字(所有类型的浮点,小数,整数)或日期/日期时间类型的字段。
当未定义 FROM const_expr
填充顺序时,则使用 ORDER BY
中的最小 expr
字段值。
如果未定义 TO const_expr
填充顺序,则使用 ORDER BY
中的最大expr
字段值。
当定义了 STEP const_numeric_expr
时,对于数字类型,const_numeric_expr
将 as 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 │
└────────────┴────────────┴──────────┘
*/
prewhere是更有效地进行过滤的优化。 默认情况下,即使在 PREWHERE
子句未显式指定。 它也会自动移动 WHERE 条件到prewhere阶段。
如果 optimize_move_to_prewhere
设置为0, PREWHERE
自动优化 被禁用。PREWHERE
只有支持 *MergeTree
族系列引擎的表。
参考链接:https://play.clickhouse.com/
k
从0到1的数字(支持小数和小数表示法),如, SAMPLE 1/2
或 SAMPLE 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
查询在至少一个样本上执行 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;
k
和 m
是从0到1的数字。SAMPLE 1/10是从数据的开始部分直接抽样1/10,而加上offset则是从数据中间部分开始抽样1/10。可以用[------++------]
来表示。
SAMPLE 1/10 OFFSET 1/2
将具有相同列的查询合并成一个结果。
如果在合并的时候指定 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 │
└───┘
*/
如果需要测试一个 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 │
└───┴───┘
*/