跳到主要内容
跳到主要内容

CREATE TABLE

创建一个新表。此查询可以根据用例具有各种语法形式。

默认情况下,表仅在当前服务器上创建。分布式 DDL 查询实现为 ON CLUSTER 子句,该子句单独描述

语法形式

使用显式模式

CREATE TABLE [IF NOT EXISTS] [db.]table_name [ON CLUSTER cluster]
(
name1 [type1] [NULL|NOT NULL] [DEFAULT|MATERIALIZED|EPHEMERAL|ALIAS expr1] [COMMENT 'comment for column'] [compression_codec] [TTL expr1],
name2 [type2] [NULL|NOT NULL] [DEFAULT|MATERIALIZED|EPHEMERAL|ALIAS expr2] [COMMENT 'comment for column'] [compression_codec] [TTL expr2],
...
) ENGINE = engine
[COMMENT 'comment for table']

db 数据库中(如果未设置 db,则在当前数据库中)创建一个名为 table_name 的表,其结构在括号中指定,引擎为 engine。表的结构是列描述、二级索引和约束的列表。如果引擎支持主键,它将作为表引擎的参数指示。

列描述在最简单的情况下是 name type。示例:RegionID UInt32

表达式也可以为默认值定义(见下文)。

如有必要,可以指定主键,带有一个或多个键表达式。

可以为列和表添加注释。

使用类似于其他表的模式

CREATE TABLE [IF NOT EXISTS] [db.]table_name AS [db2.]name2 [ENGINE = engine]

创建一个与另一个表结构相同的表。您可以为该表指定不同的引擎。如果未指定引擎,则将使用与 db2.name2 表相同的引擎。

从另一个表克隆模式和数据

CREATE TABLE [IF NOT EXISTS] [db.]table_name CLONE AS [db2.]name2 [ENGINE = engine]

创建一个与另一个表结构相同的表。您可以为该表指定不同的引擎。如果未指定引擎,则将使用与 db2.name2 表相同的引擎。创建新表后,db2.name2 中的所有分区都将附加到该表。换句话说,db2.name2 的数据在创建时被克隆到 db.table_name 中。此查询等效于以下内容

CREATE TABLE [IF NOT EXISTS] [db.]table_name AS [db2.]name2 [ENGINE = engine];
ALTER TABLE [db.]table_name ATTACH PARTITION ALL FROM [db2].name2;

从表函数

CREATE TABLE [IF NOT EXISTS] [db.]table_name AS table_function()

创建一个表,其结果与指定的表函数的结果相同。创建的表也将以与指定的相应表函数相同的方式工作。

从 SELECT 查询

CREATE TABLE [IF NOT EXISTS] [db.]table_name[(name1 [type1], name2 [type2], ...)] ENGINE = engine AS SELECT ...

创建一个结构类似于 SELECT 查询结果的表,使用 engine 引擎,并使用来自 SELECT 的数据填充它。您还可以显式指定列描述。

如果表已存在并且指定了 IF NOT EXISTS,则查询不会执行任何操作。

在查询中的 ENGINE 子句之后可以有其他子句。有关如何在表引擎的描述中创建表的详细文档,请参阅。

提示

在 ClickHouse Cloud 中,请将其拆分为两个步骤

  1. 创建表结构
CREATE TABLE t1
ENGINE = MergeTree
ORDER BY ...
# highlight-next-line
EMPTY AS
SELECT ...
  1. 填充表
INSERT INTO t1
SELECT ...

示例

查询

CREATE TABLE t1 (x String) ENGINE = Memory AS SELECT 1;
SELECT x, toTypeName(x) FROM t1;

结果

┌─x─┬─toTypeName(x)─┐
│ 1 │ String │
└───┴───────────────┘

NULL 或 NOT NULL 修饰符

列定义中数据类型之后的 NULLNOT NULL 修饰符允许或不允许其为Nullable

如果类型不是 Nullable 并且指定了 NULL,它将被视为 Nullable;如果指定了 NOT NULL,则不视为 Nullable。例如,INT NULLNullable(INT) 相同。如果类型是 Nullable 并且指定了 NULLNOT NULL 修饰符,则会抛出异常。

另请参阅 data_type_default_nullable 设置。

默认值

