跳至主要内容

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 ...
    EMPTY AS
    SELECT ...
  2. 填充表

    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 并且指定了 NULL,则它将被视为 Nullable;如果指定了 NOT NULL,则不会。例如,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

计算列(同义词)。此类型的列不会存储在表中,并且无法向其中插入值。

当 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,...]);
提示

您不能在一个查询中同时使用这两种方式。

约束

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

约束

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 子句用于在表上定义一个假设为真的 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 引擎系列,您可以在服务器配置的compression 部分更改默认压缩方法。

您还可以在 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 - 无压缩。

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

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 技术)。如果未找到此类设备,它将回退到软件中的 ZSTD 压缩。
  • 解压缩始终在软件中执行。
注意

ClickHouse Cloud 中不提供 ZSTD_QAT。

DEFLATE_QPL

DEFLATE_QPL - 由 Intel® 查询处理库实现的Deflate 压缩算法。存在一些限制

  • DEFLATE_QPL 默认禁用,只有在启用配置设置enable_deflate_qpl_codec 后才能使用。
  • DEFLATE_QPL 需要使用启用 SSE 4.2 指令编译的 ClickHouse 版本(默认情况下,情况就是这样)。有关更多详细信息,请参阅使用 DEFLATE_QPL 构建 Clickhouse
  • 如果系统具有 Intel® IAA(内存中分析加速器)卸载设备,则 DEFLATE_QPL 的效果最佳。有关更多详细信息,请参阅加速器配置使用 DEFLATE_QPL 进行基准测试
  • DEFLATE_QPL 压缩的数据只能在使用启用 SSE 4.2 编译的 ClickHouse 节点之间传输。
注意

ClickHouse Cloud 中不提供 DEFLATE_QPL。

专用编解码器

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

增量

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

双增量

DoubleDelta(bytes_size) - 计算增量的增量并以紧凑的二进制形式写入。可能的 bytes_size 值:1、2、4、8,如果等于 1、2、4 或 8,则默认值为 sizeof(type)。在所有其他情况下,它为 1。对于具有恒定步长的单调序列(例如时间序列数据)可以实现最佳压缩率。可与任何固定宽度类型一起使用。实现了 Gorilla TSDB 中使用的算法,并将其扩展为支持 64 位类型。为 32 位增量使用 1 个额外位:5 位前缀而不是 4 位前缀。有关其他信息,请参阅Gorilla:快速、可扩展的内存中时间序列数据库 中的压缩时间戳。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,如果等于 1、2、4 或 8,则默认值为 sizeof(type)。在所有其他情况下,它为 1。有关其他信息,请参阅Gorilla:快速、可扩展的内存中时间序列数据库 中的第 4.1 节。

FPC

FPC(level, float_size) - 使用两个预测器中较好的一个重复预测序列中的下一个浮点值,然后将实际值与预测值进行异或运算,并对结果进行前导零压缩。类似于 Gorilla,当存储一系列缓慢变化的浮点值时,这种方法非常有效。对于 64 位值(双精度),FPC 比 Gorilla 更快;对于 32 位值,效果可能会有所不同。可能的 level 值:1-28,默认值为 12。可能的 float_size 值:4、8,如果类型为 Float,则默认值为 sizeof(type)。在所有其他情况下,默认为 4。有关算法的详细描述,请参见 双精度浮点数数据的高吞吐量压缩

T64

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

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

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

加密编解码器

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

加密编解码器

AES_128_GCM_SIV

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

AES-256-GCM-SIV

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

这些编解码器使用固定的 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' 查询允许您以原子方式更新表。

注意

此查询仅适用于 Atomic 数据库引擎。

如果需要从表中删除一些数据,您可以创建一个新表并使用不检索不需要数据的 SELECT 语句填充它,然后删除旧表并重命名新表

CREATE TABLE myNewTable AS myOldTable;
INSERT INTO myNewTable SELECT * FROM myOldTable WHERE CounterID <12345;
DROP TABLE myOldTable;
RENAME TABLE myNewTable TO myOldTable;

您可以使用以下方法代替上述方法

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 │
└──────┴─────────────────────┘