语法
系统中存在两种类型的解析器:完整的 SQL 解析器(递归下降解析器)和数据格式解析器(快速流解析器)。除了 INSERT
查询之外,所有情况下都只使用完整的 SQL 解析器。INSERT
查询同时使用两种解析器。
INSERT INTO t VALUES (1, 'Hello, world'), (2, 'abc'), (3, 'def')
INSERT INTO t VALUES
部分由完整解析器解析,数据 (1, 'Hello, world'), (2, 'abc'), (3, 'def')
由快速流解析器解析。您也可以使用 input_format_values_interpret_expressions 设置,将完整解析器用于数据解析。当 input_format_values_interpret_expressions = 1
时,ClickHouse 首先尝试使用快速流解析器解析值。如果失败,ClickHouse 会尝试使用完整解析器解析数据,将其视为 SQL 表达式。
数据可以采用任何格式。收到查询后,服务器会计算请求在内存中不超过 max_query_size 字节(默认情况下为 1 MB),其余部分将通过流解析。这有助于避免大型 INSERT
查询带来的问题。
在 INSERT
查询中使用 Values
格式时,可能看起来数据与 SELECT
查询中的表达式解析方式相同,但事实并非如此。Values
格式更加有限。
本文其余部分介绍完整的解析器。有关格式解析器的更多信息,请参阅 格式 部分。
空格
语法结构之间(包括查询的开头和结尾)可以包含任意数量的空格符号。空格符号包括空格、制表符、换行符、回车符和换页符。
注释
ClickHouse 支持 SQL 风格和 C 风格的注释。
- SQL 风格的注释以
--
、#!
或#
开头,并持续到行尾,--
和#!
后面的空格可以省略。 - C 风格的注释从
/*
到*/
,可以跨越多行,空格也不必。
关键字
当关键字对应时,关键字不区分大小写
- SQL 标准。例如,
SELECT
、select
和SeLeCt
都是有效的。 - 一些流行的 DBMS(MySQL 或 Postgres)中的实现。例如,
DateTime
等同于datetime
。
您可以在 system.data_type_families 表中检查数据类型名称是否区分大小写。
与标准 SQL 不同,所有其他关键字(包括函数名称)都 **区分大小写**。
关键字不是保留字;它们只在相应的上下文中被视为关键字。如果使用与关键字相同的名称的 标识符,请将它们用双引号或反引号括起来。例如,查询 SELECT "FROM" FROM table_name
有效,如果表 table_name
包含名为 "FROM"
的列。
标识符
标识符是
- 集群、数据库、表、分区和列名称。
- 函数。
- 数据类型。
- 表达式别名.
标识符可以是带引号或不带引号的。建议使用不带引号的标识符。
不带引号的标识符必须匹配正则表达式 ^[a-zA-Z_][0-9a-zA-Z_]*$
,并且不能与 关键字 相同。例如:x
、_1
、X_y__Z123_
。
如果您想使用与关键字相同的标识符,或者您想在标识符中使用其他符号,请使用双引号或反引号将其括起来,例如:"id"
、`id`
。
字面量
有数字、字符串、复合和 NULL
字面量。
数字
数字字面量按如下方式解析:
- 首先,使用 strtoull 函数,将其解析为 64 位有符号数。
- 如果解析失败,则使用 strtoll 函数,将其解析为 64 位无符号数。
- 如果解析失败,则使用 strtod 函数,将其解析为浮点数。
- 否则,返回错误。
字面量值将转换为最小的能容纳该值的数据类型。例如,1 将解析为 UInt8
,而 256 将解析为 UInt16
。有关更多信息,请参阅 数据类型。数字字面量中的下划线 _
将被忽略,可以用于提高可读性。
支持以下数字字面量:
整数 – 1
、10_000_000
、18446744073709551615
、01
小数 – 0.1
指数表示法 - 1e100
、-1e-100
浮点数 – 123.456
、inf
、nan
十六进制 – 0xc0fe
SQL 标准兼容十六进制字符串 – x'c0fe'
二进制 – 0b1101
SQL 标准兼容二进制字符串 - b'1101'
不支持八进制字面量,以避免意外的解释错误。
字符串
字符串字面量必须用单引号括起来,不支持双引号。转义可以使用以下两种方式:
- 使用前面的单引号,其中单引号字符
'
(仅此字符)可以转义为''
,或者 - 使用前面的反斜杠以及以下支持的转义序列:
\\
、\'
、\b
、\f
、\r
、\n
、\t
、\0
、\a
、\v
、\xHH
。如果反斜杠位于除上述字符之外的字符之前,则反斜杠将失去其特殊含义,即会被按字面量解释。
在字符串字面量中,您需要使用转义代码 \'
(或:''
)和 \\
转义 '
和 \
。
复合
数组使用方括号 [1, 2, 3]
构造。元组使用圆括号 (1, 'Hello, world!', 2)
构造。从技术上讲,这些不是字面量,而是分别使用数组创建运算符和元组创建运算符的表达式。数组必须至少包含一个元素,元组必须至少包含两个元素。在 SELECT
查询的 IN
子句中出现元组时,有一个单独的情况。查询结果可以包含元组,但元组无法保存到数据库中(除了使用 Memory 引擎的表)。
NULL
表示值缺失。
要将 NULL
存储在表字段中,该字段必须是 Nullable 类型。
根据数据格式(输入或输出),NULL
可能有不同的表示方式。有关更多信息,请参阅 数据格式 文档。
处理 NULL
存在许多细微差别。例如,如果比较运算的至少一个参数为 NULL
,则该运算的结果也为 NULL
。乘法、加法和其他运算也是如此。有关更多信息,请阅读每个运算的文档。
在查询中,可以使用 IS NULL 和 IS NOT NULL 运算符以及相关的 isNull
和 isNotNull
函数检查 NULL
。
Heredoc
heredoc 是一种定义字符串(通常是多行字符串)的方式,同时保留原始格式。heredoc 被定义为自定义字符串字面量,放置在两个 $
符号之间,例如 $heredoc$
。两个 heredoc 之间的值将“按原样”处理。
可以使用 heredoc 嵌入 SQL、HTML 或 XML 代码片段等。
示例
查询
SELECT $smth$SHOW CREATE VIEW my_view$smth$;
结果
┌─'SHOW CREATE VIEW my_view'─┐
│ SHOW CREATE VIEW my_view │
└────────────────────────────┘
定义和使用查询参数
查询参数允许您编写包含抽象占位符而不是具体标识符的通用查询。当执行包含查询参数的查询时,所有占位符将被解析并替换为实际的查询参数值。
定义查询参数有两种方式:
- 使用
SET param_<name>=<value>
命令 - 在命令行上使用
--param_<name>='<value>'
作为clickhouse-client
的参数。<name>
是查询参数的名称,<value>
是其值。
可以使用 {<name>: <datatype>}
在查询中引用查询参数,其中 <name>
是查询参数名称,<datatype>
是要转换为的数据类型。
例如,以下 SQL 定义了名为a
、b
、c
和d
的参数,每个参数都有不同的数据类型。
SET param_a = 13;
SET param_b = 'str';
SET param_c = '2022-08-04 18:30:53';
SET param_d = {'10': [11, 12], '13': [14, 15]};
SELECT
{a: UInt32},
{b: String},
{c: DateTime},
{d: Map(String, Array(UInt8))};
结果
13 str 2022-08-04 18:30:53 {'10':[11,12],'13':[14,15]}
如果您使用的是clickhouse-client
,则参数指定为--param_name=value
。例如,以下参数名为message
,并作为String
检索。
clickhouse-client --param_message='hello' --query="SELECT {message: String}"
结果
hello
如果查询参数表示数据库、表、函数或其他标识符的名称,请使用Identifier
作为其类型。例如,以下查询从名为uk_price_paid
的表中返回行。
SET param_mytablename = "uk_price_paid";
SELECT * FROM {mytablename:Identifier};
查询参数不是可以在任意 SQL 查询中的任意位置使用的通用文本替换。它们主要设计为在SELECT
语句中代替标识符或文字。
函数
函数调用写成一个标识符,后面跟着圆括号中的参数列表(可能为空)。与标准 SQL 相反,即使是空参数列表,括号也是必需的。例如:now()
。有常规函数和聚合函数(请参见聚合函数部分)。一些聚合函数可以在括号中包含两个参数列表。例如:quantile (0.9) (x)
。这些聚合函数称为“参数”函数,第一个列表中的参数称为“参数”。无参数聚合函数的语法与常规函数相同。
运算符
运算符在查询解析期间转换为其对应的函数,同时考虑其优先级和结合性。例如,表达式1 + 2 * 3 + 4
将转换为plus(plus(1, multiply(2, 3)), 4)
。
数据类型和数据库表引擎
CREATE
查询中的数据类型和表引擎的写法与标识符或函数相同。换句话说,它们可能包含或不包含括号中的参数列表。有关更多信息,请参见数据类型、表引擎和CREATE部分。
表达式别名
别名是查询中表达式的用户定义名称。
expr AS alias
AS
— 用于定义别名的关键字。您可以使用或不使用AS
关键字在SELECT
子句中定义表名或列名的别名。例如,
SELECT table_name_alias.column_name FROM table_name table_name_alias
。在CAST 函数中,
AS
关键字有另一个含义。请参见函数说明。expr
— ClickHouse 支持的任何表达式。例如,
SELECT column_name * 2 AS double FROM some_table
。alias
—expr
的名称。别名应符合标识符语法。例如,
SELECT "table t".column_name FROM table_name AS "table t"
。
使用说明
别名对于查询或子查询是全局的,您可以在查询的任何部分为任何表达式定义别名。例如,SELECT (1 AS n) + 2, n
。
别名在子查询和子查询之间不可见。例如,在执行查询SELECT (SELECT sum(b.a) + num FROM b) - a.a AS num FROM a
时,ClickHouse 会生成异常Unknown identifier: num
。
如果为子查询SELECT
子句中的结果列定义了别名,则这些列在外部查询中可见。例如,SELECT n + m FROM (SELECT 1 AS n, 2 AS m)
。
小心与列名或表名相同的别名。让我们考虑以下示例
CREATE TABLE t
(
a Int,
b Int
)
ENGINE = TinyLog()
SELECT
argMax(a, b),
sum(b) AS b
FROM t
Received exception from server (version 18.14.17):
Code: 184. DB::Exception: Received from localhost:9000, 127.0.0.1. DB::Exception: Aggregate function sum(b) is found inside another aggregate function in query.
在此示例中,我们声明了具有列b
的表t
。然后,在选择数据时,我们定义了sum(b) AS b
别名。由于别名是全局的,ClickHouse 将表达式argMax(a, b)
中的字面量b
替换为表达式sum(b)
。这种替换导致了异常。您可以通过将prefer_column_name_to_alias 设置为1
来更改此默认行为。
星号
在SELECT
查询中,星号可以代替表达式。有关更多信息,请参见SELECT部分。
表达式
表达式是函数、标识符、文字、运算符的应用、括号中的表达式、子查询或星号。它还可以包含别名。表达式列表是一个或多个由逗号分隔的表达式。函数和运算符反过来可以将表达式作为参数。