列描述可以指定默认值表达式,形式为 DEFAULT exprMATERIALIZED exprALIAS expr。示例:URLDomain String DEFAULT domain(URL)

表达式 expr 是可选的。如果省略,则必须显式指定列类型,默认值对于数字列为 0,对于字符串列为 ''(空字符串),对于数组列为 [](空数组),对于日期列为 1970-01-01,对于可空列为 NULL

默认值列的列类型可以省略,在这种情况下,它从 expr 的类型推断出来。例如,列 EventDate DEFAULT toDate(EventTime) 的类型将为日期。

如果同时指定了数据类型和默认值表达式,则插入一个隐式类型转换函数,该函数将表达式转换为指定的类型。示例:Hits UInt32 DEFAULT 0 在内部表示为 Hits UInt32 DEFAULT toUInt32(0)

默认值表达式 expr 可以引用任意表列和常量。ClickHouse 检查表结构的更改是否会在表达式计算中引入循环。对于 INSERT,它检查表达式是否可解析——是否已传递可以从中计算的所有列。

DEFAULT

DEFAULT expr

正常默认值。如果在 INSERT 查询中未指定此类列的值,则从 expr 计算得出。

示例

CREATE OR REPLACE TABLE test
(
id UInt64,
updated_at DateTime DEFAULT now(),
updated_at_date Date DEFAULT toDate(updated_at)
)
ENGINE = MergeTree
ORDER BY id;

INSERT INTO test (id) Values (1);

SELECT * FROM test;
┌─id─┬──────────updated_at─┬─updated_at_date─┐
12023-02-24 17:06:462023-02-24
└────┴─────────────────────┴─────────────────┘

MATERIALIZED

MATERIALIZED expr

物化表达式。当插入行时,此类列的值会根据指定的物化表达式自动计算。在 INSERT 期间无法显式指定值。

此外,此类型的默认值列不包含在 SELECT * 的结果中。这是为了保留 SELECT * 的结果始终可以使用 INSERT 插回表中的不变性。可以使用设置 asterisk_include_materialized_columns 禁用此行为。

示例

CREATE OR REPLACE TABLE test
(
id UInt64,
updated_at DateTime MATERIALIZED now(),
updated_at_date Date MATERIALIZED toDate(updated_at)
)
ENGINE = MergeTree
ORDER BY id;

INSERT INTO test Values (1);

SELECT * FROM test;
┌─id─┐
1
└────┘

SELECT id, updated_at, updated_at_date FROM test;
┌─id─┬──────────updated_at─┬─updated_at_date─┐
12023-02-24 17:08:082023-02-24
└────┴─────────────────────┴─────────────────┘

SELECT * FROM test SETTINGS asterisk_include_materialized_columns=1;
┌─id─┬──────────updated_at─┬─updated_at_date─┐
12023-02-24 17:08:082023-02-24
└────┴─────────────────────┴─────────────────┘

EPHEMERAL

EPHEMERAL [expr]

临时列。此类型的列不存储在表中,也无法从中进行 SELECT。临时列的唯一目的是从它们构建其他列的默认值表达式。

没有显式指定列的插入将跳过此类型的列。这是为了保留 SELECT * 的结果始终可以使用 INSERT 插回表中的不变性。

示例

CREATE OR REPLACE TABLE test
(
id UInt64,
unhexed String EPHEMERAL,
hexed FixedString(4) DEFAULT unhex(unhexed)
)
ENGINE = MergeTree
ORDER BY id;

INSERT INTO test (id, unhexed) Values (1, '5a90b714');

SELECT
id,
hexed,
hex(hexed)
FROM test
FORMAT Vertical;

Row 1:
──────
id: 1
hexed: Z��
hex(hexed): 5A90B714

ALIAS

ALIAS expr

计算列(同义词)。此类型的列不存储在表中,也无法将值 INSERT 到其中。

当 SELECT 查询显式引用此类型的列时,该值在查询时从 expr 计算得出。默认情况下,SELECT * 排除 ALIAS 列。可以使用设置 asterisk_include_alias_columns 禁用此行为。

当使用 ALTER 查询添加新列时,不会写入这些列的旧数据。相反,当读取没有新列值的旧数据时,默认情况下会动态计算表达式。但是,如果运行表达式需要查询中未指示的不同列,则将额外读取这些列,但仅针对需要它的数据块。

如果您向表中添加新列,但稍后更改其默认表达式,则旧数据使用的值将更改(对于磁盘上未存储值的数据)。请注意,在运行后台合并时,缺少合并部分之一的列的数据将写入合并后的部分。

无法为嵌套数据结构中的元素设置默认值。

CREATE OR REPLACE TABLE test
(
id UInt64,
size_bytes Int64,
size String ALIAS formatReadableSize(size_bytes)
)
ENGINE = MergeTree
ORDER BY id;

INSERT INTO test VALUES (1, 4678899);

SELECT id, size_bytes, size FROM test;
┌─id─┬─size_bytes─┬─size─────┐
146788994.46 MiB │
└────┴────────────┴──────────┘

SELECT * FROM test SETTINGS asterisk_include_alias_columns=1;
┌─id─┬─size_bytes─┬─size─────┐
146788994.46 MiB │
└────┴────────────┴──────────┘

主键

您可以在创建表时定义主键。主键可以通过两种方式指定

  • 在列列表中
CREATE TABLE db.table_name
(
name1 type1, name2 type2, ...,
PRIMARY KEY(expr1[, expr2,...])
)
ENGINE = engine;
  • 在列列表之外
CREATE TABLE db.table_name
(
name1 type1, name2 type2, ...
)
ENGINE = engine
PRIMARY KEY(expr1[, expr2,...]);
提示

您不能在一个查询中组合这两种方式。

约束

除了列描述外,还可以定义约束

CONSTRAINT

CREATE TABLE [IF NOT EXISTS] [db.]table_name [ON CLUSTER cluster]
(
name1 [type1] [DEFAULT|MATERIALIZED|ALIAS expr1] [compression_codec] [TTL expr1],
...
CONSTRAINT constraint_name_1 CHECK boolean_expr_1,
...
) ENGINE = engine

boolean_expr_1 可以是任何布尔表达式。如果为表定义了约束,则将为 INSERT 查询中的每一行检查每个约束。如果任何约束不满足,服务器将引发异常,其中包含约束名称和检查表达式。

添加大量约束可能会对大型 INSERT 查询的性能产生负面影响。

ASSUME

ASSUME 子句用于在表上定义一个 CONSTRAINT,该约束被假定为真。然后,优化器可以使用此约束来提高 SQL 查询的性能。

以下示例在 users_a 表的创建中使用了 ASSUME CONSTRAINT

CREATE TABLE users_a (
uid Int16,
name String,
age Int16,
name_len UInt8 MATERIALIZED length(name),
CONSTRAINT c1 ASSUME length(name) = name_len
)
ENGINE=MergeTree
ORDER BY (name_len, name);

在此处,ASSUME CONSTRAINT 用于断言 length(name) 函数始终等于 name_len 列的值。这意味着,只要在查询中调用 length(name),ClickHouse 就可以将其替换为 name_len,这应该更快,因为它避免了调用 length() 函数。

然后,当执行查询 SELECT name FROM users_a WHERE length(name) < 5; 时,由于 ASSUME CONSTRAINT,ClickHouse 可以将其优化为 SELECT name FROM users_a WHERE name_len < 5;。这可以使查询运行得更快,因为它避免了为每一行计算 name 的长度。

ASSUME CONSTRAINT 不强制执行约束,它只是通知优化器约束成立。如果约束实际上不成立,则查询结果可能不正确。因此,仅当您确定约束为真时才应使用 ASSUME CONSTRAINT

TTL 表达式

定义值的存储时间。只能为 MergeTree 系列表指定。有关详细描述,请参阅 列和表的 TTL

列压缩编解码器

默认情况下,ClickHouse 在自管理版本中应用 lz4 压缩,在 ClickHouse Cloud 中应用 zstd 压缩。

对于 MergeTree 引擎系列,您可以在服务器配置的 压缩 部分中更改默认压缩方法。

您还可以在 CREATE TABLE 查询中为每个单独的列定义压缩方法。

CREATE TABLE codec_example
(
dt Date CODEC(ZSTD),
ts DateTime CODEC(LZ4HC),
float_value Float32 CODEC(NONE),
double_value Float64 CODEC(LZ4HC(9)),
value Float32 CODEC(Delta, ZSTD)
)
ENGINE = <Engine>
...

可以指定 Default 编解码器来引用默认压缩,默认压缩可能取决于运行时的不同设置(和数据属性)。示例:value UInt64 CODEC(Default) — 与缺少编解码器规范相同。

您还可以从列中删除当前 CODEC 并使用 config.xml 中的默认压缩

ALTER TABLE codec_example MODIFY COLUMN float_value CODEC(Default);

编解码器可以在管道中组合,例如 CODEC(Delta, Default)

提示

您无法使用 lz4 等外部实用程序解压缩 ClickHouse 数据库文件。而是使用特殊的 clickhouse-compressor 实用程序。

以下表引擎支持压缩

  • MergeTree 系列。支持列压缩编解码器,并通过 compression 设置选择默认压缩方法。
  • Log 系列。默认使用 lz4 压缩方法,并支持列压缩编解码器。
  • Set。仅支持默认压缩。
  • Join。仅支持默认压缩。

ClickHouse 支持通用编解码器和专用编解码器。

通用编解码器

NONE

NONE — 无压缩。

LZ4

LZ4 — 默认使用的无损 数据压缩算法。应用 LZ4 快速压缩。

LZ4HC

LZ4HC[(level)] — 具有可配置级别的 LZ4 HC(高压缩)算法。默认级别:9。设置 level <= 0 应用默认级别。可能的级别:[1, 12]。建议的级别范围:[4, 9]。

ZSTD

ZSTD[(level)] — 具有可配置 levelZSTD 压缩算法。可能的级别:[1, 22]。默认级别:1。

高压缩级别对于非对称场景很有用,例如压缩一次,重复解压缩。更高的级别意味着更好的压缩和更高的 CPU 使用率。

ZSTD_QAT

ClickHouse Cloud 中不支持

ZSTD_QAT[(level)] — 具有可配置级别的 ZSTD 压缩算法,由 Intel® QATlibIntel® QAT ZSTD Plugin 实现。可能的级别:[1, 12]。默认级别:1。建议的级别范围:[6, 12]。适用一些限制

  • ZSTD_QAT 默认情况下禁用,只能在启用配置设置 enable_zstd_qat_codec 后才能使用。
  • 对于压缩,ZSTD_QAT 尝试使用 Intel® QAT 卸载设备(QuickAssist Technology)。如果未找到此类设备,它将回退到软件中的 ZSTD 压缩。
  • 解压缩始终在软件中执行。

专用编解码器

这些编解码器旨在通过利用数据的特定功能来提高压缩效率。其中一些编解码器本身不压缩数据,而是预处理数据,以便使用通用编解码器的第二压缩阶段可以实现更高的数据压缩率。

Delta

Delta(delta_bytes) — 压缩方法,其中原始值被两个相邻值的差值替换,第一个值保持不变。最多 delta_bytes 用于存储增量值,因此 delta_bytes 是原始值的最大大小。可能的 delta_bytes 值:1、2、4、8。如果 delta_bytes 等于 1、2、4 或 8,则默认值为 sizeof(type)。在所有其他情况下,它为 1。Delta 是一种数据准备编解码器,即不能单独使用。

DoubleDelta

DoubleDelta(bytes_size) — 计算增量的增量,并以紧凑的二进制形式写入。可能的 bytes_size 值:1、2、4、8,如果 bytes_size 等于 1、2、4 或 8,则默认值为 sizeof(type)。在所有其他情况下,它为 1。对于具有恒定步幅的单调序列(例如时间序列数据)可实现最佳压缩率。可以与任何固定宽度类型一起使用。实现 Gorilla TSDB 中使用的算法,并将其扩展为支持 64 位类型。对于 32 位增量使用 1 个额外位:5 位前缀而不是 4 位前缀。有关更多信息,请参阅 Gorilla: A Fast, Scalable, In-Memory Time Series Database 中的压缩时间戳。DoubleDelta 是一种数据准备编解码器,即不能单独使用。

GCD

GCD() - - 计算列中值的最大公约数 (GCD),然后将每个值除以 GCD。可以与整数、十进制和日期/时间列一起使用。该编解码器非常适合值以 GCD 的倍数变化(增加或减少)的列,例如 24、28、16、24、8、24 (GCD = 4)。GCD 是一种数据准备编解码器,即不能单独使用。

Gorilla

Gorilla(bytes_size) — 计算当前浮点值和上一个浮点值之间的 XOR,并以紧凑的二进制形式写入。连续值之间的差异越小,即序列值的变化越慢,压缩率越好。实现 Gorilla TSDB 中使用的算法,并将其扩展为支持 64 位类型。可能的 bytes_size 值:1、2、4、8,如果 bytes_size 等于 1、2、4 或 8,则默认值为 sizeof(type)。在所有其他情况下,它为 1。有关更多信息,请参阅 Gorilla: A Fast, Scalable, In-Memory Time Series Database 中的第 4.1 节。

FPC

FPC(level, float_size) - 使用两个预测器中较好的一个重复预测序列中的下一个浮点数值,然后将实际值与预测值进行异或运算,并对结果进行前导零压缩。与 Gorilla 类似,这在存储一系列缓慢变化的浮点数值时非常有效。对于 64 位值(double),FPC 比 Gorilla 更快;对于 32 位值,效果可能因情况而异。可能的 level 值:1-28,默认值为 12。可能的 float_size 值:4、8,如果类型为 Float,则默认值为 sizeof(type)。在所有其他情况下,默认值为 4。有关该算法的详细描述,请参阅 High Throughput Compression of Double-Precision Floating-Point Data

T64

T64 — 一种压缩方法,用于裁剪整数数据类型(包括 Enum、Date 和 DateTime)中未使用的最高位。在其算法的每个步骤中,编解码器获取 64 个值的块,将它们放入 64x64 位矩阵中,对其进行转置,裁剪掉值中未使用的位,并将剩余部分作为序列返回。未使用的位是指在用于压缩的整个数据部分中,最大值和最小值之间没有差异的位。

DoubleDeltaGorilla 编解码器在 Gorilla TSDB 中用作其压缩算法的组件。当存在一系列缓慢变化的值及其时间戳时,Gorilla 方法非常有效。时间戳通过 DoubleDelta 编解码器有效地压缩,而值通过 Gorilla 编解码器有效地压缩。例如,要获得有效存储的表,您可以使用以下配置创建它

CREATE TABLE codec_example
(
timestamp DateTime CODEC(DoubleDelta),
slow_values Float32 CODEC(Gorilla)
)
ENGINE = MergeTree()

加密编解码器

这些编解码器实际上并不压缩数据,而是加密磁盘上的数据。它们仅在通过 encryption 设置指定加密密钥时可用。请注意,加密仅在编解码器管道的末端才有意义,因为加密数据通常无法以任何有意义的方式压缩。

加密编解码器

AES_128_GCM_SIV

CODEC('AES-128-GCM-SIV') — 使用 AES-128 在 RFC 8452 GCM-SIV 模式下加密数据。

AES-256-GCM-SIV

CODEC('AES-256-GCM-SIV') — 使用 AES-256 在 GCM-SIV 模式下加密数据。

这些编解码器使用固定的 nonce,因此加密是确定性的。这使其与重复数据删除引擎(如 ReplicatedMergeTree)兼容,但存在一个弱点:当同一数据块被加密两次时,生成的密文将完全相同,因此可以读取磁盘的攻击者可以看到这种等价性(尽管只能看到等价性,而无法获取其内容)。

注意

包括 "*MergeTree" 系列在内的大多数引擎在磁盘上创建索引文件时,不会应用编解码器。这意味着如果加密列被索引,则明文将出现在磁盘上。

注意

如果您执行 SELECT 查询,其中提及加密列中的特定值(例如在其 WHERE 子句中),则该值可能会出现在 system.query_log 中。您可能需要禁用日志记录。

示例

CREATE TABLE mytable
(
x String CODEC(AES_128_GCM_SIV)
)
ENGINE = MergeTree ORDER BY x;
注意

如果需要应用压缩,则必须显式指定。否则,只会对数据应用加密。

示例

CREATE TABLE mytable
(
x String Codec(Delta, LZ4, AES_128_GCM_SIV)
)
ENGINE = MergeTree ORDER BY x;

临时表

注意

请注意,临时表不会被复制。因此,无法保证插入到临时表中的数据在其他副本中可用。临时表可以发挥作用的主要用例是在单个会话期间查询或连接小型外部数据集。

ClickHouse 支持具有以下特征的临时表

  • 临时表在会话结束时消失,包括连接丢失的情况。
  • 当未指定引擎时,临时表使用 Memory 表引擎,并且它可以使用除 Replicated 和 KeeperMap 引擎之外的任何表引擎。
  • 临时表不能指定 DB。它是在数据库之外创建的。
  • 无法在所有集群服务器上使用分布式 DDL 查询(通过使用 ON CLUSTER)创建临时表:此表仅在当前会话中存在。
  • 如果临时表与另一个表具有相同的名称,并且查询指定了表名但未指定 DB,则将使用临时表。
  • 对于分布式查询处理,查询中使用的带有 Memory 引擎的临时表将传递到远程服务器。

要创建临时表,请使用以下语法

CREATE TEMPORARY TABLE [IF NOT EXISTS] table_name
(
name1 [type1] [DEFAULT|MATERIALIZED|ALIAS expr1],
name2 [type2] [DEFAULT|MATERIALIZED|ALIAS expr2],
...
) [ENGINE = engine]

在大多数情况下,临时表不是手动创建的,而是在为查询使用外部数据时,或用于分布式 (GLOBAL) IN 时创建的。有关更多信息,请参阅相应的部分

可以使用带有 ENGINE = Memory 的表来代替临时表。

REPLACE TABLE

REPLACE 语句允许您原子性地更新表。

注意

此语句受 AtomicReplicated 数据库引擎支持,它们分别是 ClickHouse 和 ClickHouse Cloud 的默认数据库引擎。

通常,如果您需要从表中删除一些数据,您可以创建一个新表,并使用 SELECT 语句填充它,该语句不检索不需要的数据,然后删除旧表并重命名新表。以下示例演示了这种方法

CREATE TABLE myNewTable AS myOldTable;

INSERT INTO myNewTable
SELECT * FROM myOldTable
WHERE CounterID <12345;

DROP TABLE myOldTable;

RENAME TABLE myNewTable TO myOldTable;

除了上述方法之外,也可以使用 REPLACE(假设您正在使用默认数据库引擎)来实现相同的结果

REPLACE TABLE myOldTable
ENGINE = MergeTree()
ORDER BY CounterID
AS
SELECT * FROM myOldTable
WHERE CounterID <12345;

语法

{CREATE [OR REPLACE] | REPLACE} TABLE [db.]table_name
注意

CREATE 语句的所有语法形式也适用于此语句。对不存在的表调用 REPLACE 将导致错误。

示例:

考虑以下表

CREATE DATABASE base 
ENGINE = Atomic;

CREATE OR REPLACE TABLE base.t1
(
n UInt64,
s String
)
ENGINE = MergeTree
ORDER BY n;

INSERT INTO base.t1 VALUES (1, 'test');

SELECT * FROM base.t1;

┌─n─┬─s────┐
1 │ test │
└───┴──────┘

我们可以使用 REPLACE 语句清除所有数据

CREATE OR REPLACE TABLE base.t1 
(
n UInt64,
s Nullable(String)
)
ENGINE = MergeTree
ORDER BY n;

INSERT INTO base.t1 VALUES (2, null);

SELECT * FROM base.t1;

┌─n─┬─s──┐
2 │ \N │
└───┴────┘

或者我们可以使用 REPLACE 语句更改表结构

REPLACE TABLE base.t1 (n UInt64) 
ENGINE = MergeTree
ORDER BY n;

INSERT INTO base.t1 VALUES (3);

SELECT * FROM base.t1;

┌─n─┐
3
└───┘

COMMENT 子句

您可以在创建表时向其添加注释。

语法

CREATE TABLE db.table_name
(
name1 type1, name2 type2, ...
)
ENGINE = engine
COMMENT 'Comment'

示例

查询

CREATE TABLE t1 (x String) ENGINE = Memory COMMENT 'The temporary table';
SELECT name, comment FROM system.tables WHERE name = 't1';

结果

┌─name─┬─comment─────────────┐
│ t1 │ The temporary table │
└──────┴─────────────────────┘