这是本节的多页打印视图。 点击此处打印.

返回本页常规视图.

数据加载

Vertica 提供了多种读取数据的方法。您可以从各种源将数据加载到数据库中,也可以选择以各种方式对其进行转换。您可以使用外部表以原始格式就地读取数据。您可以使用流式传输,也可以从其他 Vertica 数据库导入数据。有关介绍,请参阅常见用例

大多数数据加载操作(包括外部表)都围绕 COPY 语句进行,该语句包含许多选项。本文重点介绍基于 COPY 的读取(数据加载和外部表)。受 Vertica 支持的其他数据加载选项将在其他位置进行介绍:

1 - 常见用例

Vertica 支持多种读取数据的用例。此处总结了一些最常见的用例,并提供了用于获取详细信息的链接。这不是完整的功能列表。

COPY 语句是加载数据的核心。有关其使用概述,请参阅 COPY 语句简介

从文件加载数据

您可能需要将数据(或许是大量数据)加载到 Vertica 中。这些文件可能驻留在共享存储上、云中或本地节点上,并且可能采用多种格式。

有关源位置的信息,请参阅指定加载数据的位置。要处理不同格式的数据,请指定解析器;有关选项的详细信息,请参阅数据格式

您无需“按原样”加载数据;也可以在加载期间转换数据。请参阅在加载期间转换数据

从其他服务加载数据

Apache Kafka 是一个流式数据传输平台。Vertica 支持将数据流式传输到 Kafka 以及从 Kafka 流式传输数据。请参阅Apache Kafka 集成

Apache Spark 是一个用于分布式数据的群集计算框架。Vertica 支持连接到 Spark 以获取数据。请参阅Apache Spark 集成

您可以直接从另一个 Vertica 群集复制数据,而不是导出到文件后再加载这些文件。请参阅数据库导出和导入

读取存在的数据(不导入)

您可以使用外部表就地读取数据,而无需将数据加载到 Vertica 中。在以下情况下,使用外部表可能更有优势:

  • 在选择要加载到 Vertica 中的数据之前,如果想浏览诸如数据湖中的数据。

  • 如果您是共享诸如数据湖中的相同数据的多个使用者之一,则就地读取数据可以消除对查询结果是否是最新的顾虑。副本只有一份,因此所有使用者看到的数据都相同。

  • 如果您的数据变化迅速,但您不想将其流式传输到 Vertica,则可以改为自动查询最新更新。

  • 如果 Vertica 中拥有您仍希望能够查询的低优先级数据。

当您查询外部表时,Vertica 会从外部源加载所需的数据。已为此类加载优化 Parquet 和 ORC 列式格式,因此与将数据加载到 Vertica 原生表相比,使用外部表不一定会对性能产生很大影响。

有关使用外部表的详细信息,请参阅使用外部数据

复杂类型

某些数据格式支持复杂类型,例如数组和结构体(属性值对的集合)。您可以使用强类型在原生和外部表中使用 ARRAY 和 ROW 类型(包括嵌套)来定义列。请参阅复杂类型。或者,您可以使用可变(无架构)复杂类型来定义表,而无需完全指定复杂类型。您可以加载 Parquet、ORC、JSON 和 Avro 格式的可变复杂类型;请参阅可变复杂类型

未知或不断演变的架构

有时,您要加载的数据的架构是未知的或随时间而变化,尤其是使用在数据文件中未嵌入架构的 JSON 数据更是如此。可以使用 INFER_TABLE_DDL 函数检查示例数据文件并派生表定义。此函数会为列定义(包括使用复杂类型的列定义)返回良好的起点。对于 JSON 数据,如果数据文件存在变化,该函数可以返回多个候选者。

您可能仍需要进行调整(特别是对于未知类型的分区列),或调整 VARCHAR 值的大小。尤其对于具有多个列多或复杂列的表,使用此功能可以节省时间并减少手动工作量。如果您的数据随时间变化,您可以对新文件或更新后的文件运行该函数,并将建议的定义与现有的定义进行比较。

或者,您可以将无架构数据加载到 Flex 表中。所有数据都会加载到一个二进制列 (VMap) 中,并在查询时进行提取。不同的表行可能含有不同的内容,因此您无需随着数据结构的变化而更新表定义。Flex 表最适合用于初次浏览异构或多结构数据,而不是在生产数据库中浏览,因为查询时提取数据会影响性能。

加载到 Flex 表中通常与加载到任何其他表中一样,但是对于某些格式,您需要使用其他解析器。有关 Flex 表和特定于 Flex 的解析器的详细信息,请参阅 Flex 表

杂乱的数据

有时数据并不干净;值可能与声明的数据类型不匹配,或者可能缺少必需的值,或者解析器可能由于其他原因而无法解释行。您可能仍希望能够加载和浏览这些数据。您可以使用 COPY 语句的参数来指定容错程度以及在何处记录被拒数据的相关信息。有关详细信息,请参阅处理杂乱的数据

2 - COPY 语句简介

使用 COPY 语句可加载数据。 COPY 是一个具有许多参数的大型通用语句;有关所有详细信息,请参阅参考页面。COPY 的最简单加载形式是将数据从源复制到文件,如下所示:

=> COPY target-table FROM data-source

您也可以在定义外部表时使用 COPY:

=> CREATE EXTERNAL TABLE target-table (...) AS COPY FROM data-source

源数据可以是数据流或文件路径。有关 FROM 子句的详细信息,请参阅指定加载数据的位置

您可以指定有关数据加载的许多详细信息,包括:

有关参数的完整列表,请参阅参数

权限

通常,只有超级用户才能使用 COPY 语句来批量加载数据。在某些情况下,非超级用户可以使用 COPY:

  • 从主机上的流(例如 STDIN)而不是文件进行加载(请参阅通过 JDBC 进行流式数据传输)。

  • 使用 FROM LOCAL 选项进行加载。

  • 加载到已授予用户权限的存储位置。

  • 使用用户有权使用的用户定义的加载函数。

非超级用户也可以使用 JDBC 准备的语句执行批量加载,这时系统会将 COPY 用作后台任务来加载数据。

此外,用户还必须对要从中加载数据的源具有读取权限。

3 - 全局选项和特定于列的选项

您可以为整个 COPY 语句全局指定一些 COPY 选项,也可以将它们的范围限制到一列。例如,在以下 COPY 语句中,第一列用 '|' 分隔但其他列用逗号分隔。

=> COPY employees(id DELIMITER '|', name, department) FROM ... DELIMITER ',';

您可以为一列的 null 输入指定其他默认值:

=> COPY employees(id, name, department NULL 'General Admin') FROM ... ;

或者,您可以使用 COLUMN OPTION 参数来指定特定于列的参数,而不是枚举列:

=> COPY employees COLUMN OPTION (department NULL 'General Admin') FROM ... ;

如果为同一参数提供了全局值和特定于列的值,则特定于列的值控制这些列,而全局值控制其他列。

所有参数都可以全局使用。每个参数的描述指明它是否可以限制到特定的列。

4 - 指定加载数据的位置

每个 COPY 语句都需要 FROM 子句来指示正在加载的一个或多个文件的位置,或者在使用用户定义的源时需要 SOURCE 子句。有关 SOURCE 子句的详细信息,请参阅参数。此部分介绍如何使用 FROM 子句。

从特定路径加载

使用 path-to-data 实参来指示要加载的一个或多个文件的位置。可以从以下位置加载数据:

如果路径是 URL,则必须对 '%' 字符使用 URL 编码。否则,可以使用 URL 编码('%NN',其中 NN 是两位十六进制数),但不是必需的。

从本地文件系统复制时,COPY 语句会在参与查询的每个节点上的相同位置查找文件。如果使用的是 NFS,则可以在每个节点上创建 NFS 挂载点。这样做可以使所有数据库节点均参与加载,以提高性能,而无需将文件复制到所有节点。

将 NFS 挂载点视为路径中的本地文件:

=> COPY sales FROM '/mount/sales.dat' ON ANY NODE;

可以在同一 COPY 语句中指定多个路径,如以下示例所示。

=> COPY myTable FROM 'webhdfs:///data/sales/01/*.dat', 'webhdfs:///data/sales/02/*.dat',
    'webhdfs:///data/sales/historical.dat';

对于 HDFS 中的文件,可以指定名称服务 (hadoopNS)。在此示例中,COPY 语句是外部表定义的一部分:

=> CREATE EXTERNAL TABLE users (id INT, name VARCHAR(20))
    AS COPY FROM 'webhdfs://hadoopNS/data/users.csv';

如果 path-to-data 解析到本地文件系统上的存储位置,并且调用 COPY 的用户不是超级用户,则需要以下权限:

  • 存储位置必须使用 USER 选项来创建(参见 CREATE LOCATION)。

  • 用户必须已被授予对文件所在的存储位置的 READ 访问权限,如 GRANT(存储位置) 中所述。

Vertica 可防止符号链接允许未经授权的访问。

使用通配符 (glob) 加载

您可使用单个语句对共享目录中的大量文件调用 COPY,例如:

=> COPY myTable FROM '/data/manyfiles/*.dat' ON ANY NODE;

glob (*) 必须指示一组文件,而不是目录。如果 /data/manyfiles 包含任何子目录,则以下语句将失败:

=> COPY myTable FROM '/data/manyfiles/*' ON ANY NODE;

如果 /data/manyfiles 包含含有文件的子目录,则可以在子目录的路径中使用 glob:

=> COPY myTable FROM '/data/manyfiles/*/*' ON ANY NODE;

有时,目录结构会对数据进行分区,如以下示例所示:

path/created=2016-11-01/*
path/created=2016-11-02/*
path/created=2016-11-03/*
path/...

您仍可使用 glob 来读取数据,但也可以将分区值本身(在本例中为创建日期)读取为表列。请参阅分区文件路径

ON ANY NODE 子句中使用通配符可以扩展启动程序节点上的文件列表。此命令随后会在所有节点之间分发各个文件,以便跨整个群集平均分发 COPY 工作负载。ON ANY NODE 是除 Linux 之外的所有文件系统的默认值。

从 Vertica 客户端加载

使用 COPY LOCAL 将客户端系统上的文件加载到 Vertica 数据库。例如,要从本地客户端复制 GZIP 文件,请使用如下命令:

=> COPY store.store_dimension FROM LOCAL '/usr/files/my_data/input_file' GZIP;

可以使用逗号分隔的列表来加载相同压缩类型的多个文件。COPY LOCAL 随后会将这些文件连接到单个文件中,因此您不能在列表中合并具有不同压缩类型的文件。列出多个文件时,请确保指定每个输入文件的类型(如 BZIP),如下所示:

=>COPY simple_table FROM LOCAL 'input_file.bz' BZIP, 'input_file.bz' BZIP;

您可以使用 STDIN 从本地客户端加载数据,如下所示:

=> COPY simple_table FROM LOCAL STDIN;

从 Kafka 或 Spark 加载

有关从 Kafka 流式数据传输的信息,请参阅 Apache Kafka 集成

有关将 Vertica 与 Spark 数据结合使用的信息,请参阅 Apache Spark 集成

从 IDOL CFS 客户端加载数据

IDOL Connector Framework Server (CFS) VerticaIndexer 功能允许 CFS 客户端使用 ODBC 连接到您的 Vertica 数据库。连接该数据库后,CFS 会使用 COPY...FROM LOCAL 语句将 IDOL 文档元数据加载到现有 Flex 表中。有关详细信息,请参阅“使用 Flex 表”中的将 Flex 表用于 IDOL 数据部分。

5 - 数据格式

COPY 支持多种数据格式,详细信息将在后面的章节中介绍。可以通过指定解析器来指定数据格式。

默认情况下, COPY 使用 DELIMITED 解析器 (分隔数据) 将原始数据加载到数据库。原始输入数据必须采用 UTF-8 分隔文本格式。其他解析器支持其他数据格式。

指定使用哪个解析器的语法各不相同。每个解析器的描述都包含此信息。

同一 COPY 语句不能将需要不同解析器的原始数据类型(例如 NATIVEFIXEDWIDTH)进行混合。不过,您可以借助不同的解析器,使用不同的 COPY 语句将不同格式的数据加载到同一个表中。

有关验证输入数据格式的信息,请参阅处理非 UTF-8 输入

本节中描述的所有解析器都可以与常规表(使用 CREATE TABLE 或 CREATE EXTERNAL TABLE 创建的表)一起使用。有些解析器还支持 Flex 表 (CREATE FLEX TABLE)。有关 Flex 表的详细信息,请参阅使用 Flex 表解析器

所有解析器都支持所有基元数据类型,一些解析器支持基元类型的一维数组。一些解析器支持其他复杂类型。有关受支持的类型的信息,请参阅各个解析器的文档。

5.1 - 分隔数据

如果您未指定其他解析器,则 Vertica 默认使用 DELIMITED 解析器。您可以在 COPY 语句中指定分隔符、转义字符、处理 null 值的方式以及其他参数。

以下示例显示分隔符为 '|' 时的默认行为

=> CREATE TABLE employees (id INT, name VARCHAR(50), department VARCHAR(50));
CREATE TABLE

=> COPY employees FROM STDIN;
Enter data to be copied followed by a newline.
End with a backslash and a period on a line by itself.
>> 42|Sheldon Cooper|Physics
>> 17|Howard Wolowitz|Astronomy
>> \.

=> SELECT * FROM employees;
 id |      name       |  department
----+-----------------+--------------
 17 | Howard Wolowitz | Astrophysics
 42 | Sheldon Cooper  | Physics
(2 rows)

默认情况下,集合值用括号分隔,元素用逗号分隔。集合必须是一维数组或标量类型集。

=> CREATE TABLE researchers (id INT, name VARCHAR, grants ARRAY[VARCHAR], values ARRAY[INT]);
CREATE TABLE

=> COPY researchers FROM STDIN;
Enter data to be copied followed by a newline.
End with a backslash and a period on a line by itself.
>> 42|Sheldon Cooper|[US-7376,DARPA-1567]|[65000,135000]
>> 17|Howard Wolowitz|[NASA-1683,NASA-7867,SPX-76]|[16700,85000,45000]
>> \.

=> SELECT * FROM researchers;
 id |      name       |               grants               |       values
----+-----------------+------------------------------------+---------------------
 17 | Howard Wolowitz | ["NASA-1683","NASA-7867","SPX-76"] | [16700,85000,45000]
 42 | Sheldon Cooper  | ["US-7376","DARPA-1567"]           | [65000,135000]
(2 rows)

若要将某个特殊字符用作字面量,请在其前面添加一个转义字符。例如,要在加载的数据(例如当包含文件路径时)中包含一个字面量反斜线 (\),请使用两个反斜线 (\)。COPY 会在加载转义的字符时从输入中删除转义字符。

加载分隔数据时,两个连续的分隔符表示一个 null 值,除非另外设置了 NULL 参数。最后的分隔符是可选的。例如,以下输入对上一个表有效:


=> COPY employees FROM STDIN;
Enter data to be copied followed by a newline.
End with a backslash and a period on a line by itself.
>> 45|Raj|
>> 21|Leonard||
>> \.

=> SELECT * FROM employees;
 id |  name   |  department
----+---------+--------------
 21 | Leonard |
 42 | Raj     |
(2 rows)

默认情况下,如果数据包含的值太少,则加载失败。您可以使用 TRAILING NULLCOLS 选项接受任意数量的缺失列,并将其值视为 null。

Vertica 假设数据采用 UTF-8 编码。

特定于 DELIMITED 解析器的选项及其默认值为:

要将分隔数据加载到 Flex 表中,请使用 FDELIMITEDPARSER 解析器。

更改列分隔符 (DELIMITER)

默认 COPY 分隔符为竖线 ('|')。DELIMITER 是一个 ASCII 字符,用于分隔输入源中每条记录内的各个列。在两个分隔符之间,COPY 会将输入中的所有字符串数据解释为字符。请勿在字符串两侧加引号,因为引号字符也会被视为两个分隔符之间的字面量。

可以使用 E'\000' 至 E'\177'(包含)范围内的任何 ASCII 值来定义不同的分隔符。例如,如果加载的是 CSV 数据文件,并且这些文件使用逗号 (',') 字符作为分隔符,则可以将默认分隔符更改为逗号。不能对 DELIMITER 和 NULL 选项使用相同的字符。

如果分隔符位于一串数据值之间,请使用 ESCAPE AS 字符(默认值为 '\')来指明应将分隔符视为字面量。

COPY 语句可接受空值(即两个连续分隔符)作为 CHAR 和 VARCHAR 数据类型的有效输入数据。COPY 会将空列存储为一个空字符串 ('')。空字符串不等同于 NULL 字符串。

要指示一个不可打印的分隔符(例如 Tab 键),请采用扩展字符串语法 (E'...') 来指定该字符。如果数据库启用了 StandardConformingStrings,请使用 Unicode 字符串字面量 (U&'...')。例如,使用 E'\t' 或 U&'\0009' 将 Tab 键指定为分隔符。

以下示例将从逗号分隔文件加载数据:

=> COPY employees FROM ... DELIMITER ',';

在以下示例中,第一列具有特定于列的分隔符:

=> COPY employees(id DELIMITER ':', name, department) FROM ... DELIMITER ',';

更改集合分隔符(COLLECTIONDELIMITER、COLLECTIONOPEN、COLLECTIONCLOSE)

DELIMITER 选项可指定在输入中分隔列的值。对于具有集合类型(ARRAY 或 SET)的列,集合的各元素之间还需要一个分隔符。此外,集合本身具有开始和结束标记。默认情况下,集合会用括号括起来,元素则用逗号进行分隔,但您可以更改这些值。

在以下示例中,集合用大括号括起来且用句点进行分隔。

=> COPY researchers FROM STDIN COLLECTIONOPEN '{' COLLECTIONCLOSE '}' COLLECTIONDELIMITER '.';
Enter data to be copied followed by a newline.
End with a backslash and a period on a line by itself.
>> 19|Leonard|{"us-1672"."darpa-1963"}|{16200.16700}
>> \.

=> SELECT * FROM researchers;
 id |      name       |               grants               |       values
----+-----------------+------------------------------------+---------------------
 17 | Howard Wolowitz | ["NASA-1683","NASA-7867","SPX-76"] | [16700,85000,45000]
 42 | Sheldon Cooper  | ["US-7376","DARPA-1567"]           | [65000,135000]
 19 | Leonard         | ["us-1672","darpa-1963"]           | [16200,16700]
(3 rows)

更改将列或集合值括起来的字符(ENCLOSED BY、COLLECTIONENCLOSE)

使用 ENCLOSED BY 参数可以设置 ASCII 字符来分隔嵌入在字符串值中的各个字符。当且仅当括号字符是输入的第一个和最后一个字符时,括号字符才不会被视为是数据的一部分。可以将 E'\001' 至 E'\177'(包含)范围内的任何 ASCII 值(除 NULL: E'\000' 以外的任何 ASCII 字符)用于 ENCLOSED BY 值。使用双引号 (") 很常见,如以下示例所示。

=> COPY employees FROM STDIN ENCLOSED BY '"';
Enter data to be copied followed by a newline.
End with a backslash and a period on a line by itself.
>> 21|Leonard|Physics
>> 42|"Sheldon"|"Physics"
>> 17|Rajesh "Raj" K|Astronomy
>> \.

=> SELECT * FROM employees;
 id |     name       |  department
----+----------------+--------------
 17 | Rajesh "Raj" K | Astronomy
 21 | Leonard        | Physics
 42 | Sheldon        | Physics
(3 rows)

请注意,虽然 ENCLOSED BY 是双引号,但 Rajesh 姓名中嵌入的引号被视为数据的一部分,因为它们不是列中的第一个字符和最后一个字符。出于所处位置原因,已删除将 "Sheldon" 和 "Physics" 括起来的引号。

在集合值中,COLLECTIONENCLOSE 参数类似于集合中各个元素的 ENCLOSED BY。

更改 null 指示符 (NULL)

默认情况下,列值的空字符串 ('') 表示 NULL。您可以在 E'\001'E'\177'(包含)范围内指定其他 ASCII 值(除 NUL: E'\000' 之外的任何 ASCII 字符)作为 NULL 指示符。不能对 DELIMITER 和 NULL 选项使用相同的字符。

包含一个或多个空格字符的列不是 NULL,除非空格的顺序与 NULL 字符串完全匹配。

NULL 不区分大小写,但必须是数据字段分隔符之间的唯一值。例如,如果 NULL 字符串为 NULL 且分隔符为默认的竖线 (|):

|NULL| 表示 null 值。

| NULL | 不表示 null 值。

在脚本中使用 COPY 语句时,必须将包含反斜线的每个 NULL 字符串替换为双反斜线。例如,用于加载示例数据库的脚本包含:

COPY ... NULL E'\\n' ...

更改集合值的 null 指示符 (COLLECTIONNULLELEMENT)

NULL 选项指定要将列值视为 null 的值。对于具有集合类型(ARRAY 或 SET)的列,单独的选项将指定如何解释 null 元素。默认情况下,"null" 表示 null 值。表示两个连续的元素分隔符的空值表示 null:

=> COPY researchers FROM STDIN;
Enter data to be copied followed by a newline.
End with a backslash and a period on a line by itself.
>> 17|Howard|["nasa-143",,"nasa-6262"]|[10000,1650,15367]
>> 19|Leonard|["us-177",null,"us-6327"]|[16200,64000,26500]
>> \.

=> SELECT * FROM researchers;
 id |  name   |           grants            |       values
----+---------+-----------------------------+---------------------
 17 | Howard  | ["nasa-143","","nasa-6262"] | [10000,1650,15367]
 19 | Leonard | ["us-177",null,"us-6327"]   | [16200,64000,26500]
(2 rows)

使用 COLLECTIONNULLELEMENT 指定不同的值,如以下示例所示。

=> COPY researchers from STDIN COLLECTIONNULLELEMENT 'x';
Enter data to be copied followed by a newline.
End with a backslash and a period on a line by itself.
>> 42|Sheldon|[x,"us-1672"]|[x,165000]
>> \.

=> SELECT * FROM researchers;
 id |  name   |           grants            |       values
----+---------+-----------------------------+---------------------
 17 | Howard  | ["nasa-143","","nasa-6262"] | [10000,1650,15367]
 19 | Leonard | ["us-177",null,"us-6327"]   | [16200,64000,26500]
 42 | Sheldon | [null, "us-1672"]           | [null,165000]
(3 rows)

填充缺失的列 (TRAILING NULLCOLS)

默认情况下,如果输入不包含足够的列,则 COPY 会失败。使用 TRAILING NULLCOLS 选项为缺少数据的任何列插入 NULL 值。此选项不能用于具有 NOT NULL 约束的列。

以下示例演示了如何使用此选项。

=> CREATE TABLE z (a INT, b INT, c INT );

--- insert with enough data:
=> INSERT INTO z VALUES (1, 2, 3);

=> SELECT * FROM z;
 a | b | c
---+---+---
 1 | 2 | 3
(1 row)

--- insert deficient data:
=> COPY z FROM STDIN TRAILING NULLCOLS;
Enter data to be copied followed by a newline.
End with a backslash and a period on a line by itself.
>> 4 | 5 | 6
>> 7 | 8
>> \.

=> SELECT * FROM z;
 a | b | c
---+---+---
 1 | 2 | 3
 4 | 5 | 6
 7 | 8 |
(3 rows)

更改转义字符(ESCAPE AS、NO ESCAPE)

您可以指定一个转义字符,它可以将任何特殊字符视为数据的一部分。例如,如果 CSV 文件中的元素应包含逗号,您可以通过在数据中的逗号前面附加转义字符来加以表示。默认转义字符为反斜线 (\)。

要将默认字符更改为其他字符,请使用 ESCAPE AS 选项。可以将转义字符设置为 E'\001'E'\177'(包含)范围内的任何 ASCII 值。

如果您不需要任何转义字符且希望阻止任何字符被解释为转义序列,请使用 NO ESCAPE 选项。

ESCAPE AS 和 NO ESCAPE 可以在列和全局级别进行设置。

更改行尾字符 (RECORD TERMINATOR)

要指定表示数据文件记录结尾的字面量字符串,请使用 RECORD TERMINATOR 参数,后跟要使用的字符串。如果不指定值,则 Vertica 会尝试确定正确的行结束符,即仅接受换行符 (E'\n')(通常在 UNIX 系统上)或接受回车键和换行符 (E'\r\n')(通常在 Windows 平台上)。

例如,如果您的文件包含以换行符终止的逗号分隔值且您希望保留换行符,请使用 RECORD TERMINATOR 选项指定替代值:

=>  COPY mytable FROM STDIN DELIMITER ',' RECORD TERMINATOR E'\n';

要将 RECORD TERMINATOR 指定为不可打印的字符,请使用扩展字符串语法或 Unicode 字符串字面量。下表列出了一些常见的记录终止符。有关字面量字符串格式的说明,请参阅字符串字面量

如果使用 RECORD TERMINATOR 选项指定自定义值,请确保输入文件与该值匹配,否则可能会得到不一致的数据加载。

使用 JDBC 时,Vertica 建议将以下值用于 RECORD TERMINATOR

System.getProperty("line.separator")

5.2 - 二进制(原生)数据

可使用 NATIVE 解析器选项加载二进制数据,不支持此选项的 COPY LOCAL 除外。由于二进制格式数据不需要使用和处理分隔符,因此无需将整数、日期和时间戳从文本转换成本机存储格式,与分隔的数据相比,其加载性能更佳。所有二进制格式文件都必须符合附录:创建原生二进制格式文件中描述的格式规范。

本机二进制格式数据文件通常比其分隔文本格式的数据文件要大,所以在加载之前需先压缩数据。NATIVE 解析器不支持串联的压缩二进制文件。开发 ETL 应用程序插件时,您可以加载本机(二进制)格式文件。

逐字节加载二进制数据时无格式复制,因为数据中的列和记录分隔符必须进行转义。二进制数据类型值会填充到输入、受支持的函数、操作符以及类型转换中并进行转换。

加载十六进制、八进制和位字符串数据

可以仅使用十六进制、八进制和位字符串格式来加载二进制列。若要指定这些列格式,请使用 COPY 语句的 FORMAT 选项:

  • 十六进制

  • 八进制

  • 位字符串

以下示例说明如何使用 FORMAT 选项。

  1. 创建一个表:

    => CREATE TABLE t(oct VARBINARY(5),
         hex VARBINARY(5),
         bitstring VARBINARY(5) );
    
  2. 创建投影:

    => CREATE PROJECTION t_p(oct, hex, bitstring) AS SELECT * FROM t;
    
  3. 使用带有 STDIN 子句的 COPY 语句并指定每个格式:

    => COPY t (oct FORMAT 'octal', hex FORMAT 'hex',
               bitstring FORMAT 'bitstring')
       FROM STDIN DELIMITER ',';
    
  4. 在单独一行中输入要加载的数据,以反斜杠 () 和句点 (.) 结束语句:

    Enter data to be copied followed by a newline.
    End with a backslash and a period on a line by itself.
    >> 141142143144145,0x6162636465,0110000101100010011000110110010001100101
    >> \.
    
  5. 对表 t 使用选择查询以查看输入值结果:

    => SELECT * FROM t;
     oct   | hex   | bitstring
    -------+-------+-----------
    abcde  | abcde | abcde
    (1 row)
    

COPY 使用与输入二进制数据所用格式相同的默认格式来加载二进制数据。由于反斜杠字符 ('\\') 为默认转义字符,因此必须对八进制输入值进行转义。例如,将字节 '\141' 输入为 '\\\141'

在输入中,COPY 按如下方式转换字符串数据:

这两个函数都使用 VARCHAR 实参并返回 VARBINARY 值。

也可以使用转义字符通过转义两次来表示(十进制)字节 92;例如 '\\\\\\'。请注意,vsql 以四个反斜杠输入转义的反斜杠。等效输入为十六进制值 '0x5c' 和八进制值 '\134' (134 = 1 x 8^2 + 3 x 8^1 + 4 x 8^0 = 92)。

您可以加载使用反斜杠转义后的分隔符值。例如,假定分隔符为 '|''\\\001\\|\\\002' 则被加载为 {1,124,2},也可以采用八进制格式将其表示成 '\\\001\\\174\\\002'

如果插入值的字节数太多,以致于目标列容纳不下,COPY 将会返回错误。例如,如果列 c1VARBINARY(1)

=> INSERT INTO t (c1) values ('ab');   ERROR: 2-byte value too long for type Varbinary(1)

如果您对字节数过多以致于目标数据类型容纳不下的值进行隐式或显式类型转换,COPY 则会截断该数据而不进行提示。例如:

=> SELECT 'abcd'::binary(2);
 binary
--------
 ab
(1 row)

十六进制数据

可选的 '0x' 前缀指示值为十六进制而非十进制,但并非所有十六进制值都使用 A-F;例如 5396。在加载输入数据时,COPY 将忽略 0x 前缀。

如果十六进制值中存在奇数个字符,则将第一个字符处理为第一个(最左边)字节的低效半字节。

八进制数据

加载八进制格式数据要求使用三个数字的八进制代码表示每个字节。第一个数字必须在 [0,3] 范围内,第二个和第三个数字必须同时在 [0,7] 范围内。

如果某个八进制值的长度不是三的倍数,或者三个数字中有一个不在正确范围内,该值则无效,COPY 将拒绝该值所在的行。如果提供的八进制值无效,COPY 将会返回错误。例如:

=> SELECT '\\000\\387'::binary(8);
ERROR: invalid input syntax for type binary

如果行包含具有无效八进制表示的二进制值,则也会被拒绝。例如,COPY 会拒绝 '\\\008',因为 '\\\ 008' 不是有效的八进制数字。

位字符串数据

加载位字符串数据要求每个字符必须是零 (0) 或一 (1),值必须是八个字符的倍数。如果位字符串值不是八个字符的倍数,COPY 则会将前 n 个字符视为第一个(最左端)字节的低位,其中 n 为值长度除以八之后的余数。

示例

以下示例显示了 VARBINARY HEX_TO_BINARY(VARCHAR)VARCHAR TO_HEX(VARBINARY) 的用法。

  1. 创建带有二进制列的表 t 及其投影:

    => CREATE TABLE t (c BINARY(1));
    => CREATE PROJECTION t_p (c) AS SELECT c FROM t;
    
  2. 插入最小和最大字节值,包括以字符串表示的 IP 地址:

    => INSERT INTO t values(HEX_TO_BINARY('0x00'));
    => INSERT INTO t values(HEX_TO_BINARY('0xFF'));
    => INSERT INTO t values (V6_ATON('2001:DB8::8:800:200C:417A'));
    

    使用 TO_HEX 函数在输出中以十六进制设置二进制值的格式:

    => SELECT TO_HEX(c) FROM t;
     to_hex
    --------
     00
     ff
     20
    (3 rows)
    

另请参阅

5.3 - 原生 varchar 数据

当原始数据主要包含 CHARVARCHAR 数据时,请使用 NATIVE VARCHAR 解析器选项。 COPY 将在数据库服务器上执行转换为实际表数据类型的操作。COPY LOCAL 不支持此解析器选项。

使用 NATIVE VARCHARNATIVE 的效率不同。但 NATIVE VARCHAR 无需使用分隔符或转义特殊字符(例如引号),这样可以更方便地使用客户端应用程序。

通过 Vertica ODBC 和 JDBC 驱动程序执行的批量数据插入操作将自动使用 NATIVE VARCHAR 格式。

5.4 - 固定宽度格式数据

使用 FIXEDWIDTH 解析器选项可批量加载固定宽度数据。必须指定 COLSIZES 选项值以指定每一列的字节数。所加载表的定义 (COPY table f (x, y, z)) 确定要声明的 COLSIZES 值的数量。

若要加载固定宽度数据,请使用 COLSIZES 选项指定每个输入列的字节数。如果任何记录都没有值,COPY 会插入一个或多个 null 字符以补齐指定字节数。固定宽度数据文件中的最后一条记录必须包含记录终止符,以确定加载数据的结尾。

以下 COPY 选项不受支持:

  • DELIMITER

  • ENCLOSED BY

  • ESCAPE AS

  • TRAILING NULLCOLS

在固定宽度数据中使用 null 值

用于固定宽度加载的默认 NULL 字符串不能为空字符串,应改用全部由空格组成的字符串。空格数取决于使用 COLSIZES (integer, [,...]) 选项声明的列宽度。

对于固定宽度加载,NULL 定义取决于是在列级别还是在语句级别指定 NULL:

  • 语句级别:必须将 NULL 定义为单字符。整个列宽度重复使用默认(或自定义)的 NULL 字符。

  • 列级别:必须将 NULL 定义为字符串,其长度与列宽度相匹配。

对于固定宽度加载,如果输入数据列的值数量少于指定列大小,COPY 会插入 NULL 字符。NULL 数量必须与声明的列宽度相匹配。如果您在列级别指定 NULL 字符串,COPY 会按照列宽度匹配字符串。

定义 null 字符(语句级别)

  1. 创建包含两个列的表 (fw):

    => CREATE TABLE fw(co int, ci int);
    CREATE TABLE
    
  2. 复制该表并将 NULL 指定为 'N',然后输入一些数据:

    => COPY fw FROM STDIN FIXEDWIDTH colsizes(2,2) null AS 'N' NO COMMIT;
    Enter data to be copied followed by a newline.
    End with a backslash and a period on a line by itself.
    >> NN12
    >> 23NN
    >> NNNN
    >> nnnn
    >> \.
    
  3. 从表中选择所有 (*):

    => SELECT * FROM fw;
     co | ci
    ----+----
        | 12
     23 |
    (2 rows)
    

定义自定义的记录终止符

要定义不同于加载固定宽度数据时的 COPY 默认值的记录终止符,请执行以下步骤:

  1. 创建包含两列(coci)的表 fw

    => CREATE TABLE fw(co int, ci int);
    CREATE TABLE
    
  2. 复制该表 fw,并指定列大小为 2 个字节,指定逗号 (,) 作为记录终止符:

    => COPY fw FROM STDIN FIXEDWIDTH colsizes(2,2) RECORD TERMINATOR ',';
    Enter data to be copied followed by a newline.
    End with a backslash and a period on a line by itself.
    >> 1234,1444,6666
    >> \.
    
  3. 查询表 fw 中的所有数据:

    => SELECT * FROM fw;
     co | ci
    ----+----
     12 | 34
     14 | 44
    (2 rows)
    

SELECT 输出仅指示两个值。 COPY 拒绝了第三个值 (6666),因为其后无逗号 (,) 记录终止符。仅在显式指定记录终止符时,固定宽度数据才需要尾随记录终止符。

复制固定宽度的数据

使用 COPY FIXEDWIDTH COLSIZES (n [,...) 将文件加载到 Vertica 数据库中。默认情况下,所有空格均为 NULL。例如:

=> CREATE TABLE mytest(co int, ci int);
=> CREATE PROJECTION mytest_p1 AS SELECT * FROM mytest SEGMENTED BY HASH(co) ALL NODES;
=> COPY mytest(co,ci) FROM STDIN FIXEDWIDTH colsizes(6,4) NO COMMIT;
Enter data to be copied followed by a newline.
End with a backslash and a period on a line by itself.
>> \.
=> SELECT * FROM mytest ORDER BY co;
 co | ci
----+----
(0 rows)

跳过固定宽度数据中的内容

COPY 语句有两个用于跳过输入数据的选项。SKIP BYTES 选项仅用于固定宽度数据加载:

以下示例将使用 SKIP BYTES 在加载包含两列(4 和 6 个字节)的固定宽度表时跳过 11 个字节:

  1. 使用 SKIP BYTES 复制表:

    => COPY fw FROM STDIN FIXEDWIDTH colsizes (4,6) SKIP BYTES 11;
    Enter data to be copied followed by a newline.
    End with a backslash and a period on a line by itself.
    >> 2222666666
    >> 1111999999
    >> 1632641282
    >> \.
    
  2. 查询表 fw 中的所有数据:

    => SELECT * FROM fw ORDER BY co;
      co  |   ci
    ------+--------
     1111 | 999999
     1632 | 641282
    (2 rows)
    

输出会确认 COPY 跳过了已加载数据的前 11 个字节。

以下示例会在加载固定宽度 (4、6) 表时使用 SKIP

  1. 复制表并使用 SKIP 跳过输入数据的两条记录:

    => COPY fw FROM STDIN FIXEDWIDTH colsizes (4,6) SKIP 2;
    Enter data to be copied followed by a newline.
    End with a backslash and a period on a line by itself.
    >> 2222666666
    >> 1111999999
    >> 1632641282
    >> 3333888888
    >> \.
    
  2. 查询表 fw 中的所有数据:

    => SELECT * FROM fw ORDER BY co;
      co  |   ci
    ------+--------
     1632 | 641282
     3333 | 888888
    (2 rows)
    

输出会确认 COPY 跳过了已加载数据的前两条记录。

在固定宽度的数据加载中修剪字符

使用 TRIM 选项可修剪字符。 TRIM 接受在数据开头和结尾处修剪的单字节字符。对于固定宽度数据加载,当您指定 TRIM 字符时,COPY 会先检查行是否为 NULL。如果行不为 NULL,COPY 会修剪字符。下一个示例指示 COPY 修剪字符 A,并显示结果:

  1. 复制表 fw 并指定 TRIM 字符 A

    => COPY fw FROM STDIN FIXEDWIDTH colsizes(4,6) TRIM 'A';
    Enter data to be copied followed by a newline.
    End with a backslash and a period on a line by itself.
    >> A22A444444
    >> A22AA4444A
    >> \.
    
  2. 查询表 fw 中的所有数据:

    => SELECT * FROM fw ORDER BY co;
     co |   ci
    ----+--------
     22 |   4444
     22 | 444444
    (2 rows)
    

在固定宽度的数据加载中使用填充

默认情况下,填充字符为“ ”(单个空格)。固定宽度数据加载的填充行为类似于如何以其他格式处理空格,处理方式因数据类型而异,具体如下:

5.5 - ORC 数据

ORC(优化的行列)格式是一种面向列的文件格式。Vertica 拥有一个可处理此格式且可利用列式布局的解析器。

COPY 语句中,请按如下所示指定解析器。请注意,这不是通常的 PARSER parser-name 语法;COPY 直接支持 ORC:

=> COPY tableName FROM path ORC[(...)];

该解析器会使用多个可选参数;请参阅 ORC 参考页面。

请注意,如果您在同一个 COPY 语句中从多个文件加载数据,而其中任何一个加载被中止,那么整个加载都会中止。这种行为与分隔文件的行为不同,分隔文件中的 COPY 语句会加载其可以加载的数据并忽略剩余数据。

支持的数据类型

Vertica 可以本机读取所有 Hive 原始数据类型的列。有关完整列表,请参阅 HIVE 数据类型(特别是数字、日期/时间、字符串和其他列表)。Vertica 还可以加载 UUID 和 复杂类型(任意组合的数组和结构)。

您为 COPY 或 CREATE EXTERNAL TABLE AS COPY 指定的数据类型必须与数据中的类型完全匹配,但 Vertica 允许在兼容的数字类型之间进行隐式转换。

时间戳和时区

要正确报告时间戳,Vertica 必须知道将数据写入的时区。早期版本的 ORC 格式不会记录该时区。

Hive 版本 1.2.0 及更高版本会在条带页脚中记录写入者所在的时区。Vertica 会使用该时区来确保读入到数据库中的时间戳值与写入到源文件的时间戳值相匹配。对于缺少此时区信息的 ORC 文件,Vertica 假设这些值已写入到本地时区,并在 QUERY_EVENTS 系统表中记录 ORC_FILE_INFO 事件。在第一次查询后检查此类型的事件,以验证时间戳是否按预期予以处理。

5.6 - Parquet 数据

Parquet 是一种面向列的文件格式。Vertica 拥有一个可以利用列式布局的解析器。

COPY 语句中,请按如下所示指定解析器。请注意,这不是通常的 PARSER parser-name 语法;COPY 直接支持 Parquet:


=> COPY tableName FROM path PARQUET[(...)];

该解析器会使用多个可选参数;请参阅 PARQUET 参考页面。

请注意,如果您在同一个 COPY 语句中从多个文件加载数据,而其中任何一个加载被中止,那么整个加载都会中止。这种行为与分隔文件的行为不同,分隔文件中的 COPY 语句会加载其可以加载的数据并忽略剩余数据。

架构匹配

默认情况下,Parquet 解析器使用强架构匹配。这意味着加载必须按照列在数据中出现的顺序来使用 Parquet 数据中的所有列。但是,您可以改为使用弱架构匹配,以便选择所需的列而忽略剩余列。弱架构匹配取决于数据中列的名称而非其顺序,因此表中的列名称必须与数据中的列名称匹配。类型必须匹配或可进行强制设置。有关如何使用宽泛的架构匹配的详细信息,请参阅 PARQUET 参考页面上的弱架构匹配

您可以对基元类型的列和基元类型的一维数组使用弱架构匹配。对于其他复杂类型,请改用可变复杂类型。请参阅可变复杂类型

使用 do_soft_schema_match_by_name 参数指定弱架构匹配。在以下示例中,Parquet 数据包含的列多于表中使用的列。

=> CREATE EXTERNAL TABLE restaurants(name VARCHAR, cuisine VARCHAR)
    AS COPY FROM '/data/rest*.parquet'
    PARQUET(do_soft_schema_match_by_name='True');

=> SELECT * from restaurant;
       name        | cuisine
-------------------+----------
 Bob's pizzeria    | Italian
 Bakersfield Tacos | Mexican
(2 rows)

元数据缓存

Parquet 文件包含 Vertica 在加载数据时使用的元数据。为避免重复获提取此数据,尤其是从远程源或 API 调用会产生财务成本的情况下,Vertica 会在计划阶段将此元数据缓存在每个参与节点上,以供在执行阶段使用。

仅当 TEMP 存储位于本地文件系统上时,Vertica 才会将 TEMP 存储用于缓存。

您可以通过设置 ParquetMetadataCacheSizeMB 配置参数来限制缓存的大小。默认值为 4GB。

支持的数据类型

Vertica 可以本机读取所有 Hive 原始数据类型的列。有关完整列表,请参阅 HIVE 数据类型(特别是数字、日期/时间、字符串和其他列表)。Vertica 还可以加载 UUID 和 复杂类型(任意组合的数组和结构)。

您为 COPY 或 CREATE EXTERNAL TABLE AS COPY 指定的数据类型必须与数据中的类型完全匹配,但 Vertica 允许在兼容的数字类型之间进行隐式转换。

仅对于 Parquet 格式,您可以使用可变复杂类型,而不是完全指定复杂类型的架构。请参阅可变复杂类型

时间戳和时区

要正确报告时间戳,Vertica 必须知道将数据写入的时区。Hive 不会记录写入者所在的时区。Vertica 假设时间戳值是在本地时区写入的,并在查询时报告警告。

在写入 Parquet 文件时,Hive 提供了用于在本地时区中记录时间戳的选项。如果您使用以这种方式记录时间的 Parquet 文件,请将 UseLocalTzForParquetTimestampConversion 配置参数设置为 0 以禁用由 Vertica 完成的转换。(请参阅常规参数。)

5.7 - JSON 数据

使用 FJSONPARSER 加载 JSON 格式的数据。

JSON 数据的架构是属性/值对中的一组属性名称。将 JSON 数据加载到列式表或 Flex 表中的实体化列中时,数据中的属性名称必须与表中的列名称匹配。您不需要加载数据中的所有列。

JSON 解析器可以将数据加载到任何标量类型、强类型复杂类型可变复杂类型的列中。可变复杂类型意味着您没有完全指定该列的架构。可以将表中的这些列定义为 LONG VARBINARY,并且可以使用 Flex 函数从中提取值。

COPY 语句中,使用 PARSER 参数可指定 JSON 解析器,如以下示例所示:

=> CREATE EXTERNAL TABLE customers(id INT, address VARCHAR, transactions ARRAY[INT,10])
    AS COPY FROM 'cust.json' PARSER FJSONPARSER();

此解析器有几个可选参数,其中一些专门用于 Flex 表和可变复杂类型。

在加载 JSON 数据之前,请考虑使用 JSONLint 等工具来验证数据是否有效。

如果将 JSON 数据加载到 Flex 表中,Vertica 会将所有数据加载到 __raw__ (VMap) 列中,包括在数据中找到的复杂类型。可以使用 Flex 函数来提取值。

强类型复杂类型

JSON 数据可能包含数组、结构体以及这两者的组合。可以将这些数据作为可变 (VMap) 列或具有强类型的可变列来加载。强类型允许您直接查询值,而无需使用函数来解压 VMap 列。

像往常一样,在表定义中使用 ARRAYROW 类型:

=> CREATE EXTERNAL TABLE rest
  (name VARCHAR, cuisine VARCHAR,
   location_city ARRAY[VARCHAR(80),50],
   menu ARRAY[ ROW(item VARCHAR(80), price FLOAT), 100 ]
  )
 AS COPY FROM :restdata PARSER FJSONPARSER();

=> SELECT name, location_city, menu FROM rest;
       name        |       location_city        |                                     menu
-------------------+----------------------------+------------------------------------------------------------------------------
 Bob's pizzeria    | ["Cambridge","Pittsburgh"] | [{"item":"cheese pizza","price":8.25},{"item":"spinach pizza","price":10.5}]
 Bakersfield Tacos | ["Pittsburgh"]             | [{"item":"veggie taco","price":9.95},{"item":"steak taco","price":10.95}]
(2 rows)

将 JSON 数据加载到具有复杂类型的强类型的表中时,Vertica 会忽略解析器的 flatten_mapsflatten_arrays 参数。

强复杂类型和可变复杂类型

强类型的优点是在查询中访问数据更容易(且更高效)。缺点是会忽略在数据中找到但未包含在列定义中的其他值。如果此数据中的菜单结构体包含更多属性(例如卡路里),则不会加载它们,因为列的定义仅指定了选项和价格。以下示例使用可变复杂类型来显示额外的属性:

=> CREATE EXTERNAL TABLE rest
  (name VARCHAR, cuisine VARCHAR,
   location_city LONG VARBINARY, menu LONG VARBINARY)
 AS COPY FROM :restdata
 PARSER FJSONPARSER(flatten_maps=false);

=> SELECT name, MAPTOSTRING(location_city) as location_city, MAPTOSTRING(menu) AS menu FROM rest;
       name        |       location_city      |                     menu
-------------------+--------------------------+---------------------------------------------------
 Bob's pizzeria    | {
    "0": "Cambridge",
    "1": "Pittsburgh"
} | {
    "0": {
        "calories": "1200",
        "item": "cheese pizza",
        "price": "8.25"
    },
    "1": {
        "calories": "900",
        "item": "spinach pizza",
        "price": "10.50"
    }
}
 Bakersfield Tacos | {
    "0": "Pittsburgh"
}                    | {
    "0": {
        "item": "veggie taco",
        "price": "9.95",
        "vegetarian": "true"
    },
    "1": {
        "item": "steak taco",
        "price": "10.95"
    }
}
(2 rows)

从特定的起点加载

您无需加载整个 JSON 文件。您可以使用 start_point 参数在特定键而非文件开头处加载数据。数据解析始于 start_point 键后,直到文件的末尾或首个 start_point 值的末尾。解析器将忽略 start_point 的任何后续实例,即使该键在输入文件中多次出现也是如此。如果输入数据只包含 start_point 键的一个副本,并且该值为 JSON 元素的列表,解析器会将列表中的各个元素加载为行。

如果 start_point 值在 JSON 数据中出现多次,您可以使用 start_point_occurrence 整数参数来指定在哪次出现时开始解析。

此示例使用以下 JSON 数据,这些数据已保存到名为 alphanums.json 的文件中:

 { "A": { "B": { "C": [ { "d": 1, "e": 2, "f": 3 }, { "g": 4, "h": 5, "i": 6 },
{ "j": 7, "k": 8, "l": 9 } ] } } }

将此数据加载到 Flex 表中会产生以下结果:

=> CREATE FLEX TABLE start_json;
CREATE TABLE

=> COPY start_json FROM '/home/dbadmin/data/alphanums.json' PARSER FJSONPARSER();
 Rows Loaded
-------------
           1
(1 row)

=> SELECT maptostring(__raw__) FROM start_json;
                        maptostring
-------------------------------------------------------------------------------
 {
   "A.B.C" : {
      "0.d" : "1",
      "0.e" : "2",
      "0.f" : "3",
      "1.g" : "4",
      "1.h" : "5",
      "1.i" : "6",
      "2.j" : "7",
      "2.k" : "8",
      "2.l" : "9"
   }
}

(1 row)

以下加载指定了起点:

=> TRUNCATE TABLE start_json;
TRUNCATE TABLE

=>  COPY start_json FROM '/home/dbadmin/data/alphanums.json' PARSER FJSONPARSER(start_point='B');
 Rows Loaded
-------------
           1
(1 row)

=> SELECT maptostring(__raw__) FROM start_json;
                         maptostring
--------------------------------------------------------------------------------
 {
   "C" : {
      "0.d" : "1",
      "0.e" : "2",
      "0.f" : "3",
      "1.g" : "4",
      "1.h" : "5",
      "1.i" : "6",
      "2.j" : "7",
      "2.k" : "8",
      "2.l" : "9"
   }
}
(1 row)

处理无效的 JSON 记录

如果 JSON 数据包含语法错误,您的加载可能会因无效记录而失败。如果 JSON 记录始终用换行符等字符进行分隔,则可以在 COPY 语句中使用 RECORD_TERMINATOR 选项来跳过这些无效的记录。设置记录终止符允许解析器跳过无效记录并继续解析剩余数据。

如果标记记录所用的字符不一致,则可以使用 ERROR TOLERANCE 选项。ERROR TOLERANCE 会跳过包含无效 JSON 记录的整个源文件,而 RECORD_TERMINATOR 会跳过个别格式错误的 JSON 记录。您可以同时使用这两个选项。

以下示例将使用无效记录:

=> => CREATE FLEX TABLE fruits();
CREATE TABLE

=> COPY fruits FROM STDIN PARSER FJSONPARSER(RECORD_TERMINATOR=E'\n');
Enter data to be copied followed by a newline.
End with a backslash and a period on a line by itself
>> {"name": "orange", "type": "fruit", "color": "orange", "rating": 5 }
>> {"name": "apple", "type": "fruit", "color": "green" }
>> {"name": "blueberry", "type": "fruit", "color": "blue", "rating": 10 }
>> "type": "fruit", "rating": 7 }
>> {"name": "banana", "type" : "fruit", "color": "yellow", "rating": 3 }
>> \.

使用 MAPTOSTRING 查看 Flex 表,以确认在成功加载剩余记录时跳过了无效记录:

=> SELECT MAPTOSTRING(__raw__) FROM fruits;
maptostring
--------------------------------------------------------------------------------------------
{
"color" : "orange",
"name" : "orange",
"rating" : "5",
"type" : "fruit"
}
{
"color" : "green",
"name" : "apple",
"type" : "fruit"
}
{
"color" : "blue",
"name" : "blueberry",
"rating" : "10",
"type" : "fruit"
}
{
"color" : "yellow",
"name" : "banana",
"rating" : "3",
"type" : "fruit"
}
(4 rows)

拒绝包含实体化列类型错误的数据

默认情况下,如果 FJSONPARSER 无法将数据值强制转换为与列定义匹配的类型,它会将值设置为 NULL。您可以选择使用 reject_on_materialized_type_error 参数来拒绝这些值。如果此参数为 true,则 COPY 拒绝此类行并报告错误。

如果该列是强类型复杂类型,而不是可变复杂类型,则复杂类型中的类型不匹配将导致整个列被视为不匹配。解析器不会部分加载复杂类型;如果无法强制使用任何 ROW 字段或 ARRAY 元素,则会为列加载 NULL。

以下示例会尝试加载无效数据。请注意,查询结果中缺少无效行:

=> CREATE TABLE test(one VARCHAR, two INT);
CREATE TABLE

=> COPY test FROM stdin
   PARSER FJSONPARSER(reject_on_materialized_type_error=true);
Enter data to be copied followed by a newline.
End with a backslash and a period on a line by itself.
>> {"one": 1, "two": 2}
>> {"one": "one", "two": "two"}
>> {"one": "one", "two": 2}
>> \.

=> SELECT one, two FROM test;
 one | two
-----+-----
 1   |   2
 one |   2
(2 rows)

拒绝或忽略 Flex 表中的空键

有效的 JSON 文件可能包含空键值对。默认情况下,对于 Flex 表,FJSONPARSER 会加载空键值对,如以下示例所示:

=> CREATE FLEX TABLE fruits();
CREATE TABLE

=> COPY fruits FROM STDIN PARSER FJSONPARSER();
Enter data to be copied followed by a newline.
End with a backslash and a period on a line by itself.
>> {"name": "orange", "rating": 5}
>> {"name": "apple", "rating" : 10}
>> {"": "banana", "rating" : 2}
>> \.

=> SELECT MAPTOSTRING(__raw__) FROM fruits;
              maptostring
---------------------------------------
 {
        "name": "orange",
        "rating": "5"
}
 {
        "name": "apple",
        "rating": "10"
}
 {
        "": "banana",
        "rating": "2"
}
(3 rows)

要忽略具有空白键的字段,请使用 omit_empty_keys 参数:

=> COPY fruits FROM STDIN PARSER FJSONPARSER(omit_empty_keys=true);
Enter data to be copied followed by a newline.
End with a backslash and a period on a line by itself.
>> {"name": "apple", "rating": 5}
>> {"": "missing", "rating" : 1}
>> {"name": "", "rating" : 3}
>> \.
=> SELECT MAPTOSTRING(__raw__) FROM fruits;
             maptostring
--------------------------------------
 {
        "name": "apple",
        "rating": "5"
}
 {
        "rating": "1"
}
 {
        "name": "",
        "rating": "3"
}
(3 rows)

请注意,缺少名称的第二个值仍会与另一个(非空)字段一起加载。要完全拒绝该行,请使用 reject_on_empty_key 参数:

=> COPY fruits FROM STDIN PARSER FJSONPARSER(reject_on_empty_key=true);
Enter data to be copied followed by a newline.
End with a backslash and a period on a line by itself.
>> {"name" : "apple", "rating" : 5}
>> {"" : "missing", "rating" : 1}
>> {"name" : "", "rating" : 3}
>> \.
=> SELECT MAPTOSTRING(__raw__) FROM fruits;
             maptostring
--------------------------------------
 {
        "name": "apple",
        "rating": "5"
}
 {
        "name": "",
        "rating": "3"
}
(2 rows)

5.8 - Avro 数据

使用 FAVROPARSER 可加载 Avro 数据文件。此解析器支持列式表和 Flex 表。

列可以是任何标量类型、强类型复杂类型可变复杂类型。可变复杂类型意味着您没有完全指定该列的架构。您将表中的这些列定义为 LONG VARBINARY,并且可以使用 Flex 函数从中提取值。

需要满足以下要求:

  • Avro 文件必须以 Avro 二进制序列化编码格式进行编码,如 Apache Avro 标准中所述。解析器还支持 Snappy 和 deflate 压缩。

  • Avro 文件必须在正在加载的文件中包含其相关架构;FAVROPARSER 不支持具有单独架构文件的 Avro 文件。

在 COPY 语句中,使用 PARSER 参数可指定 Avro 解析器,如下例所示:

=> COPY weather FROM '/home/dbadmin/data/weather.avro' PARSER FAVROPARSER()

此解析器有几个可选参数,其中一些专门用于 Flex 表和可变复杂类型。

Avro 架构和列式表

Avro 将架构包含在数据中。将 Avro 数据加载到列式表中时,数据中架构中的列名称必须与表中的列名称匹配。您不需要加载数据中的所有列。

例如,以下 Avro 架构使用 Avro 记录类型来表示用户配置文件:

{
  "type": "record",
  "name": "Profile",
  "fields" : [
      {"name": "UserName", "type": "string"},
      {"name": "Email", "type": "string"},
      {"name": "Address", "type": "string"}
   ]
}

要使用此架构成功地将数据加载到列式表中,每个目标列名称必须与架构中的“名称”值匹配。在以下示例中,profiles 表不会加载与架构的 Email 字段对应的值,因为目标列名为 EmailAddr

=> COPY profiles FROM '/home/dbadmin/data/user_profile.avro' PARSER FAVROPARSER();

=> SELECT * FROM profiles;
     UserName    |     EmailAddr      |       Address
-----------------+--------------------+---------------------
    dbadmin      |                    |    123 Main St.

强类型复杂类型

Avro 数据可以包含数组、结构和两者的组合。可以将这些数据作为可变 (VMap) 列或强类型来读取。强类型允许您直接查询值,而无需使用函数来解压 VMap 列。

像往常一样,在表定义中使用 ARRAYROW 类型:

=> CREATE EXTERNAL TABLE rest
  (name VARCHAR, cuisine VARCHAR,
   location_city ARRAY[VARCHAR(80)],
   menu ARRAY[ ROW(item VARCHAR(80), price FLOAT) ]
  )
AS COPY FROM :avro_file PARSER FAVROPARSER();

您可以在外部表和原生表中使用强类型。

可变复杂类型可以替代复杂类型的强类型。

拒绝包含实体化列类型错误的数据

默认情况下,如果 FAVROPARSER 无法将数据值强制转换为与列定义匹配的类型,它会将值设置为 NULL。您可以选择使用 reject_on_materialized_type_error 参数来拒绝这些值。如果此参数为 true,则 COPY 拒绝此类行并报告错误。

如果该列是强类型复杂类型,而不是可变复杂类型,则复杂类型中的类型不匹配将导致整个列被视为不匹配。解析器不会部分加载复杂类型。

如果 Flex 表具有实体化列,则加载的数据必须强制转换为该列的类型。例如,如果实体化列声明为 FLOAT 而您尝试为该键加载 VARCHAR 值,则 FAVROPARSER 拒绝该数据行。

另请参阅

5.9 - 正则表达式中的匹配项

您可以使用 fregexparser 将正则表达式的匹配结果加载到 Flex 表或列式表中。本节将介绍 Flex 解析器支持选项的一些使用示例。

示例正则表达式

这些示例将使用以下正则表达式,它们会搜索包括 timestampdatethread_namethread_id 字符串在内的信息。

此示例表达式会加载任何 thread_id 十六进制值,而无论这些值是否具有 0x 前缀 (<thread_id>(?:0x)?[0-9a-f]+).

'^(?<time>\d\d\d\d-\d\d-\d\d \d\d:\d\d:\d\d\.\d+)
 (?<thread_name>[A-Za-z ]+):(?<thread_id>(?:0x)?[0-9a-f]+)
-?(?<transaction_id>[0-9a-f])?(?:[(?<component>\w+)]
\<(?<level>\w+)\> )?(?:<(?<elevel>\w+)> @[?(?<enode>\w+)]?: )
?(?<text>.*)'

将正则表达式匹配项用于 Flex 表

您可以使用 fregexparser 将正则表达式结果加载到 Flex 表中。。有关执行此操作的完整示例,请参阅 FREGEXPARSER

针对列式表使用 fregexparser

此部分说明了如何加载针对 Vertica 数据库示例日志文件使用的正则表达式的结果。通过使用外部表定义,介绍了使用 fregexparser 将数据加载到列式表中的示例。将 Flex 表解析器用于列式表使您能够在一个表中混合数据加载。例如,您可以在一个会话中加载正则表达式的结果,在另一个会话中加载 JSON 数据。

以下基本示例说明了这种用法。

  1. 创建一个列式表 vlog,带有以下列:

    => CREATE TABLE vlog (
        "text"             varchar(2322),
        thread_id         varchar(28),
        thread_name     varchar(44),
        "time"             varchar(46),
        component         varchar(30),
        level             varchar(20),
        transaction_id     varchar(32),
        elevel             varchar(20),
        enode             varchar(34)
    );
    
  2. 借助 fregexparser 使用 COPY 加载采用上述示例正则表达式的日志文件的多个部分:在您自己尝试之前,请务必从该表达式示例中移除任何行字符:

    => COPY v_log FROM '/home/dbadmin/data/flex/vertica.log' PARSER
    FRegexParser(pattern=
    '^(?<time>\d\d\d\d-\d\d-\d\d \d\d:\d\d:\d\d\.\d+)
     (?<thread_name>[A-Za-z ]+):(?<thread_id>(?:0x)?[0-9a-f]+)
    -?(?<transaction_id>[0-9a-f])?(?:[(?<component>\w+)]
    \<(?<level>\w+)\> )?(?:<(?<elevel>\w+)> @[?(?<enode>\w+)]?: )
    ?(?<text>.*)'
    
    ) rejected data as table fregex_reject;
  3. 查询 time 列:

    => SELECT time FROM flogs limit 10;
              time
    -------------------------
     2014-04-02 04:02:02.613
     2014-04-02 04:02:02.613
     2014-04-02 04:02:02.614
     2014-04-02 04:02:51.008
     2014-04-02 04:02:51.010
     2014-04-02 04:02:51.012
     2014-04-02 04:02:51.012
     2014-04-02 04:02:51.013
     2014-04-02 04:02:51.014
     2014-04-02 04:02:51.017
    (10 rows)
    

借助 fregexparser 使用外部表

通过为 Vertica 日志文件创建外部列式表,查询该表将返回更新后的日志信息。以下基本示例说明了这种用法。

  1. 创建列式表 vertica_log,使用 AS COPY 子句和 fregexparser 加载正则表达式的匹配结果。为了便于说明,此正则表达式具有用于拆分长文本行的换行符。在使用此表达式进行测试之前,请移除所有换行符:

    => CREATE EXTERNAL TABLE public.vertica_log
    (
        "text" varchar(2322),
        thread_id varchar(28),
        thread_name varchar(44),
        "time" varchar(46),
        component varchar(30),
        level varchar(20),
        transaction_id varchar(32),
        elevel varchar(20),
        enode varchar(34)
    )
    AS COPY
    FROM '/home/dbadmin/data/vertica.log'
    PARSER FRegexParser(pattern=
    '^(?<time>\d\d\d\d-\d\d-\d\d \d\d:\d\d:\d\d\.\d+)
     (?<thread_name>[A-Za-z ]+):(?<thread_id>(?:0x)?[0-9a-f]+)
    -?(?<transaction_id>[0-9a-f])?(?:[(?<component>\w+)]
    \<(?<level>\w+)\> )?(?:<(?<elevel>\w+)> @[?(?<enode>\w+)]?: )
    ?(?<text>.*)'
    
    );
  2. 从外部表查询以获取更新后的结果:

    => SELECT component, thread_id, time FROM vertica_log limit 10;
     component | thread_id  |          time
    -----------+------------+-------------------------
     Init      | 0x16321430 | 2014-04-02 04:02:02.613
     Init      | 0x16321430 | 2014-04-02 04:02:02.613
     Init      | 0x16321430 | 2014-04-02 04:02:02.613
     Init      | 0x16321430 | 2014-04-02 04:02:02.613
     Init      | 0x16321430 | 2014-04-02 04:02:02.613
     Init      | 0x16321430 | 2014-04-02 04:02:02.613
     Init      | 0x16321430 | 2014-04-02 04:02:02.613
               | 0x16321430 | 2014-04-02 04:02:02.614
               | 0x16321430 | 2014-04-02 04:02:02.614
               | 0x16321430 | 2014-04-02 04:02:02.614
    (10 rows)
    

5.10 - 通用事件格式 (CEF) 数据

使用 Flex 解析器 fcefparser 将 Micro Focus ArcSight 或其他通用事件格式 (CEF) 日志文件数据加载到列式表和 Flex 表中。

当您使用该解析器加载任意 CEF 格式文件时,它会将数据中的键名解释为 Flex 表中的虚拟列。加载后,无论每行中存在何种键集,您均可直接查询 CEF 数据。您还可以使用关联 Flex 表数据和映射函数来管理对 CEF 数据的访问。

创建 Flex 表并加载 CEF 数据

此部分使用了一个示例 CEF 数据集。所有 IP 地址均已被有意更改为不准确的地址,并为了便于说明而添加了换行符。

要使用此示例数据,请复制下面的文本并删除所有换行符。将文件另存为 CEF_sample.cef,该名称将被用于所有这些示例。

CEF:0|ArcSight|ArcSight|6.0.3.6664.0|agent:030|Agent [test] type [testalertng] started|Low|
eventId=1 mrt=1396328238973 categorySignificance=/Normal categoryBehavior=/Execute/Start
categoryDeviceGroup=/Application catdt=Security Mangement categoryOutcome=/Success
categoryObject=/Host/Application/Service art=1396328241038 cat=/Agent/Started
deviceSeverity=Warning rt=1396328238937 fileType=Agent
cs2=<Resource ID\="3DxKlG0UBABCAA0cXXAZIwA\=\="/> c6a4=fe80:0:0:0:495d:cc3c:db1a:de71
cs2Label=Configuration Resource c6a4Label=Agent
IPv6 Address ahost=SKEELES10 agt=888.99.100.1 agentZoneURI=/All Zones/ArcSight
System/Private Address Space
Zones/RFC1918: 888.99.0.0-888.200.255.255 av=6.0.3.6664.0 atz=Australia/Sydney
aid=3DxKlG0UBABCAA0cXXAZIwA\=\= at=testalertng dvchost=SKEELES10 dvc=888.99.100.1
deviceZoneURI=/All Zones/ArcSight System/Private Address Space Zones/RFC1918:
888.99.0.0-888.200.255.255 dtz=Australia/Sydney _cefVer=0.1
  1. 创建 Flex 表 logs

    => CREATE FLEX TABLE logs();
    CREATE TABLE
    
  2. 使用 Flex 解析器 fcefparser 加载示例 CEF 文件:

    => COPY logs FROM '/home/dbadmin/data/CEF_sample.cef' PARSER fcefparser();
     Rows Loaded
    -------------
               1
    (1 row)
    
  3. 使用 maptostring() 函数查看 logs Flex 表的内容:

    => SELECT maptostring(__raw__) FROM logs;
                                      maptostring
    -------------------------------------------------------------------------------------
      {
       "_cefver" : "0.1",
       "agentzoneuri" : "/All Zones/ArcSight System/Private Address
        Space Zones/RFC1918: 888.99.0.0-888.200.255.255",
       "agt" : "888.99.100.1",
       "ahost" : "SKEELES10",
       "aid" : "3DxKlG0UBABCAA0cXXAZIwA==",
       "art" : "1396328241038",
       "at" : "testalertng",
       "atz" : "Australia/Sydney",
       "av" : "6.0.3.6664.0",
       "c6a4" : "fe80:0:0:0:495d:cc3c:db1a:de71",
       "c6a4label" : "Agent IPv6 Address",
       "cat" : "/Agent/Started",
       "catdt" : "Security Mangement",
       "categorybehavior" : "/Execute/Start",
       "categorydevicegroup" : "/Application",
       "categoryobject" : "/Host/Application/Service",
       "categoryoutcome" : "/Success",
       "categorysignificance" : "/Normal",
       "cs2" : "<Resource ID=\"3DxKlG0UBABCAA0cXXAZIwA==\"/>",
       "cs2label" : "Configuration Resource",
       "deviceproduct" : "ArcSight",
       "deviceseverity" : "Warning",
       "devicevendor" : "ArcSight",
       "deviceversion" : "6.0.3.6664.0",
       "devicezoneuri" : "/All Zones/ArcSight System/Private Address Space
        Zones/RFC1918: 888.99.0.0-888.200.255.255",
       "dtz" : "Australia/Sydney",
       "dvc" : "888.99.100.1",
       "dvchost" : "SKEELES10",
       "eventid" : "1",
       "filetype" : "Agent",
       "mrt" : "1396328238973",
       "name" : "Agent [test] type [testalertng] started",
       "rt" : "1396328238937",
       "severity" : "Low",
       "signatureid" : "agent:030",
       "version" : "0"
    }
    
    (1 row)
    

创建列式表并加载 CEF 数据

此示例可让您针对 CEF 数据比较 Flex 表与列式表。要实现这一目标,您应该创建一个新表,并加载在前面 Flex 表示例中使用的同一个 CEF_sample.cef 文件。

  1. 创建一个列式表 col_logs,并定义在 fcefparser 中硬编码的前缀名:

    => CREATE TABLE col_logs(version INT,
      devicevendor VARCHAR,
      deviceproduct VARCHAR,
      deviceversion VARCHAR,
      signatureid VARCHAR,
      name VARCHAR,
      severity VARCHAR);
    CREATE TABLE
    
  2. 将示例文件加载到 col_logs 中,就像您之前对 Flex 表所做的那样:

    => COPY col_logs FROM '/home/dbadmin/data/CEF_sample.cef' PARSER fcefparser();
     Rows Loaded
    -------------
               1
    (1 row)
    
  3. 查询该表。您可以在 Flex 表输出中找到完全相同的信息。

    => \x
    Expanded display is on.
    VMart=> SELECT * FROM col_logs;
    -[ RECORD 1 ]-+----------------------------------------
    version       | 0
    devicevendor  | ArcSight
    deviceproduct | ArcSight
    deviceversion | 6.0.3.6664.0
    signatureid   | agent:030
    name          | Agent [test] type [testalertng] started
    severity      | Low
    

计算键并生成 Flex 表视图

在此示例中,您使用 Flex 的 Helper 函数来为 logs Flex 表计算键并构建视图。

  1. 使用 compute_flextable_keys_and_build_view 函数计算键并填充从 logs Flex 表中生成的视图:

    => SELECT compute_flextable_keys_and_build_view('logs');
                                compute_flextable_keys_and_build_view
    -------------------------------------------------------------------------------------
     Please see public.logs_keys for updated keys
    The view public.logs_view is ready for querying
    (1 row)
    
  2. 查询 logs_keys 表以了解函数从示例 CEF 数据中计算的内容:

    => SELECT * FROM logs_keys;
           key_name       | frequency | data_type_guess
    ----------------------+-----------+-----------------
     c6a4                 |         1 | varchar(60)
     c6a4label            |         1 | varchar(36)
     categoryobject       |         1 | varchar(50)
     categoryoutcome      |         1 | varchar(20)
     categorysignificance |         1 | varchar(20)
     cs2                  |         1 | varchar(84)
     cs2label             |         1 | varchar(44)
     deviceproduct        |         1 | varchar(20)
     deviceversion        |         1 | varchar(24)
     devicezoneuri        |         1 | varchar(180)
     dvchost              |         1 | varchar(20)
     version              |         1 | varchar(20)
     ahost                |         1 | varchar(20)
     art                  |         1 | varchar(26)
     at                   |         1 | varchar(22)
     cat                  |         1 | varchar(28)
     catdt                |         1 | varchar(36)
     devicevendor         |         1 | varchar(20)
     dtz                  |         1 | varchar(32)
     dvc                  |         1 | varchar(24)
     filetype             |         1 | varchar(20)
     mrt                  |         1 | varchar(26)
     _cefver              |         1 | varchar(20)
     agentzoneuri         |         1 | varchar(180)
     agt                  |         1 | varchar(24)
     aid                  |         1 | varchar(50)
     atz                  |         1 | varchar(32)
     av                   |         1 | varchar(24)
     categorybehavior     |         1 | varchar(28)
     categorydevicegroup  |         1 | varchar(24)
     deviceseverity       |         1 | varchar(20)
     eventid              |         1 | varchar(20)
     name                 |         1 | varchar(78)
     rt                   |         1 | varchar(26)
     severity             |         1 | varchar(20)
     signatureid          |         1 | varchar(20)
    (36 rows)
    
  3. 查询来自 logs_view 的几列:

    => \x
    Expanded display is on.
    VMart=> select version, devicevendor, deviceversion, name, severity, signatureid
      from logs_view;
    -[ RECORD 1 ]-+----------------------------------------
    version       | 0
    devicevendor  | ArcSight
    deviceversion | 6.0.3.6664.0
    name          | Agent [test] type [testalertng] started
    severity      | Low
    signatureid   | agent:030
    

使用 fcefparser 的 delimiter 参数

在此示例中,您使用 fcefparser delimiter 参数查询加利福尼亚州、新墨西哥州和亚利桑那州的事件。

  1. 创建一个新的列式表 CEFData3

    => CREATE TABLE CEFData3(eventId INT, location VARCHAR(20));
    CREATE TABLE
    
  2. 使用 delimiter=',' 参数,将一些 CEF 数据加载到表中:

    => COPY CEFData3 FROM stdin PARSER fcefparser(delimiter=',');
    Enter data to be copied followed by a newline.
    End with a backslash and a period on a line by itself.
    >> eventId=1,location=California
    >> eventId=2,location=New Mexico
    >> eventId=3,location=Arizona
    >> \.
    
  3. 查询表:

    
    => SELECT eventId, location FROM CEFData3;
     eventId |  location
    ---------+------------
           1 | California
           2 | New Mexico
           3 | Arizona
    (3 rows)
    

6 - 复杂类型

表可以包含复杂类型的列,包括嵌套的复杂类型。您可以在原生和外部表(包括 Flex 表)中使用 ROW (结构体)、ARRAYSET 类型。集合仅限于标量类型的一维集合。可用于外部表的 MAP 类型有限,但您可以改用 ARRAY 和 ROW 来表示映射。选定的解析器支持加载具有复杂类型的数据。

您可以为 ARRAY 和 ROW 类型的异构组合定义列:包含数组字段的结构体或结构体数组。这些类型可以嵌套的最大嵌套深度为 100。

原生表存在的限制

原生表中使用的复杂类型除了其参考页面上列出的个别类型的限制外,还有一些限制:

  • 原生表必须至少有一个基本类型或原生数组(基本类型的一维数组)的列。如果一个 Flex 表有真实的列,它也必须至少有一个满足这个限制的列。

  • 复杂类型列不能在 ORDER BY 或 PARTITION BY 子句中使用,也不能用作 FILLER 列。

  • 复杂类型列不能有 约束

  • 返回复杂类型的表达式不能用作投影列,并且投影不能按复杂类型的列进行分段或排序。

  • 具有复杂类型列的表不能使用 DEFAULT 和 SET USING。

有关其他限制,请参阅 CREATE TABLEALTER TABLE

从数据派生表定义

可以使用 INFER_TABLE_DDL 函数检查 Parquet、ORC、JSON 或 Avro 数据,并为表定义生成一个起点。此函数返回 CREATE TABLE 语句,它可能需要进一步编辑。对于函数无法推断数据类型的列,该函数会将类型标记为未知并发出警告。对于 VARCHAR 和 VARBINARY 列,可能需要调整长度。请始终查看函数返回的语句,但特别是对于具有很多列的表,使用该函数可以节省时间和精力。

Parquet、ORC 和 Avro 文件包含架构信息,但 JSON 文件并不包含。对于 JSON,该函数检查原始数据以生成一个或多个候选表定义。在以下示例中,两个输入文件的菜单列结构不同:

=> SELECT INFER_TABLE_DDL ('/data/*.json'
    USING PARAMETERS table_name='restaurants', format='json',
max_files=3, max_candidates=3);
WARNING 0:  This generated statement contains one or more float types which might lose precision
WARNING 0:  This generated statement contains one or more varchar/varbinary types which default to length 80

                INFER_TABLE_DDL
------------------------------------------------------------------------
 Candidate matched 1/2 of total files(s):
  create table "restaurants"(
    "cuisine" varchar,
    "location_city" Array[varchar],
    "menu" Array[Row(
      "item" varchar,
      "price" float
    )],
    "name" varchar
  );
Candidate matched 1/2 of total files(s):
  create table "restaurants"(
    "cuisine" varchar,
    "location_city" Array[varchar],
    "menu" Array[Row(
      "items" Array[Row(
        "item" varchar,
        "price" numeric
      )],
      "time" varchar
    )],
    "name" varchar
  );

(1 row)

强类型的替代方案

尽管您可以完全指定表示 ROW 和 ARRAY 类型的任意组合的列,但在某些情况下您可能更喜欢更灵活的方法。如果数据包含具有数百个字段的结构体,而您只需要其中几个字段,则您可能更愿意在查询时仅提取这几个字段,而不是定义所有字段。同样,如果数据结构可能发生更改,您可能更愿意推迟完全指定复杂类型。您可以使用可变列来替代完全指定复杂列的结构。这与用于 Flex 表的方法相同,该表中的所有数据最初都会加载到一个二进制列中,然后根据需要从该位置实体化。有关使用此方法的详细信息,请参阅可变复杂类型

6.1 - 结构体

列可以包含用于存储(类型化)属性-值对的结构体。例如,地址列可以使用带有街道地址、城市/州或省和邮政编码字符串的结构体,例如 { "street":"150 Cambridgepark Dr.", "city":"Cambridge MA", "postalcode":"02140"}。(这是一种 Hive 显示格式,而不是字面意思上存储在数据文件中的内容。)结构体字段可以是基元类型或其他复杂类型。

使用 ROW 表达式定义结构体列。在以下示例中,数据包含客户姓名、地址和帐号列,而地址是数据中的结构体。您在 Vertica 中声明的类型必须与加载到其中的数据类型兼容。

=> CREATE TABLE customers (
    name VARCHAR,
    address ROW(street VARCHAR, city VARCHAR, zipcode INT),
    accountID INT);

在 ROW 中,您可以使用与为列指定字段及数据类型相同的语法来指定字段及其数据类型。出于查询目的,Vertica 会将 ROW 视为单个列。

结构体可以包含其他结构体。在以下示例中,员工拥有各种个人信息,包括本身就是结构体的地址。

=> CREATE TABLE employees(
    employeeID INT,
    personal ROW(
      name VARCHAR,
      address ROW(street VARCHAR, city VARCHAR, zipcode INT),
      taxID INT),
    department VARCHAR);

结构体可以包含基元类型、数组或结构体的数组。

=> CREATE TABLE customers(
  name VARCHAR,
  contact ROW(
    street VARCHAR,
    city VARCHAR,
    zipcode INT,
    email ARRAY[VARCHAR]
  ),
  accountid INT );

在定义外部表时,Vertica 要求表的定义与外部数据的架构相匹配。例如,对于上面员工示例中使用的数据,以下定义是错误的:

=> CREATE EXTERNAL TABLE employees(
    employeeID INT,
    personal ROW(
      name VARCHAR,
      address ROW(street VARCHAR, city VARCHAR),
      zipcode INT,
      taxID INT),
    department VARCHAR)
  AS COPY FROM '...' PARQUET;
ERROR 9151: Datatype mismatch [...]

数据包含具有三个字段(街道、城市、邮政编码)的地址结构体,因此外部表也必须使用具有三个字段的 ROW。将 ROW 更改为具有两个字段并将其中一个字段提升为父 ROW 是不匹配的。每个 ROW 都必须匹配,如果结构体嵌套在数据中,则完整的结构必须匹配。

对于原生表,您可以指定要从数据中加载的列,因此您无需使所有列都匹配。对于加载的列,表的定义必须与数据文件中的架构相匹配。

处理 null 值

如果结构体存在但字段值为 null,Vertica 会在 ROW 中将 NULL 作为其值。所有字段均为 null 的结构体将被视为具有 null 字段的 ROW。如果结构体本身为 null,Vertica 会将 ROW 读取为 NULL。

查询

请参阅行(结构)

限制

ROW 列有几个限制:

  • 最大嵌套深度为 100。

  • Vertica 表最多支持 9800 个列和字段。不计算 ROW 本身,只计算其字段。

  • ROW 列不能使用任何约束(例如 NOT NULL)或默认值。

  • ROW 字段不能是 auto_increment 或 setof。

  • ROW 定义必须至少包含一个字段。

  • “Row”是 ROW 定义中的保留关键字,但允许作为表或列的名称。

  • 不能使用 ALTER TABLE...ALTER COLUMN 修改 ROW 列。

  • 包含 ROW 列的表也不能包含标识、自动增量、默认、SET USING 或序列列。

6.2 - 数组

列可以包含数组,这些数组存储相同类型元素的有序列表。例如,地址列可以使用字符串数组来存储个人可能拥有的多个地址,例如 ['668 SW New Lane', '518 Main Ave', '7040 Campfire Dr']

数组有两种类型:

  • 原生数组:基元类型的一维数组。

  • 非原生数组:所有其他受支持的数组,包括含有其他数组(多维数组)或结构体 (ROW) 的数组。非原生数组存在一些使用限制

使用 ARRAY 类型定义数组列,以指定其元素的类型(基元类型、ROW(结构体)或数组):

=> CREATE TABLE orders
   (orderkey    INT,
    custkey     INT,
    prodkey     ARRAY[VARCHAR(10)],
    orderprices ARRAY[DECIMAL(12,2)],
    orderdate   DATE
   );

如果数组是多维数组,则将其表示为包含数组的数组:

ARRAY[ARRAY[FLOAT]]

查询

请参阅数组和集(集合)

限制

  • 原生数组仅支持原始类型的数据,例如 int、UUID 等。

  • 强制执行数组维数。列不能包含不同维度的数组。例如,包含三维数组的列只能包含其他三维数组;它不能同时包含一个一维数组。但是,一列中的数组大小可能不同,其中一个数组可以包含四个元素,而另一个数组包含十个元素。

  • 如果指定了数组边界,则对所有加载或更改数据的操作强制执行。无界数组可能包含与分配的二进制大小一样多的元素。

  • 数组具有最大二进制大小。如果在定义数组时未设置此大小,则使用默认值。

  • 数组不支持 LONG 类型(如 LONG VARBINARY 或 LONG VARCHAR)或用户定义类型(如 Geometry)。

6.3 - 可变复杂类型

定义表时,可以使用强类型复杂类型来充分描述结构体和数组的任意组合。但是,有时您可能不希望:

  • 如果数据包含具有大量字段的结构体,而在查询中只需要使用其中几个字段,则不必枚举表 DDL 中的剩余部分。此外,如果完全指定复杂类型,则一组深度嵌套的结构体可能会超过表的嵌套限制。

  • 如果数据架构仍在演变,您可以推迟最终确定强类型 DDL。

  • 如果预计在数据中引入新字段,您可以使用可变类型来发现这些字段。另一方面,具有强类型的表会静默忽略这些值。有关使用可变类型发现新字段的示例,请参阅强类型和可变类型

可变类型是一种将复杂或非结构化数据作为二进制 blob 存储在一个列中的方法,它允许访问该数据的各个元素。这与 Vertica 用于 Flex 表的方法相同,Flex 表支持加载非结构化或半结构化数据。在 Flex 表中,源中的所有数据都会加载到一个名为 __raw__ 的 VMap 列中。从此列中,您可以实体化其他列(例如 JSON 数据中的特定字段),或在查询中使用特殊查找函数直接从 __raw__ 列中读取值。

Vertica 对复杂类型会使用类似的方法。您可以使用表定义中的 ROWARRAY 类型来充分描述类型,也可以将复杂类型视为可变类型而不充分描述它。选择以这种方式进行处理的每种复杂类型都将成为其自己的 Flex 样式列。您不必像 Flex 表中那样使用一列包含所有数据;相反,您可以将任何复杂类型的列视为一个类似 Flex 的列,而无论它嵌套的其他类型有多深。

定义可变列

要使用可变复杂类型,请将列声明为 LONG VARBINARY。您可能还需要在解析器中设置其他参数,如解析器文档中所述。

下面我们来探讨包含餐厅表和以下列的 Parquet 文件:

  • name:varchar

  • cuisine 类型:varchar

  • location(城市):数组[varchar]

  • menu:结构体数组,每个结构体都有选项名称和价格

这些数据包含两个复杂的列:location(数组)和 menu(结构体数组)。以下示例将使用 LONG VARBINARY 将这两个列定义为可变列:

=> CREATE EXTERNAL TABLE restaurants(name VARCHAR, cuisine VARCHAR,
        location_city LONG VARBINARY, menu LONG VARBINARY)
    AS COPY FROM '/data/rest*.parquet'
    PARQUET(allow_long_varbinary_match_complex_type='True');

allow_long_varbinary_match_complex_type 参数特定于 Parquet 解析器。如果将任何列定义为可变类型,则该列为必需列。如果不使用此参数,Vertica 会尝试将表中的 LONG VARBINARY 声明与 Parquet 文件中的 VARBINARY 列相匹配,改为查找复杂类型,然后报告数据-类型不匹配。

您不需要将所有复杂列都视为可变类型。以下定义同样有效:

=> CREATE EXTERNAL TABLE restaurants(
            name VARCHAR, cuisine VARCHAR,
            location_city ARRAY[VARCHAR,50],
            menu LONG VARBINARY)
    AS COPY FROM '/data/rest*.parquet'
    PARQUET(allow_long_varbinary_match_complex_type='True');

对于许多常见数据格式,可以使用 INFER_TABLE_DDL 函数从数据文件中派生表定义。此函数几乎在所有情况下都对复杂类型使用强类型。

查询可变列

可变列将存储为 LONG VARBINARY,因此直接选择它们会产生无用的结果。相反,应使用 Flex 映射函数从这些列中提取值。MAPTOSTRING 函数会将复杂类型转换为 JSON,如以下示例所示:

=> SELECT name, location_city, MAPTOSTRING(menu) AS menu FROM restaurants;
       name        |       location_city        |                    menu
-------------------+---------+------------------+----------------------------------------
 Bob's pizzeria    | ["Cambridge","Pittsburgh"] | {
    "0": {
        "item": "cheese pizza",
        "price": "$8.25"
    },
    "1": {
        "item": "spinach pizza",
        "price": "$10.50"
    }
}
 Bakersfield Tacos | ["Pittsburgh"]             | {
    "0": {
        "item": "veggie taco",
        "price": "$9.95"
    },
    "1": {
        "item": "steak taco",
        "price": "$10.95"
    }
}
(2 rows)

menu 列是结构体数组。请注意,输出是一组键/值对,其中键为数组索引。Bob's Pizzeria 的菜单上有两个选项,每个值都是结构体。第一个选项 ("0") 是结构体,"item" 值为 "cheese pizza","price" 为 "$8.25"。

您可以使用键来访问特定值。以下示例将从每个餐厅中选择第一个菜单项。请注意,所有键都是字符串,甚至是数组索引:

=> SELECT name, location_city, menu['0']['item'] AS item, menu['0']['price'] AS price FROM restaurants;
       name        |       location_city        |     item     | price
-------------------+----------------------------+--------------+-------
 Bob's pizzeria    | ["Cambridge","Pittsburgh"] | cheese pizza | $8.25
 Bakersfield Tacos | ["Pittsburgh"]             | veggie taco  | $9.95
(2 rows)

您可以在子查询中使用 MAPITEMS 函数来激增可变类型,而不是访问特定索引,如以下示例所示:

=>  SELECT name, location_city, menu_items['item'], menu_items['price']
    FROM (SELECT mapitems(menu, name, location_city) OVER(PARTITION BEST)
         AS (indexes, menu_items, name, location_city)
    FROM restaurants) explode_menu;
       name        |       location_city        |  menu_items   | menu_items
-------------------+----------------------------+---------------+------------
 Bob's pizzeria    | ["Cambridge","Pittsburgh"] | cheese pizza  | $8.25
 Bob's pizzeria    | ["Cambridge","Pittsburgh"] | spinach pizza | $10.50
 Bakersfield Tacos | ["Pittsburgh"]             | veggie taco   | $9.95
 Bakersfield Tacos | ["Pittsburgh"]             | steak taco    | $10.95
(4 rows)

有关 Flex 映射函数的完整列表,请参阅 Flex 数据函数

JSON 和 avro 可变类型

JSON 和 Avro 的解析器支持复杂类型的可变类型和强类型。当使用可变复杂类型或加载到 Flex 表中时,请使用 flatten_mapsflatten_arrays 参数来控制解析器处理复杂数据的方式。对于强类型的复杂类型,这些解析器会忽略这些参数。

以下示例演示了如何使用可变复杂类型。下面我们来探讨包含以下数据的 JSON 文件:

{
    "name" : "Bob's pizzeria",
    "cuisine" : "Italian",
    "location_city" : ["Cambridge", "Pittsburgh"],
    "menu" : [{"item" : "cheese pizza", "price" : "$8.25"},
              {"item" : "spinach pizza", "price" : "$10.50"}]
}
{
    "name" : "Bakersfield Tacos",
    "cuisine" : "Mexican",
    "location_city" : ["Pittsburgh"],
    "menu" : [{"item" : "veggie taco", "price" : "$9.95"},
              {"item" : "steak taco", "price" : "$10.95"}]
}

创建一个表(使用 LONG VARBINARY 表示可变复杂类型),然后加载指定这些参数的数据:

=> CREATE TABLE restaurant(name VARCHAR, cuisine VARCHAR,
        location_city LONG VARBINARY, menu LONG VARBINARY);

=> COPY restaurant FROM '/data/restaurant.json'
   PARSER FJSONPARSER(flatten_maps=false, flatten_arrays=false);

您可以使用 Flex 函数和直接访问(通过索引)来返回可读值:

=> SELECT MAPTOSTRING(location_city), MAPTOSTRING(menu) FROM restaurant;
                   maptostring                   |             maptostring
-------------------------------------------------+--------------------------------------------------------
 {
    "0": "Cambridge",
    "1": "Pittsburgh"
} | {
    "0": {
        "item": "cheese pizza",
        "price": "$8.25"
    },
    "1": {
        "item": "spinach pizza",
        "price": "$10.50"
    }
}
 {
    "0": "Pittsburgh"
}                       | {
    "0": {
        "item": "veggie taco",
        "price": "$9.95"
    },
    "1": {
        "item": "steak taco",
        "price": "$10.95"
    }
}
(2 rows)

=> SELECT menu['0']['item'] FROM restaurant;
     menu
--------------
 cheese pizza
 veggie taco
(2 rows)

此示例中显示的 COPY 语句将 flatten_maps 设置为 false。如果没有该更改,复杂列的键将无法按预期工作,因为记录和数组键将在顶层“展平”。查询菜单 ['0']['item'] 不会产生任何结果。相反,查询展平值,如下例所示:

=> SELECT menu['0.item'] FROM restaurant;
     menu
--------------
 veggie taco
 cheese pizza
(2 rows)

展平指令适用于整个 COPY 语句。您不能展平某些列而不展平其他列,或者防止展平本身位于展平 Flex 表中的复杂列中的值。因为可变复杂类型和强类型的复杂类型需要不同的值来进行展平,所以不能在同一个加载操作中组合强类型和可变复杂类型。

6.4 - 复杂类型的系统表

所有复杂类型的信息都记录在 COMPLEX_TYPES 系统表中。您必须对使用某种类型的外部表具有读取权限,才能在此系统表中查看其条目。TYPES 系统表中未显示复杂类型。

对于 ROW 类型,COMPLEX_TYPES 中的每一行代表一个 ROW 的一个字段。字段名称是表定义中使用的名称(如果存在),否则是生成的以 _field 开头的名称。每行还包括其包含类型的(生成的)名称,即一个以 ct 开头的字符串。("CT" 代表“复杂类型”。)

以下示例定义一个外部表,然后显示 COMPLEX_TYPES 中的类型:

=> CREATE EXTERNAL TABLE warehouse(
    name VARCHAR, id_map MAP<INT,VARCHAR>,
    data row(record INT, total FLOAT, description VARCHAR(100)),
    prices ARRAY[INT], comment VARCHAR(200), sales_total FLOAT, storeID INT)
  AS COPY FROM ... PARQUET;

=> SELECT type_id,type_kind,type_name,field_id,field_name,field_type_name,field_position
    FROM COMPLEX_TYPES ORDER BY type_id,field_name;

      type_id      | type_kind |       type_name       | field_id | field_name  | field_type_name | field_position
-------------------+-----------+-----------------------+----------+-------------+-----------------+----------------
 45035996274278280 | Map       | _ct_45035996274278280 |        6 | key         | int             |              0
 45035996274278280 | Map       | _ct_45035996274278280 |        9 | value       | varchar(80)     |              1
 45035996274278282 | Row       | _ct_45035996274278282 |        9 | description | varchar(80)     |              2
 45035996274278282 | Row       | _ct_45035996274278282 |        6 | record      | int             |              0
 45035996274278282 | Row       | _ct_45035996274278282 |        7 | total       | float           |              1
 45035996274278284 | Array     | _ct_45035996274278284 |        6 |             | int             |              0
(6 rows)

此表显示表中定义的两种 ROW 类型的字段。当一个 ROW 包含另一个 ROW 时(就像这里嵌套地址字段的情况一样),field_type_name 列使用包含的 ROW 的生成名称。去掉前导 "ct" 后的数字用作 field_id。

7 - 处理非 UTF-8 输入

Vertica 支持加载 Unicode UTF-8 格式的数据文件。可以加载与 UTF-8 兼容的 ASCII 数据。字符集(如 ISO 8859-1 (Latin1))与 UTF-8 不兼容,因此不直接受支持。

如果您的数据不符合 UTF-8 标准,则可以在加载期间修改数据,也可以在加载前转换数据文件。

检查数据格式

在从文本文件加载数据之前,可以使用多个 Linux 工具确保数据为 UTF-8 格式。file 命令可报告任何文本文件的编码。例如:

$ file Date_Dimension.tbl
Date_Dimension.tbl: ASCII text

file 命令可指示 ASCII 文本,即使文件包含多字节字符也是如此。

若要检查 ASCII 文件中的多字节字符,请使用 wc 命令。例如:

$ wc Date_Dimension.tbl
  1828   5484 221822 Date_Dimension.tbl

如果 wc 命令返回错误(例如 Invalid or incomplete multibyte or wide character),说明数据文件正在使用不兼容的字符集。

此示例显示了两个不是 UTF-8 数据文件的文件:

$ file data*
data1.txt: Little-endian UTF-16 Unicode text
data2.txt: ISO-8859 text

结果指示没有一个文件为 UTF-8 格式。

加载时转换数据

您可以在加载期间移除或替换文本数据中的非 UTF-8 字符。默认情况下,MAKEUTF8 函数会移除此类字符,或者您可以指定替换字符串。

以下示例显示了如何在加载期间使用该函数:将原始数据加载到 orig_name 列,然后将转换后的数据加载到 name 列。通常,您会为原始值使用 FILLER 列,而不是将该列添加到表定义中;此示例会添加该列以并排显示差异。

=> CREATE TABLE people (orig_name VARCHAR, name VARCHAR);
CREATE TABLE

=> COPY people (orig_name, name AS MAKEUTF8(orig_name)) FROM ...;
 Rows Loaded
-------------
           8
(1 row)

=> SELECT * FROM people;
orig_name |  name
----------+--------
  Dáithí    | Dith
  Fíona     | Fona
  Móirín     | Mirn
  Róisín     | Risn
  Séamus    | Samus
  Séan      | San
  Tiarnán  | Tiarnn
  Áine     | ine
(8 rows)

有关转换数据的常规信息,请参阅在加载期间转换数据

在加载数据之前转换文件

要在将文件加载到 Vertica 之前转换文件,请使用 iconv UNIX 命令。例如,若要转换以上示例中的 data2.txt 文件,请使用 iconv 命令,如下所示:

$ iconv -f ISO88599 -t utf-8 data2.txt > data2-utf8.txt

有关详细信息,请参阅 fileiconv 手册页。

在加载数据之后检查 UTF-8 合规性

加载数据之后,使用 ISUTF8 函数验证表中的所有字符串数据是否为 UTF-8 格式。例如,如果将数据加载到具有名为 name 的 VARCHAR 列的名为 people 的表中,可以使用此语句验证所有字符串是否为 UTF-8 编码:

=> SELECT name FROM people WHERE NOT ISUTF8(name);

如果所有字符串都为 UTF-8 格式,查询不应返回任何行。

8 - 在加载期间转换数据

为增强数据库的一致性并减少使用脚本转换源数据的需要,您可以在加载过程中使用表达式转换数据。通过在加载时转换数据,您可以计算要插入到目标列中的值,这些值可以来自其他列,也可以来自您作为 FILLER 列加载的数据中的值(请参阅根据数据文件列派生表列)。您可以将要加载的数据转换为标量类型和原生数组的列,但不能转换为其他复杂类型

例如,您的文本数据可能与 UTF-8(Vertica 期望采用的编码)不兼容。您可以在加载过程中使用 MAKEUTF8 函数移除或替换非 UTF-8 字符,如加载时转换数据中所示。或者,您可能希望从单个输入日期中提取与日、月和年对应的字段。

在加载期间转换数据时,您可以正常将数据加载到列中,然后在表达式中使用该列填充另一列。COPY 语句必须始终至少包含一个已解析的列,该列可以是 FILLER 列。您可以在 COPY 语句中穿插已解析的列和已计算的列。

以下示例将从单个输入日期列中提取与日、月和年对应的列:

=> CREATE TABLE purchases
    (id INT, year VARCHAR(10), month VARCHAR(10), day VARCHAR(10), ts TIMESTAMP);

=> COPY purchases (id, year AS TO_CHAR(ts,'YYYY'),
                   month AS TO_CHAR(ts,'MM'), day AS TO_CHAR(ts, 'DD'),
                   ts FORMAT 'YYYY-MM-DD') FROM STDIN;
Enter data to be copied followed by a newline.
End with a backslash and a period on a line by itself.
>> 1943|2021-03-29
>> 1256|2021-03-30
>> 1512|2021-03-31
>> \.

=> SELECT * FROM purchases;
  id  | year | month | day |         ts
------+------+-------+-----+---------------------
 1256 | 2021 | 03    | 30  | 2021-03-30 00:00:00
 1512 | 2021 | 03    | 31  | 2021-03-31 00:00:00
 1943 | 2021 | 03    | 29  | 2021-03-29 00:00:00
(3 rows)

输入数据有两列:idts(时间戳)。COPY 语句会使用 FORMAT 选项来指定时间戳列的格式。TO_CHAR 函数会使用该格式信息来提取 yearmonthday 列。

在 COPY 语句中使用表达式

COPY 语句中的表达式可以简单到只有一列,也可以更加复杂,例如使用多列的选择语句。一个表达式可以指定多个列,多个表达式可以引用同一个已解析的列。您可以对所有受支持数据类型的列使用表达式。

COPY 表达式可以使用许多 SQL 函数、运算符、常量、NULL 和注释,包括以下函数:

要求和限制:

  • COPY 表达式不能使用 SQL 元函数、分析函数、聚合函数或已计算的列。

  • 对于已计算的列,您必须在 COPY 语句表达式中列出所有已解析的列。请勿在已计算列的源数据中指定 FORMAT 或 RAW。

  • 表达式的返回数据类型必须可以转换到目标列的返回数据类型。已解析列参数也会进行转换,以与表达式匹配。

处理表达式错误

COPY 语句中的表达式错误是 SQL 错误。因此,它们的处理方式与解析错误不同。当发生解析错误时,COPY 会拒绝该行并将其添加到拒绝数据文件或表中。COPY 还会将拒绝行的原因添加到异常文件或拒绝数据表中。例如,COPY 解析不会隐式转换数据类型。如果正在加载的数据与列类型之间出现类型不匹配(例如,尝试将文本值加载到 FLOAT 列中),COPY 将拒绝该行并继续处理数据。

如果 COPY 语句中的表达式出现错误,则默认情况下整个加载都会失败。例如,如果 COPY 语句使用函数表达式,并且该表达式中存在语法错误,则整个加载将回退。所有 SQL 错误(包括由回退 COPY 引起的错误)都存储在 Vertica 日志文件中。但是,与解析拒绝和异常消息不同,SQL 表达式错误很简短,可能需要进一步研究。

您可以让 COPY 处理转换表达式中的错误,例如解析错误。将拒绝的行添加到同一个文件或表中,并将异常添加到同一个异常文件或表中。要启用此行为,请将 CopyFaultTolerantExpressions 配置参数设置为 1。(请参阅常规参数。)

加载含有表达式拒绝的数据可能比加载相同数量的解析拒绝更慢。如果数据存在一些错误行,则需启用表达式拒绝,以允许加载剩余数据。如果您担心完成包含许多错误行的加载所需的时间较长,请使用 REJECTMAX 参数设置限值。如果 COPY 找到 REJECTMAX 个以上的错误行,则会中止并回退加载。

有关管理拒绝数据的详细信息,请参阅处理杂乱的数据

根据数据文件列派生表列

加载数据时,源数据可能包含目标表中不存在的一个或多个列。或者,源和目标表拥有匹配的列,但是您想从目标表中忽略一个或多个源列。

使用 FILLER 参数能够标识源数据所在的列,COPY 可以忽略源数据或将其用于计算加载到目标表的新值。需要满足以下要求:

  • 定义 FILLER 参数数据类型,以便它与源数据兼容。例如,确保在目标表中定义 VARCHAR,以便其长度可以包含所有源数据;否则,数据可能会被截断。您可以通过在 COPY 语句中多次使用 FILLER 参数来指定多个 FILLER 列。

  • FILLER 列的名称不能与目标表中任何列的名称匹配。

在以下示例中,该表包含名字、姓氏和全名列,但正在加载的数据包含名字、中间名和姓氏列。COPY 语句读取所有源数据,但只加载名字和姓氏的源列。它通过连接每个源数据列(包括中间名)来构造全名数据。中间名被读取为 FILLER 列,因此可以在串联中使用,但在其他情况下会被忽略。(中间名没有表格列。)

=> CREATE TABLE names(first VARCHAR(20), last VARCHAR(20), full VARCHAR(60));
CREATE TABLE
=> COPY names(first,
              middle FILLER VARCHAR(20),
              last,
              full AS first||' '||middle||' '||last)
      FROM STDIN;
Enter data to be copied followed by a newline.
End with a backslash and a period on a line by itself.
>> Marc|Gregory|Smith
>> Sue|Lucia|Temp
>> Jon|Pete|Hamilton
>> \.
=> SELECT * from names;
 first |   last   |        full
-------+----------+--------------------
 Jon   | Hamilton | Jon Pete Hamilton
 Marc  | Smith    | Marc Gregory Smith
 Sue   | Temp     | Sue Lucia Temp
(3 rows)

9 - 分配加载

Vertica 可以在多个数据库节点之间拆分加载数据的工作,从而利用并行度来加快操作速度。如何做到这一点取决于数据的位置以及解析器支持的并行度类型。

在对所有节点可用的共享存储中找到要加载的数据时,Vertica 分配加载的效率最高。但是,有时数据仅在您必须指定的特定节点上可用。

加载并行度的类型

Vertica 支持多种类型的并行度。所有内置解析器都支持分布式加载,某些解析器支持分摊加载和协作解析。各个解析器的参考页面均包含它们支持的加载并行度类型。

加载并行度默认处于启用状态,但可以禁用。常规参数中描述了本节中指定的配置参数。

分布式加载

Vertica 会将多文件加载中的文件分配给多个节点以并行加载,而不是在单个节点上加载所有文件。如果所有节点都可以访问文件且您不限制参与节点,则 Vertica 会自动分配加载。

分摊加载和协作解析都需要可以在记录边界处拆分的输入。不同之处在于,协作解析会执行顺序扫描以查找记录边界,而分摊加载会先跳(寻找)至给定位置,然后再执行扫描。某些格式(例如通用 XML)不支持寻找。

分摊加载

在分摊加载中,Vertica 会将单个大型文件或其他单个源拆分成多个段(部分),然后分配给多个节点以进行并行加载。分摊加载会根据每个节点上的可用节点和核心在计划时拆分加载。

要使用分摊加载,您必须确保所有参与的数据库节点都可以访问源。分摊加载通常与分布式文件系统结合使用。

对于支持分摊加载的解析器,分摊加载默认处于启用状态。要将其禁用,请将 EnableApportionLoad 配置参数设置为 0。

协作解析

默认情况下,Vertica 会在一个数据库节点上的单个线程中解析数据源。如果解析器支持协作解析,则节点将改为使用多个线程来并行进行解析。协作解析会根据线程的调度方式在执行时拆分加载。

对于支持协作解析的解析器,协作解析默认处于启用状态。要将其禁用,请将 EnableCooperativeParse 配置参数设置为 0。

在特定节点上加载

您可以使用以下任一方法指示哪个或哪些节点应解析输入路径:

  • 节点名称: ON 节点

  • 一组节点: ON nodeset(请参阅指定分布式文件加载

  • ON ANY NODE (HDFS 和云路径的默认值)

  • ON EACH NODE

使用 ON ANY NODE 子句表示源路径在所有节点上都可用。如果指定此子句,则 COPY 会解析来自群集中任何节点的文件。如果路径包含 glob,则 COPY 会在启动程序节点上展开 glob。 ON ANY NODE 是除 Linux 之外的所有文件系统的默认值。

使用 ON nodeset 子句表示源文件位于所有指定的节点上。如果指定此子句,则 COPY 会打开相应文件并从集合中的任何节点对其进行解析。请确保指定的源文件可用且在每个适用的群集节点上可供访问。

ON nodeON ANY NODE 会加载每个文件一次,以选择一个节点来执行加载。相反,如果每个节点上的数据不同,而您想加载所有数据且路径相同,请使用 ON EACH NODE

=> COPY myTable FROM '/local_export/*.dat' ON EACH NODE;

如果路径在所有节点上都无效,则 COPY 会加载有效路径并生成警告。如果路径是共享位置,则就 ON ANY NODE 而言,COPY 仅加载路径一次。

当您想从每个节点上的相同路径加载且文件不同时,请使用 ON EACH NODE。例如,如果您拥有特定于计算机的数据(例如系统事件日志),或者如果操作单独在每个节点上写入数据,则可以使用此子句来加载所有数据。

如果要加载的数据位于客户端上,请使用 COPY FROM LOCAL 而不是指定节点。所有本地文件都使用每个 COPY 语句按顺序进行加载和解析,因此您不能使用 LOCAL 选项执行并行加载。

指定分布式文件加载

您可以将多文件加载中的单个文件定向到特定节点:

=> COPY t FROM '/data/file1.dat' ON v_vmart_node0001, '/data/file2.dat' ON v_vmart_node0002;

您可以使用 glob(通配符扩展)通过 ON ANY NODE 选项指定一组文件:

=> COPY t FROM '/data/*.dat' ON ANY NODE;

您可以限制参与分摊加载的节点。如果您需要平衡多个并发加载,那么这样做非常有用。Vertica 会单独分摊每个加载;它不会考虑这些节点上可能正在进行的其他加载。因此,您可以通过自己管理分摊来潜在加快加载速度:

=> COPY t FROM '/data/big1.dat' ON (v_vmart_node0001, v_vmart_node0002, v_vmart_node0003),
        '/data/big2.dat' ON (v_vmart_node0004, v_vmart_node0005);

您可以为每个路径指定压缩类型。但是,由于文件压缩是一种筛选器,因此不能对压缩文件使用分摊加载。

指定具有源的分布式加载

此外,您还可以使用 COPY WITH SOURCE 来分摊加载。您可以使用用户定义的加载 (UDL) API 来创建源和解析器。如果源和解析器都支持分摊加载,并且设置了 EnableApportionLoad,则 Vertica 会尝试在节点之间拆分加载。

以下示例显示了您可以分摊的加载:

=> COPY t WITH SOURCE MySource() PARSER MyParser();

内置的分隔解析器支持分摊,因此您可以将它与用户定义的源结合使用:

=> COPY t WITH SOURCE MySource();

加载流数

虽然您可以加载的文件数量不受限制,但加载流的最佳数量取决于多个因素,包括:

  • 节点数

  • 物理和逻辑架构

  • 主机处理器

  • 内存

  • 磁盘空间

使用过多的加载流可能会耗尽或减少最佳查询处理所需的系统内存。有关配置加载流的建议,请参阅工作负载资源管理最佳实践

10 - 使用事务暂存加载

默认情况下,COPY 会自动提交自身和其他当前事务,加载临时表或查询外部表时除外。您可以通过使用 NO COMMIT 选项限定 COPY 语句来覆盖此行为。当您指定 NO COMMIT 时,Vertica 不会提交事务,直到您明确发出 COMMIT 语句为止。

您可以通过两种方式使用 COPY...NO COMMIT

  • 将多个 COPY 命令作为单个事务执行。

  • 在提交加载之前检查数据是否违反约束。

在同一个事务中合并多个 COPY 语句

当您在同一个事务中合并多个 COPY...NO COMMIT 语句时,Vertica 可以将所有操作的数据合并到更少的 ROS 容器中,从而更高效地执行操作。

例如,以下一组 COPY...NO COMMIT 语句会按顺序执行若干复制语句,然后再全部提交这些数据。这样,所有复制数据都会作为单个事务提交或回退。

COPY... NO COMMIT;
COPY... NO COMMIT;
COPY... NO COMMIT;
COPY X FROM LOCAL NO COMMIT;
COMMIT;

检查约束违规

如果在目标表中未强制执行约束,则 COPY 在加载数据时不会检查是否有约束违规。要对加载数据的约束违规进行故障排除,请使用 COPY...NO COMMITANALYZE_CONSTRAINTS。这样做可以让您在提交加载操作之前检测到约束违规,并在必要时回退操作。有关详细信息,请参阅检测约束违规

11 - 处理杂乱的数据

使用 COPY 加载数据有两个主要阶段:解析和加载。在解析过程中,如果 COPY 遇到错误,它会拒绝错误数据并继续加载数据。每当 COPY 无法解析某行数据时,便会创建拒绝数据。下面是一些可能导致拒绝行的解析器错误:

  • 解析器选项不受支持

  • 数据要加载到的表的数据类型不正确,包括集合成员的数据类型不正确

  • 所用解析器的上下文格式错误

  • 缺少分隔符

COPY 可以选择在加载阶段转换数据时拒绝数据并继续加载。此行为由配置参数控制。默认情况下,如果在加载阶段遇到错误,COPY 会中止加载。

一些可选参数可让您确定 COPY 处理拒绝的严格程度。例如,可让 COPY 在拒绝单行时就失败,也可以在加载失败前允许特定数量的拒绝。此部分介绍用于确定 COPY 如何处理被拒绝数据的参数。

保存拒绝行(REJECTED DATA 和 EXCEPTIONS)

COPY 语句自动将每个拒绝行的副本保存在 rejections 文件中。COPY 还将有关拒绝原因的相应解释保存在 exceptions 文件中。默认情况下,Vertica 将这两个文件保存在名为 CopyErrorLogs 的数据库编录子目录中,如本例中所示:

v_mart_node003_catalog\CopyErrorLogs\trans-STDIN-copy-from-rejected-data.1
v_mart_node003_catalog\CopyErrorLogs\trans-STDIN-copy-from-exceptions.1

您可以选择以其他两种方式之一保存 COPY 拒绝和异常:

  • 使用 REJECTED DATAEXCEPTIONS 选项将两个输出保存到您选择的位置。REJECTED DATA 记录拒绝的行,而 EXCEPTIONS 记录每行拒绝原因的描述。如果路径值是现有目录或以 '/' 结尾,或者加载包含多个源,则将文件写入该目录中。(如果该目录不存在,COPY 会创建该目录。)如果路径值是一个文件且写入多个文件,则 COPY 使用它作为文件前缀。

  • 使用 REJECTED DATA AS TABLE 选项。此选项将拒绝数据和异常描述写入同一个表。有关详细信息,请参阅将拒绝的数据保存到表中

如果将拒绝数据保存到表中,表文件则存储在数据子目录中。例如,在 Vmart 数据库安装中,拒绝数据表记录存储在 RejectionTableData 目录中,如下所示:

=> cd v_mart_node003_data\RejectionTableData\
=> ls
TABLE_REJECTED_RECORDS_"bg"_mytest01.example.-25441:0x6361_45035996273805099_1.1
TABLE_REJECTED_RECORDS_"bg"_mytest01.example.-25441:0x6361_45035996273805113_2.2
.
.
.
TABLE_REJECTED_RECORDS_"delimr"_mytest01.example.-5958:0x3d47_45035996273815749_1.1
TABLE_REJECTED_RECORDS_"delimr"_mytest01.example.-5958:0x3d47_45035996273815749_1.2

COPY LOCAL 拒绝数据

对于 COPY LOCAL 操作,如果您将 REJECTED DATAEXCEPTIONS 用于文件路径,则文件写入客户端。如果您希望拒绝在所有节点上可用,请使用 REJECTED DATA AS TABLE 而不是 REJECTED DATA

强制截断或拒绝行 (ENFORCELENGTH)

解析 CHAR、VARCHAR、BINARY 或 VARBINARY 数据时,行可能会超过目标表长度。默认情况下,COPY 会截断此类行,但不拒绝它们。使用 ENFORCELENGTH 选项拒绝超出目标表的行。

例如,如果将 'abc' 加载到指定为 VARCHAR(2) 的表列,COPY 则会将值截断成 'ab',然后再进行加载。使用 ENFORCELENGTH 选项加载同一行会导致 COPY 拒绝该行。

指定最大拒绝数 (REJECTMAX)

REJECTMAX 选项指定在加载失败前可以拒绝的最大逻辑记录数。拒绝行由批量加载过程中无法解析(或无法转换)成相应数据类型的数据组成。拒绝数据并不指示引用约束。有关使用约束以及在批量加载过程中强制执行约束的选项的信息,请参阅约束

当拒绝记录数等于 REJECTMAX 值时,加载将失败。如果未指定 REJECTMAX 值或该值为 0,则 COPY 允许的异常数无限制。

如果允许 COPY 在遇到转换错误时拒绝行然后继续,请考虑使用 REJECTMAX 限制影响。请参阅处理转换错误

处理转换错误

默认情况下,如果在执行转换时遇到错误,则 COPY 中止加载。这是默认设置,因为拒绝转换错误可能比拒绝解析错误成本更高。但是,有时您更愿意无论如何都加载数据并拒绝有问题的行,就像对待解析错误一样。

要让 COPY 将转换表达式中的错误视为解析错误,请将 CopyFaultTolerantExpressions 配置参数设置为 1。(请参阅常规参数。)在数据加载的表达式评估阶段,在转换期间被拒绝的行将被写入与解析期间被拒绝的行相同的目的地。使用 REJECTED DATAREJECTED DATA AS TABLE 指定输出位置。

如果数据包含一些错误行,您可能希望启用转换拒绝。通过启用这些拒绝,您可以加载大部分数据并继续。Vertica 建议在启用转换拒绝时使用 REJECTMAX

如果数据包含许多错误值,则加载非错误行的性能可能比解析器错误更差。

出现任何错误时中止数据加载 (ABORT ON ERROR)

使用 ABORT ON ERROR 选项是限制性最强的数据加载方式,因为不允许任何异常或拒绝。如果任何行遭拒,COPY 操作将停止。不会加载任何数据,而且 Vertica 回退命令。

如果您使用 ABORT ON ERROR 作为 CREATE EXTERNAL TABLE 语句的一部分,则每当查询引用外部表时都会使用该选项。违规错误保存在 COPY 异常或拒绝数据文件中。

了解行拒绝和回退错误

根据 COPY 遇到的错误类型,Vertica 将执行下列操作之一:

  • 拒绝违规行并将其他行加载到表中

  • 回退整个 COPY 语句,但不加载任何数据

COPY 无法解析包含以下任一项的行:

  • 不兼容数据类型

  • 缺失字段

  • 缺少分隔符

如果遇到以下任一条件,COPY 会回退语句:

  • 服务器端错误,如缺少内存

  • 违反主键或外键约束

  • 将 NULL 数据加载到 NOT NULL 列中

  • 转换错误(默认情况)

此示例演示当 Vertica 无法将行解析成所请求的数据类型时所发生的情况。例如,在下列 COPY 语句中,"a::INT + b::INT" 是一个 SQL 表达式,其中 ab 为衍生值:

=> CREATE TABLE t (i INT);
=> COPY t (a FILLER VARCHAR, b FILLER VARCHAR, i AS a::INT + b::INT)
   FROM STDIN;
Enter data to be copied followed by a newline.
End with a backslash and a period on a line by itself.
>> cat|dog
>> \.

Vertica 无法将行解析成所请求的数据类型,因而拒绝行:

ERROR 2827:  Could not convert "cat" from column "*FILLER*".a to an int8

如果 a 解析为 'cat'b 解析为 'dog',则下一个表达式 'cat'::INT + 'dog'::INT 将返回表达式评估程序错误:

=> SELECT 'cat'::INT + 'dog'::INT;
ERROR 3681:  Invalid input syntax for integer: "cat"

下列 COPY 语句也会回退,因为 Vertica 无法将行解析成所请求的数据类型:

=> COPY t (a FILLER VARCHAR, i AS a::INT) FROM STDIN;

在下列 COPY 语句中,Vertica 仅拒绝违规行但不回退语句。COPY 将 'cat' 直接解析为 INTEGER,而不是将 'cat' 行计算为 VARCHAR 类型。

=> COPY t (a FILLER INT, i AS a) FROM STDIN;

在以下示例中,转换错误被拒绝而不是中止加载。

=> ALTER DATABASE DEFAULT SET CopyFaultTolerantExpressions = 1;
ALTER DATABASE

=> CREATE TABLE sales (price INTEGER);
CREATE TABLE

=> COPY sales (f FILLER VARCHAR, price AS f::INT)
   FROM STDIN REJECTED DATA AS TABLE sales_rej;

=> COPY sales FROM STDIN REJECTED DATA AS TABLE sales_rej;
Enter data to be copied followed by a newline.
End with a backslash and a period on a line by itself.
>>1
>>2a
>>3
>>\.

=> SELECT * FROM sales;
 price
-------
     1
     3
(2 rows)

=> SELECT rejected_data, rejected_reason FROM sales_rej;
 rejected_data |                rejected_reason
---------------+-----------------------------------------------
 Tuple (2a)    | ERROR 2827:  Could not convert "2a" from column "*FILLER*".f to an int8
(1 row)

另请参阅

11.1 - 保存加载拒绝 (REJECTED DATA)

COPY 加载拒绝是由于解析器出现异常或者出现转换错误而未加载的数据行。默认情况下,如果您未指定包含拒绝数据的文件,则 COPY 会将包含拒绝数据的文件保存到以下位置:

catalog_dir/CopyErrorLogs/target_table-source-copy-from-rejected-data.n

将拒绝的数据保存到默认位置或所选位置,可以让您查看文件内容、解决问题以及从包含拒绝数据的文件中重新加载数据。将拒绝的数据保存到某个表中后,您可以查询该表以查看拒绝的数据行以及无法解析行的原因(异常)。Vertica 建议将拒绝的数据保存到表中。

多个包含拒绝数据的文件

除非加载非常小 (< 10 MB),否则 COPY 会创建多个文件来保存拒绝的行。决定 COPY 为拒绝的数据创建的文件数有几个因素。以下是其中一些因素:

  • 正在加载的源数

  • 拒绝的行总数

  • 源文件(或多个文件)的大小

  • 协作解析和正在使用的线程数

  • 支持分摊加载的 UDL

  • 对于您自己的 COPY 解析器,从 prepareUDSources() 返回的对象数

拒绝文件的命名约定

您可以使用正在加载的文件指定一个或多个包含拒绝数据的文件。使用 REJECTED DATA 参数指定文件位置和名称,并用逗号 (,) 分隔包含连续拒绝数据的文件名。请勿使用 ON ANY NODE 选项,因为它仅适用于加载文件。

如果指定一个或多个文件,并且 COPY 需要多个文件来存储拒绝的数据,则 COPY 使用您提供的拒绝数据文件名作为前缀,并为每个包含拒绝数据的文件附加数字后缀。例如,如果为 REJECTED_DATA 参数指定名称 my_rejects,并且您正在加载的文件足够大 (> 10 MB),则会存在如下几个文件:

  • my_rejects-1

  • my_rejects-2

  • my_rejects-3

COPY 默认使用协作解析,可让节点解析文件内容的特定部分。根据文件或部分的大小,每个线程会为每个源文件或部分至少生成一个包含拒绝数据的文件,并将加载结果返回给启动程序节点。当 COPY 使用多个线程(.1、.2、.3 等)时,文件后缀是线程索引。

要使每个线程能够解析任何部分,拒绝的最大数据文件数量不能大于正在加载的源数量。最大线程数由资源池决定。对于协作解析,请使用所有可用线程。

如果将 COPY 与支持分摊加载的 UDL 结合使用,则文件后缀是偏移量值。支持分摊加载的 UDL 使得协作解析变得没有必要。对于分摊加载,COPY 会为每个数据部分至少创建一个拒绝文件,并根据加载大小和拒绝的行数来创建更多文件。

对于除 COPY LOCAL 以外的所有数据加载,COPY 的行为如下所示:

文件名的最大长度

在一个语句中加载多个输入文件时,需要为每个文件指定完整的路径名称。请牢记,将较长的输入文件名与拒绝数据文件名组合使用可能会超出操作系统的最大长度(通常为 255 个字符)。若要解决文件名超出最大长度的问题,请使用一个与默认路径不同的拒绝数据文件路径,例如 \tmp\\<shorter-file-name>

11.2 - 将拒绝的数据保存到表中

配合使用 REJECTED DATA 参数和 AS TABLE 子句可指定要将拒绝的数据保存到的表。将拒绝的数据保存到文件与使用 AS TABLE 子句是互斥的。

使用 AS TABLE 子句时,Vertica 会创建一个新表(如果不存在表)或将其附加到现有表。如果加载期间未发生解析拒绝,则表示表存在但却为空。下次加载数据时,Vertica 会将任何拒绝的行插入到现有表中。

加载拒绝表是一种特殊类型的表,其功能和限制如下:

  • 支持 SELECT 语句

  • 可以使用 DROP TABLE

  • 不能创建于 COPY 语句之外

  • 不支持 DML 和 DDL 活动

  • 并非 K-safe

若要使拒绝表中的数据变得 K-safe,可执行下列操作之一:

  • 编写一条 CREATE TABLE..AS 语句,如下例:

    => CREATE TABLE new_table AS SELECT * FROM rejected_table;
    
  • 创建一个表以存储拒绝的记录,并对新表运行 INSERT..SELECT 操作

使用 COPY NO COMMIT

如果 COPY 语句包含选项 NO COMMITREJECTED DATA AS TABLE,并且 reject-table 尚不存在,则 Vertica 分析数据库会将拒绝数据表保存为 LOCAL TEMP 表,并返回一条指示正在创建 LOCAL TEMP 表的消息。

拒绝数据表对于提取-加载-转换工作流很有用,在这些工作流程中,您可能会更频繁地使用临时表。拒绝数据表可让您快速加载数据并确定无法加载的记录。如果您将数据加载到使用 ON COMMIT DELETE 子句创建的临时表,则不会提交 COPY 操作。

拒绝数据表记录的位置

使用 REJECTED DATA AS TABLE table_name 选项将拒绝的记录保存到表中时,表数据将保存到数据库数据子目录 RejectionTableData 中。例如,对于 VMart 数据库,表数据文件的位置为:

/home/dbadmin/VMart/v_vmart_node0001_data/RejectionTableData

拒绝数据表包括拒绝数据和拒绝(异常)原因以及其他数据列(在下面加以介绍)。Vertica 建议您定期删除不再需要的任何拒绝数据表。

查询拒绝数据表

如果在使用 COPY 加载数据时指定了拒绝数据表,则可在加载操作完成后查询该表,以获取有关拒绝数据的信息。例如:

  1. 创建 loader 表:

    => CREATE TABLE loader(a INT)
    CREATE TABLE
    
  2. 使用 COPY 加载值并将拒绝数据保存到表 loader_rejects 中:

    => COPY loader FROM STDIN REJECTED DATA AS TABLE loader_rejects;
    Enter data to be copied followed by a newline.
    End with a backslash and a period on a line by itself.
    >> 1
    >> 2
    >> 3
    >> a
    >> \.
    
  3. 加载数据后查询 loader 表:

    => SELECT * FROM loader;
     x
    ---
     1
     2
     3
    (3 rows)
    
  4. 查询 loader_rejects 表以查看其列行:

    => SELECT * FROM loader_rejects;
    -[ RECORD 1 ]-------------+--------------------------------------------
    node_name                 | v_vmart_node0001
    file_name                 | STDIN
    session_id                | v_vmart_node0001.example.-24016:0x3439
    transaction_id            | 45035996274080923
    statement_id              | 1
    batch_number              | 0
    row_number                | 4
    rejected_data             | a
    rejected_data_orig_length | 1
    rejected_reason           | Invalid integer format 'a' for column 1 (x)
    

拒绝数据表具有以下列:

导出拒绝记录表

您可以将列 rejected_data 的内容导出到文件以仅捕获在第一个 COPY 语句期间拒绝的数据。然后,更正文件中的数据,保存数据并加载更新后的文件。

要导出拒绝记录:

  1. 创建示例表:

    => CREATE TABLE t (i int);
    CREATE TABLE
    
  2. 将数据直接复制到表,并使用一个表存储拒绝数据:

    => COPY t FROM STDIN REJECTED DATA AS TABLE t_rejects;
    Enter data to be copied followed by a newline.
    End with a backslash and a period on a line by itself.
    >> 1
    >> 2
    >> 3
    >> 4
    >> a
    >> b
    >> c
    >> \.
    
  3. 仅显示元组,并设置输出格式:

    => \t
    Showing only tuples.
    => \a
    Output format is unaligned.
    
  4. 输出到文件:

    => \o rejected.txt
    => select rejected_data from t_rejects;
    => \o
    
  5. 对已保存的文件使用 cat 命令:

    
    => \! cat rejected.txt
    a
    b
    c
    

文件存在后,您可以修复加载错误,并使用更正的文件作为 COPY 语句的加载输入。

11.3 - 保存加载异常 (EXCEPTIONS)

COPY 异常包含一些描述为何无法解析某个数据行的说明性消息。EXCEPTIONS 选项允许您指定 COPY 写入异常的文件。如果省略此选项,则 COPY 会将异常文件保存到以下路径: catalog-dir/CopyErrorLogs/tablename-sourcefilename-copy-from-exceptions,其中:

  • catalog-dir 是存放数据库编录文件的目录

  • table 是要加载到的表的名称

  • sourcefile 是要加载的文件的名称

EXCEPTIONS 选项生成的文件指示每个异常的行号和原因。

如果从 STDIN 复制,则源文件名为 STDIN

您可以为数据加载中的单个文件指定拒绝数据和异常文件。在 COPY 语句中使用逗号分隔拒绝数据和异常文件。

您必须在路径中指定文件名才能加载多个输入文件。请记住,将较长的表名与较长的数据文件名结合使用可能会超出操作系统的最大长度(通常为 255 个字符)。若要解决文件名超出最大长度的问题,请使用一个与默认路径不同的异常文件路径,例如 /tmp/<shorter-file-name>

如果您指定 EXCEPTIONS 路径:

  • 对于一个数据文件,路径必须为文件,并且 COPY 将所有信息都存储在此文件中。

  • 对于多个数据文件,路径必须为目录。COPY 在此目录中为每个数据文件创建一个文件。

  • 不将异常文件存储在启动节点上。

  • 只能为每个节点指定一个路径。

如果未指定 EXCEPTIONS 路径,则 COPY 将异常文件存储在默认目录。

11.4 - COPY 拒绝数据和异常文件

当执行 COPY 语句并且并行处理为 ON(默认设置)时,COPY 创建单独的线程来处理加载文件。通常,线程的数量取决于系统中节点核心的数量。每个节点处理部分加载数据。如果加载总体成功,在加载处理期间发生的所有解析器拒绝都会写入到该节点的特定拒绝数据以及异常文件中。如果加载失败,则拒绝数据文件内容可能不完整或为空。如果您未明确指定文件名,则 COPY 会对拒绝数据文件使用默认名称和位置。请参阅下一个主题以指定您自己的拒绝数据和异常文件。

拒绝数据和异常文件都会保存并存储在每个节点上。此示例将多个文件用作 COPY 输入。由于语句不包括 REJECTED DATAEXCEPTIONS 参数,拒绝数据和异常文件都会写入每个节点的默认位置,即数据库编录子目录 CopyErrorLogs

\set dir `pwd`/data/ \set remote_dir /vertica/test_dev/tmp_ms/
\set file1 '''':dir'C1_large_tbl.dat'''
\set file2 '''':dir'C2_large_tbl.dat'''
\set file3 '''':remote_dir'C3_large_tbl.dat'''
\set file4 '''':remote_dir'C4_large_tbl.dat'''
=>COPY large_tbl FROM :file1 ON site01,:file2 ON site01,
               :file3 ON site02,
               :file4 ON site02
               DELIMITER '|';

指定拒绝的数据和异常文件

使用可选的 COPY REJECTED DATAEXCEPTIONS 参数 'path' 元素可以指定用来存储文件的非默认路径。

如果 path 解析到存储位置,并且调用 COPY 的用户不是超级用户,则需要以下权限:

两个参数也具有可使用指定路径的可选 ONnodename 子句:

...[ EXCEPTIONS 'path' [ ON nodename ] [, ...] ]...[ REJECTED DATA 'path' [ ON nodename ] [, ...] ]

使用 'path' 指定拒绝数据和异常文件(及其相应的参数)的位置时,可选的 ONnodename 子句会将节点上的任何现有拒绝数据和异常文件移动到同一个节点上的指定路径。

将拒绝数据和异常文件保存到单个服务器

COPY 处理完成后,COPY 语句并无可合并异常和拒绝数据文件的功能。查看异常和拒绝数据文件的内容时,需要访问每个节点的特定文件。

将 VSQL 变量用于拒绝数据和异常文件

此示例使用了 vsql 变量来指定要与 exceptionsrejected data 参数(except_s1reject_s1)一起使用的路径和文件名。COPY 语句在启动程序节点上指定了单个输入文件 (large_tbl):

\set dir `pwd`/data/ \set file1 '''':dir'C1_large_tbl.dat'''
\set except_s1 '''':dir'exceptions'''
\set reject_s1 '''':dir'rejections'''

COPY large_tbl FROM :file1 ON site01 DELIMITER '|'
REJECTED DATA :reject_s1 ON site01
EXCEPTIONS :except_s1 ON site01;

此示例使用变量指定了远程节点上的异常和拒绝数据文件(except_s2reject_s2)。COPY 语句包含远程节点上的单个输入文件 (site02):

\set remote_dir /vertica/test_dev/tmp_ms/\set except_s2 '''':remote_dir'exceptions'''
\set reject_s2 '''':remote_dir'rejections'''

COPY large_tbl FROM :file1 ON site02 DELIMITER '|'
REJECTED DATA :reject_s2 ON site02
EXCEPTIONS :except_s2 ON site02;

此示例使用变量来指定异常和拒绝数据文件位于远程节点上(由 :remote_dir 指示)。COPY 语句的输入包括两个节点(site01site02)上的多个数据文件。exceptionsrejected data 选项使用了具有变量的 ON nodename 子句来指示文件的驻留位置(site01site02):

\set dir `pwd`/data/ \set remote_dir /vertica/test_dev/tmp_ms/
\set except_s1 '''':dir''''
\set reject_s1 '''':dir''''
\set except_s2 '''':remote_dir''''
\set reject_s2 '''':remote_dir''''
COPY large_tbl FROM :file1 ON site01,
               :file2 ON site01,
               :file3 ON site02,
               :file4 ON site02
               DELIMITER '|'
               REJECTED DATA :reject_s1 ON site01, :reject_s2 ON site02
               EXCEPTIONS :except_s1 ON site01, :except_s2 ON site02;

11.5 - COPY LOCAL 拒绝和异常文件

调用 COPY LOCAL(或 COPY LOCAL FROM STDIN)不会自动创建已拒绝的数据和异常文件。此行为与使用 COPY 不同,使用 COPY 将自动保存两种文件,而不管是否使用可选 REJECTED DATAEXCEPTIONS 参数显式指定任一文件。

将 REJECTED DATA 和 EXCEPTIONS 参数与 COPY LOCAL 和 COPY LOCAL FROM STDIN 结合使用,可将相应输出文件保存到客户端。如果使用这些选项,则不会保留已拒绝的数据解析事件(以及描述这些内容的异常文件),即使发生这些事件。

可以使用 COPY LOCAL(或 COPY LOCAL FROM STDIN)加载多个输入文件。如果还使用 REJECTED DATAEXCEPTIONS 选项,该语句会将已拒绝的行和异常写入不同文件。各个文件中分别包含所有已拒绝的行和相应异常,而不管加载了多少输入文件。

如果 COPY LOCAL 不拒绝任何行,则不会创建任何一个文件。

指定拒绝的数据和异常文件

若要将任何已拒绝的数据及异常保存到文件中:

  1. 在 COPY LOCAL(和 COPY LOCAL FROM STDIN)语句中,分别使用 REJECTED DATA 'path'EXCEPTIONS 'path' 参数。

  2. 为这两个选项指定两个不同的文件名。不能为 REJECTED DATAEXCEPTIONS 使用一个文件。

  3. 调用 COPY LOCAL 或 COPY LOCAL FROM STDIN 时,指定的文件无需预先存在。如果预先存在,COPY LOCAL 必须能够覆盖这些文件。

可以通过 vsql 变量指定路径和文件名:

\set rejected ../except_reject/copyLocal.rejected
\set exceptions ../except_reject/copyLocal.exceptions

使用 COPY LOCAL 或 COPY LOCAL FROM STDIN 语句时,使用相应参数为文件指定变量名称:

=> COPY large_tbl FROM LOCAL rejected data :rejected exceptions :exceptions;
=> COPY large_tbl FROM LOCAL STDIN rejected data :rejected exceptions :exceptions;

12 - 监控 COPY 加载和指标

您可以使用以下内容检查 COPY 加载:

  • Vertica 函数

  • LOAD_STREAMS 系统表

  • LOAD_SOURCES 系统表

一般而言,执行_引擎_配置文件 系统表会记录有关查询事件的信息,包括加载。

使用 Vertica 函数

两个元函数会从当前会话中运行的最近 COPY 语句返回已接受或已拒绝行数的 COPY 指标:

  1. 要获取接受的行数,请使用 GET_NUM_ACCEPTED_ROWS 函数:

    => select get_num_accepted_rows();
     get_num_accepted_rows
    -----------------------
                        11
    (1 row)
    
  2. 要检查拒绝的行数,请使用 GET_NUM_REJECTED_ROWS 函数:

    => select get_num_rejected_rows();
     get_num_rejected_rows
    -----------------------
                        0
    (1 row)
    

使用 CURRENT_LOAD_SOURCE 函数

可以将 CURRENT_LOAD_SOURCE 函数作为 COPY 语句的一部分。这样做可以将由该函数计算得出的输入文件名或值插入到列中。

若要将多个源文件的文件名插入列中:

=> COPY t (c1, c2, c3 as CURRENT_LOAD_SOURCE()) FROM '/home/load_file_1' ON  exampledb_node02,
'/home/load_file_2' ON exampledb_node03 DELIMITER ',';

使用 LOAD_STREAMS 系统表

Vertica 包括一组包含监控信息的系统表。LOAD_STREAMS 系统表包含有关来自 COPYCOPY FROM VERTICA 语句的加载流指标的信息。因此,您可以查询表值以获取 COPY 指标。

Vertica 将维护系统表指标直到其达到指定的大小配额(以 KB 计)。配额是通过内部进程设置的,不能直接设置或查看。

标记复制流

COPY 可以包含 STREAM NAME 参数以标记其加载流,以便在 LOAD_STREAMS 系统表中可以轻松识别。例如:

=> COPY mytable FROM myfile DELIMITER '|' STREAM NAME 'My stream name';

加载流指标

以下 LOAD_STREAMS 列显示加载进度的状态:

取决于数据大小,在 PARSE_COMPLETE_PERCENT 达到 100% 的时间与 SORT_COMPLETE_PERCENT 开始增长的时间之间可能会出现明显的滞后。

此示例将从 LOAD_STREAMS 系统表中查询加载流数据:

=> \pset expanded
Expanded display is on.
=> SELECT stream_name, table_name, load_start, accepted_row_count,
   rejected_row_count, read_bytes, unsorted_row_count, sorted_row_count,
   sort_complete_percent FROM load_streams;
-[ RECORD 1 ]----------+---------------------------
stream_name            | fact-13
table_name             | fact
load_start             | 2010-12-28 15:07:41.132053
accepted_row_count     | 900
rejected_row_count     | 100
read_bytes             | 11975
input_file_size_bytes  | 0
parse_complete_percent | 0
unsorted_row_count     | 3600
sorted_row_count       | 3600
sort_complete_percent  | 100

使用 LOAD_SOURCES 系统表

LOAD_STREAMS 表显示已加载或已拒绝的总行数。按源对这些信息进行分组可以帮助您确定数据的来源。LOAD_SOURCES 系统表包含一些与 LOAD_STREAMS 相同的数据,但添加了这些特定于源的信息。如果启用分摊,LOAD_SOURCES 还会提供有关如何在节点之间分摊加载的信息。

您可以使用此表确定不同查询结果的原因。例如,您可以使用以下语句创建基于 glob 的外部表:

=> CREATE EXTERNAL TABLE tt AS COPY WITH SOURCE AWS(dir = 'foo', file = '*');

如果从此表进行选择,Vertica 会从 foo 目录中的每个文件加载数据,并在 LOAD_SOURCES 表中为每个文件创建一行。假设您稍后重复查询看到了不同的结果。您可以查看 LOAD_SOURCES 表,并发现在两次查询之间有人将另一个文件添加到了 foo 目录。由于每个文件均记录在 LOAD_SOURCES 中,因此您可以看到解释更改后的查询结果的新文件。

如果使用多个数据源,您可能更愿意禁用此报告。要禁用报告,请将 LoadSourceStatisticsLimit 配置参数设置为 0。此参数会设置由 LOAD_SOURCES 为每个加载配置的源数量的上限。默认值为 256。

13 - 使用加载脚本

可以使用简单文本分隔的文件格式为 COPY 语句编写和运行加载脚本。有关其他加载格式的信息,请参阅数据格式。Vertica 建议您先加载小型表,之后再加载最大的表。要在加载前检查数据格式,请参阅处理非 UTF-8 输入

在加载脚本中使用绝对路径

除非您正在使用 COPY FROM LOCAL 语句,否则在远程客户端上使用 COPY 需要数据文件的绝对路径。不能在远程客户端上使用相对路径。对于加载脚本,可以使用 vsql 变量指定数据文件相对于 Linux 工作目录的位置。

要使用 vsql 变量指定数据文件位置:

  1. 创建包含 Linux 当前目录的 vsql 变量。

    \set t_pwd `pwd`
    
  2. 为特定数据文件创建使用相对于 Linux 当前目录变量的路径的另一个 vsql 变量。

    \set input_file '\'':t_pwd'/Date_Dimension.tbl\''
    
  3. 在 COPY 语句中使用第二个变量:

    => COPY Date_Dimension FROM :input_file DELIMITER '|';
    
  4. 重复步骤 2 和 3 以加载所有数据文件。

运行加载脚本

可以在任何主机上运行加载脚本,只要数据文件在该主机上。

  1. 将 Linux 工作目录更改为数据文件的位置。

    $ cd /opt/vertica/doc/retail_example_database
    
  2. 运行管理工具。

    $ /opt/vertica/bin/admintools
    
  3. 连接到数据库。

  4. 运行加载脚本。

14 - 对数据加载进行故障排除

加载数据时,可能会遇到以下问题。对于特定于外部表的问题,请参阅外部表故障排除

无法加载数据,因为所有列都是复杂类型

原生表必须至少包含一个列,该列是标量类型或原生数组(标量类型的一维数组)。如果要加载的数据完全由复杂类型组成,则必须在加载数据之前向表中添加标量列。您无需填充标量列。

首先,使用数据中的列以及一个标量列来创建原生表,然后将数据加载到该表中,如以下示例所示:

-- Native table with extra scalar column:
=> CREATE TABLE customers(person ROW(name VARCHAR, card VARCHAR), tempval int);
CREATE TABLE

-- Load complex data, ignoring scalar column:
=> COPY customers(person) FROM :custdata PARQUET;
 Rows Loaded
-------------
           3
(1 row)

加载因磁盘配额问题而失败

如果加载的数据量导致表或架构超过磁盘配额,则 COPY 可能会失败:

ERROR 0: Disk Quota for the Table Level Exceeded
HINT: Delete data and PURGE or increase disk quota at the table level

默认情况下,Vertica 表和架构没有磁盘配额;它们仅受您的硬件和许可证容量的限制。但是,超级用户可以设置配额,如磁盘配额中所述。

如果因配额问题而导致失败,则不能简单地使用 DELETE 来减少使用量。DELETE 不会释放空间,因为已删除的数据仍保留在存储容器中。要回收空间,您必须清除已删除的数据;请参阅移除表数据

从 Parquet 文件读取数据时报告意料之外的数据类型不匹配

如果 Parquet 文件包含类型为 STRING 的列,但 Vertica 中的列属于不同的类型(如 INTEGER),您可能会看到不明确的错误消息。在这种情况下,Vertica 会将 Parquet 文件中的列报告为 BYTE_ARRAY,如以下示例所示:


ERROR 7247:  Datatype mismatch: column 2 in the parquet_cpp source
[/tmp/nation.0.parquet] has type BYTE_ARRAY, expected int

这种行为特定于 Parquet 文件;对于 ORC 文件,该类型将被正确报告为 STRING。发生此问题的原因是,Parquet 文件不为 STRING 类型提供原生支持,而是用 BYTE_ARRAY 代表字符串。由于 Parquet 文件将其类型报告为 BYTE_ARRAY,Vertica 无法确定该类型实际为 BYTE_ARRAY 还是 STRING。

错误 7087:列数错误

加载 ORC 或 Parquet 数据时,您可能会看到表明列数出错的错误消息:

=> CREATE TABLE nation (nationkey bigint, name varchar(500),
            regionkey bigint, comment varchar(500));
   CREATE TABLE

=> COPY nation from :orc_dir ORC;
   ERROR 7087: Attempt to load 4 columns from an orc source
   [/tmp/orc_glob/test.orc] that has 9 columns

当您从 ORC 或 Parquet 文件加载数据时,您的表必须使用文件中的所有数据,否则就会生成此错误。为了避免此问题,请将缺失的列添加到表定义。

对于 Parquet 数据,时间戳值中的时区不正确

在 Vertica 中从 Parquet 文件读取时间戳可能会导致不同的值,具体取决于本地时区。发生此问题的原因是 Parquet 格式不支持 SQL TIMESTAMP 数据类型。如果您在表中使用 TIMESTAMP 数据类型定义列,Vertica 将从 Parquet 文件读取的时间戳解释为本地时区中的值。Hive 中也会发生相同的行为。发生这种情况时,Vertica 将在查询时生成如下警告:

WARNING 0:  SQL TIMESTAMPTZ is more appropriate for Parquet TIMESTAMP
because values are stored in UTC

在 Vertica 中创建表时,使用 TIMESTAMPTZ 数据类型代替 TIMESTAMP 即可避免此问题。

ORC 数据中的时区也可能不正确,但原因不同。

对于 ORC 数据,时间戳值中的时区不正确

Vertica 和 Hive 都使用 Apache ORC 库与 ORC 数据进行交互。此库的行为随着 Hive 版本 1.2.0 而改变,因此时间戳表示取决于用于写入数据的版本。

写入时间戳时,ORC 库现在会在条带页脚中记录时区。Vertica 会查找此值并在加载时间戳时应用该值。如果文件是用早期版本的库编写的,则文件中缺少时区。

如果文件不包含时区,Vertica 将使用本地时区并在 QUERY_EVENTS 系统表中记录 ORC_FILE_INFO 事件。

首次查询新的 ORC 数据源时,应查询此表以查找缺失的时区信息:

=> SELECT event_category, event_type, event_description, operator_name, event_details, COUNT(event_type)
   AS COUNT FROM QUERY_EVENTS WHERE event_type ILIKE 'ORC_FILE_INFO'
   GROUP BY event_category, event_type, event_description, operator_name, event_details
   ORDER BY event_details;
event_category | event_type | event_description | operator_name | event_details | count
----------------+---------------+----------------------------------------------------+---------------+-----------------------------------------------------------------------------------------------------------------------------------------------------------+-------
EXECUTION | ORC_FILE_INFO | ORC file does not have writer timezone information | OrcParser | Timestamp values in the ORC source [data/sales_stats.orc] will be computed using local timezone | 2

(1 row)

Parquet 数据中的时区也可能不正确,但原因不同。

某些 DATE 和 TIMESTAMP 值存在几天的误差

当 Hive 写入 ORC 或 Parquet 文件时,它会将 1583 年以前的日期从公历转换成儒略历。Vertica 不会执行此转换。如果文件包含早于此时间的日期,则 Hive 中的值和 Vertica 中的相应值可能会相差多达十天。这种差异同时适用于 DATE 和 TIMESTAMP 值。

15 - 使用外部数据

将数据导入 Vertica 的另一种方法是就地查询。在某些情况下,查询外部数据而不是导入它可能是有利的:

  • 在选择要加载到 Vertica 中的数据之前,如果想浏览诸如数据湖中的数据。

  • 如果您是共享诸如数据湖中的相同数据的多个使用者之一,则就地读取数据可以消除对查询结果是否是最新的顾虑。副本只有一份,因此所有使用者看到的数据都相同。

  • 如果您的数据变化迅速,但您不想将其流式传输到 Vertica,则可以改为自动查询最新更新。

  • 如果您有大量数据并且不想增加许可证容量。

  • 如果 Vertica 中拥有您仍希望能够查询的低优先级数据。

要查询外部数据,必须将您的数据描述为外部表。和原生表一样,外部表也有表定义,可以被查询。与原生表不同,外部表没有编录,Vertica 根据需要从外部源加载选定数据。对于某些格式,查询计划器可以利用数据中的分区和排序,因此查询外部表并不意味着您在查询时加载所有数据。(有关原生表的详细信息,请参阅使用原生表。)

本节未涉及一种特殊类型的外部数据。如果您正在从 Hadoop 中读取数据,特别是从 Hive 数据仓库中读取,则可以从 Hive 中读取架构信息,而不是定义您自己的外部表。有关详细信息,请参阅使用 HCatalog 连接器

15.1 - 外部表与原生表有何差异

您可以像使用 Vertica 原生表一样使用外部表。但是,由于数据在数据库外部,因此外部表的操作方式存在一些差异。

数据

外部表的数据可以驻留在任何地方,前提是所有数据库节点都可以访问它。S3、HDFS 和 NFS 挂载点是查找外部数据的常见位置。与查询本地存储的 ROS 数据相比,查询外部数据自然会产生一些延迟,但 Vertica 的优化可以减少影响。例如,Vertica 可以利用 HDFS 数据的节点和机架位置。

由于数据是外部数据,因此 Vertica 会在您每次查询时加载外部数据。Vertica 经过优化以减少读取数据量,包括支持分区的格式的谓词下推和分区修剪。ORC 和 Parquet 格式支持这些优化。

由于数据是在查询时读取的,因此您必须确保您的用户拥有并保留在其原始位置读取数据的权限。根据数据的存储位置,您可能需要采取额外的步骤来管理访问权限,例如在 S3 上创建 AWS IAM 角色。

由于数据未存储在 Vertica 中,因此外部表不使用超投影和伙伴实例投影。

资源使用

外部表对 Vertica 编录的添加很少,从而减少了查询使用的资源。由于数据不存储在 Vertica 中,因此外部表不受 Tuple Mover 的影响,也不会导致 ROS 回推。在读取外部表数据时,Vertica 将使用少量内存,因为表内容不是数据库的一部分,每次使用外部表时都会进行解析。

备份和还原

由于外部表中的数据在 Vertica 以外进行管理,因此数据库备份中仅包含外部表定义(而非数据文件)。为外部表数据安排单独的备份过程。

DML 支持

外部表允许您读取外部数据。不允许您进行修改。因此,某些 DML 操作不适用于外部表,包括:

  • DELETE FROM

  • INSERT INTO

  • SELECT...FOR UPDATE

序列和标识列

外部表的 COPY 语句定义可以包含标识列和序列。每当 select 语句查询外部表时,都会重新评估序列和标识列。这会导致外部表列的值发生变化,即使基础外部表数据保持不变。

15.2 - 创建外部表

要创建外部表,请使用 CREATE EXTERNAL TABLE AS COPY 语句将表定义与复制语句结合起来。CREATE EXTERNAL TABLE AS COPY 使用来自 CREATE TABLE 和 COPY 的参数子集。

您可以使用 CREATE TABLE 像定义 Vertica 原生表一样定义表列。您还可以指定 COPY FROM 子句以描述如何读取数据,就像加载数据一样。如何指定 FROM 路径取决于文件所在的位置和数据格式。请参阅指定加载数据的位置数据格式

与原生表一样,您可以使用 INFER_TABLE_DDL 函数从支持格式的数据文件中派生列定义。

以下示例为存储在 HDFS 中的分隔数据定义了一个外部表:

=> CREATE EXTERNAL TABLE sales (itemID INT, date DATE, price FLOAT)
    AS COPY FROM 'hdfs:///data/ext1.csv' DELIMITER ',';

以下示例使用存储在 S3 中的 ORC 格式的数据。数据有两个分区列。有关分区的详细信息,请参阅分区文件路径

=> CREATE EXTERNAL TABLE records (id int, name varchar(50), created date, region varchar(50))
   AS COPY FROM 's3://datalake/sales/*/*/*'
   PARTITION COLUMNS created, region;

下面的示例演示了,如何才能从本地目录(没有分区和 glob)中的所有 Parquet 文件读取数据:

=> CREATE EXTERNAL TABLE sales (itemID INT, date DATE, price FLOAT)
    AS COPY FROM '/data/sales/*.parquet' PARQUET;

创建外部表时,数据不会添加到数据库中,也不会创建投影。相反,Vertica 会对 CREATE EXTERNAL TABLE AS COPY 语句执行语法检查并在编录中存储表名称和 COPY 语句定义。每次 SELECT 查询引用外部表时,Vertica 将解析并执行存储的 COPY 语句以获取引用的数据。只有通过查询表才能发现表定义中的任何问题(例如不正确的列类型)。

若要从外部表成功返回数据,COPY 定义必须正确,其他依赖项(如文件、节点和其他资源)必须可供访问且在查询时可用。如果表定义使用 glob(通配符)并添加或删除文件,则外部表中的数据可以在查询之间更改。

ORC 和 Parquet 格式

为 ORC 或 Parquet 数据定义外部表时,您必须定义文件中的所有数据列。如果省略数据列,则使用该表的查询会因错误而中止。

如果您在同一 COPY 语句中从多个 ORC 或 Parquet 文件加载,并且其中任何一个被中止,则整个加载中止。这种行为与分隔文件的行为不同,分隔文件中的 COPY 语句会加载其可以加载的数据并忽略剩余数据。

外部表的特殊注意事项

如果列的最大长度小于实际数据(如过短的 VARCHAR),Vertica 会将数据截断并记录该事件。

如果列上的约束导致值被拒绝,您可能会看到意外的查询结果:

  • 如果您指定 NOT NULL 列约束并且数据包含 null 值,则这些行将被拒绝。

  • 如果您使用 ENFORCELENGTH,太长的值将被拒绝而不是被截断。

  • 读取 ORC 数据时,如果声明了标量精度并且某些数据不适合,则该行将被拒绝。例如,如果将列指定为 Decimal(6,5),则拒绝值 123.456。

了解列约束是否导致数据被拒绝的一种方法是,列上的 COUNT 返回的值是否与 COUNT(*) 不同。

使用 COPY 参数 ON ANY NODE 时,请确认源文件定义在所有节点上均相同。指定不同的外部文件会导致生成的结果不一致。

可以利用分区来限制 Vertica 读取的数据量。有关使用分区数据的详细信息,请参阅分区文件路径

取消 CREATE EXTERNAL TABLE AS COPY 语句会导致不可预测的结果。如果您在开始操作后发现表定义不正确(例如,无意中指定了错误的外部位置),请等待查询完成。当外部表存在时,使用 DROP TABLE 移除其定义。

创建外部表后,分析其行数以提高查询性能。请参阅提高外部表的查询性能

必需的权限

除了在 Vertica 中拥有权限外,用户还必须拥有对外部数据的读取访问权限。

  • 对于本地磁盘上的数据,此访问权限由本地文件权限控制。

  • 对于 HDFS 中的数据,访问权限可能受 Kerberos 身份验证控制。请参阅访问已进行 Kerberize 的 HDFS 数据

  • 对于 S3 中的数据,您需要通过 AWS IAM 角色进行访问。请参阅S3 对象存储

对于 GCS 中的数据,您必须在读取数据之前启用 S3 兼容性。请参阅Google Cloud Storage (GCS) 对象存储

默认情况下,您还必须是数据库超级用户才能通过 SELECT 语句访问外部表。

在大多数情况下,要允许没有超级用户访问权限的用户查询外部表,则管理员必须创建一个 USER 存储位置,并为这些用户授予对该位置的读取访问权限。请参阅 CREATE LOCATIONGRANT(存储位置)。该位置必须是在创建外部表时 COPY 语句中使用的路径的父项。此要求不适用于 HDFS 中存储的外部表。以下示例显示了为一个名为 Bob 的用户授予对数据位于 /tmp(包含任何深度的子目录)之下的所有外部表的访问权限:

=> CREATE LOCATION '/tmp' ALL NODES USAGE 'user';
=> GRANT ALL ON LOCATION '/tmp' to Bob;

组织外部表数据

如果存储在外部表中的数据定期更改(例如每月存储最近的历史数据),您可以结合使用分区和通配符 (globs),更加动态地解析存储的 COPY 语句定义。例如,如果将每月数据存储在 NFS 挂载上,您可以在日历年的顶层目录中按月组织文件,例如:

/year=2018/month=01/

然后,您可以从 COPY 语句的目录名称中读取年份和月份值:

=> CREATE EXTERNAL TABLE archive (...) AS COPY FROM '/nfs_name/*/*/*' PARTITION COLUMNS year, month;

每当 Vertica 查询引用外部表 archive 并且 Vertica 解析 COPY 语句时,查询都可以访问顶级 monthly 目录中存储的所有数据。如果查询按年份或月份筛选(例如在 WHERE 子句中),则 Vertica 在评估 glob 时会跳过不相关的目录。有关详细信息,请参阅分区文件路径

验证表定义

创建外部表时,Vertica 会验证 CREATE EXTERNAL TABLE AS COPY FROM 语句的语法。例如,如果在语句中忽略必需的关键字,则创建外部表会失败:

=> CREATE EXTERNAL TABLE ext (ts timestamp, d varchar)
    AS COPY '/home/dbadmin/designer.log';
ERROR 2778:  COPY requires a data source; either a FROM clause or a WITH SOURCE for a user-defined source

系统不会检查 COPY 定义的其他组件(例如路径语句和节点可用性),直到 SELECT 查询引用外部表。

若要验证外部表定义,请运行引用外部表的 SELECT 查询。检查返回的查询数据是否符合预期。如果查询没有正确地返回数据,请检查 COPY 异常以及拒绝数据日志文件。

由于 COPY 定义确定了查询外部表时发生的内容,因此 COPY 语句错误可以揭示潜在问题。有关 COPY 异常和拒绝的详细信息,请参阅处理杂乱的数据

查看外部表定义

创建外部表时,Vertica 将在 TABLES 系统表的 table_definition 列中存储 COPY 定义语句。

要列出所有表,请使用 SELECT * 查询,如下所示:

=> SELECT * FROM TABLES WHERE table_definition <> '';

使用如下所示的查询列出外部表定义:

=> SELECT table_name, table_definition FROM TABLES;
 table_name |                                table_definition
------------+----------------------------------------------------------------------
 t1         | COPY            FROM 'TMPDIR/external_table.dat'  DELIMITER ','
 t1_copy    | COPY            FROM 'TMPDIR/external_table.dat'  DELIMITER ','
 t2         | COPY FROM 'TMPDIR/external_table2.dat' DELIMITER ','
(3 rows)

15.3 - 分区文件路径

数据文件有时在文件系统中使用目录结构进行分区。分区将值从原始数据中移出(在原始数据中,它们必须包含在每一行中),然后移入目录结构,从而节省磁盘空间。分区还可以通过允许跳过整个目录来提高查询性能。

一种常见使用情况是按日期分区:

/data/created=2016-11-01/*
/data/created=2016-11-02/*
/data/created=2016-11-03/*
/data/...

glob 中的文件不包含 created 列,因为此信息是通过文件系统表示的。Vertica 可以将分区值(在本例中为日期)读入表列(在本例中为 created)。

数据可以按多个值分区:

/data/created=2016-11-01/region=northeast/*
/data/created=2016-11-01/region=central/*
/data/created=2016-11-01/region=southeast/*
/data/created=2016-11-01/...
/data/created=2016-11-02/region=northeast/*
/data/created=2016-11-02/region=central/*
/data/created=2016-11-02/region=southeast/*
/data/created=2016-11-02/...
/data/created=2016-11-03/...
/data/...

如果数据按多个值分区,则分区必须以与所有目录路径中相同的顺序显示在 glob 中。在此示例中,创建日期在区域之前,并且必须始终如此。

COPY 语法

要从分区文件中读取值,请使用 COPY PARTITION COLUMNS 选项:

=> CREATE EXTERNAL TABLE records (id int, name varchar(50), created date, region varchar(50))
   AS COPY FROM 'webhdfs:////data/*/*/*'
   PARTITION COLUMNS created, region;

该路径针对每一级目录分区包含一个通配符 (),针对文件再包含一个通配符 ()。通配符的数量必须至少比分区列的数量多 1。数据文件必须至少包含一个实际列;您不能完全通过目录结构来表示数据。

每个分区目录名的第一部分必须与表定义中的列名相匹配。COPY 解析 = 之后的字符串以获取值。空值(例如名为 created= 的目录)被视为 null 值。为了向后兼容,值 __HIVE_DEFAULT_PARTITION__ 也意味着 null。

无法强制转换为正确类型的值将被拒绝,拒绝方式与拒绝数据中的非强制值相同。

查询执行

使用谓词执行查询时,Vertica 会跳过不满足谓词的子目录。此过程称为分区修剪,它可以显着提高查询性能。以下示例仅读取指定区域的所有日期的分区。虽然数据也按日期分区,但查询不限制日期。

=> SELECT * FROM t WHERE region='northeast';

要验证 Vertica 是否正在修剪分区,请在说明计划中查找类似于以下内容的消息:

files with unmatched partition have been pruned

创建分区结构

要创建分区文件结构,可以使用 Hive 或文件导出器。有关使用 Hive 的信息,请参阅 适用于 Vertica 集成的 Hive 入门知识

您可以为任何简单数据类型的列创建分区。但是,作为最佳实践,您应当避免使用 BOOLEAN、FLOAT 和 NUMERIC 类型对列进行分区。

15.4 - 查询外部表

创建外部表后,可以像查询任何其他表一样查询它。假设您创建了以下外部表:

=> CREATE EXTERNAL TABLE catalog (id INT, description VARCHAR, category VARCHAR)
    AS COPY FROM 'hdfs:///dat/catalog.csv' DELIMITER ',';
CREATE TABLE
=> CREATE EXTERNAL TABLE inventory(storeID INT, prodID INT, quantity INT)
    AS COPY FROM 'hdfs:///dat/inventory.csv' DELIMITER ',';
CREATE TABLE

您现在可以针对这些表编写查询,如下所示:

=> SELECT * FROM catalog;
 id |     description      |  category
----+----------------------+-------------
 10 | 24in monitor         | computers
 11 | 27in monitor         | computers
 12 | 24in IPS monitor     | computers
 20 | 1TB USB drive        | computers
 21 | 2TB USB drive        | computers
 22 | 32GB USB thumb drive | computers
 30 | 40in LED TV          | electronics
 31 | 50in LED TV          | electronics
 32 | 60in plasma TV       | electronics
(9 rows)

=> SELECT * FROM inventory;
 storeID | prodID | quantity
---------+--------+----------
     502 |     10 |       17
     502 |     11 |        2
     517 |     10 |        1
     517 |     12 |        2
     517 |     12 |        4
     542 |     10 |        3
     542 |     11 |       11
     542 |     12 |        1
(8 rows)

=> SELECT inventory.storeID,catalog.description,inventory.quantity
    FROM inventory JOIN catalog ON inventory.prodID = catalog.id;
 storeID |   description    | quantity
---------+------------------+----------
     502 | 24in monitor     |       17
     517 | 24in monitor     |        1
     542 | 24in monitor     |        3
     502 | 27in monitor     |        2
     542 | 27in monitor     |       11
     517 | 24in IPS monitor |        2
     517 | 24in IPS monitor |        4
     542 | 24in IPS monitor |        1
(8 rows)

外部表和 Vertica 原生表之间的一个重要区别是,查询外部表每次都会读取外部数据。(请参阅外部表与原生表有何差异。)具体来说,选择查询每次引用外部表时,Vertica 会重新解析 COPY 语句定义以访问数据。在运行查询之前,您的表定义或数据中的某些错误不会很明显,因此请在将外部表部署到生产环境之前测试外部表。

处理错误

使用不正确的 COPY FROM 语句定义查询外部表数据可能会导致多个拒绝行。为了限制拒绝数量,Vertica 使用 ExternalTablesExceptionsLimit 配置参数设置保留的最大拒绝数。默认值为 100。将 ExternalTablesExceptionsLimit 设置为 –1 可移除此限制,但不建议这样做。

如果 COPY 错误达到最大拒绝数,外部表查询会继续,但 COPY 会在 vertica.log 文件中生成一条警告,且不会报告后续拒绝行。

设置拒绝阈值下限时,使用 ExternalTablesExceptionsLimit 配置参数不同于使用 COPY 语句 REJECTMAX 参数。REJECTMAX 值控制在导致加载失败前所允许的拒绝行数量。如果 COPY 遇到等于或大于 REJECTMAX 的拒绝行数,则 COPY 中止执行,而不是在 vertica.log 中记录警告。

提高外部表的查询性能。

如果较小的表是内部表,则包含联接的查询会执行得更好。对于原生表,查询优化器使用基数来选择内部表。对于外部表,查询优化器使用行计数(如果可用)。

创建外部表后,使用 ANALYZE_EXTERNAL_ROW_COUNT 收集此信息。调用此函数可能成本很高,因为它必须实体化表的一列才能计算行数,因此请在数据库不忙于关键查询时进行此分析。(这就是 Vertica 在您创建表时不会自动执行此操作的原因。)

查询优化器在计划查询时使用您最近调用此函数的结果。因此,如果数据量发生显著变化,您应当再次运行它以提供更新的统计信息。几个百分点的差异无关紧要,但如果您的数据量增长了 20% 或更多,则应当在可能的情况下重复此操作。

如果您的数据已分区,Vertica 会自动修剪不会影响查询结果的分区,从而减少加载的数据。

对于使用 Hive 0.14 及更高版本写入的 ORC 和 Parquet 数据,Vertica 自动使用谓词下推以进一步提高查询性能。谓词下推可将部分查询移动到更接近数据的位置执行,从而减少了必须从磁盘或通过网络读取的数据量。使用早期版本的 Hive 写入的 ORC 文件可能不包含执行此优化所需的统计信息。对缺少上述统计信息的文件执行查询时,Vertica 会在 QUERY_EVENTS 系统表中记录一个 EXTERNAL_PREDICATE_PUSHDOWN_NOT_SUPPORTED 事件。如果您发现查询出现性能方面的问题,请检查此表中是否有这些事件。

将外部表与用户定义的加载 (UDL) 函数结合使用

您可以将外部表与自己创建的 UDL 函数结合使用。有关使用 UDL 的详细信息,请参阅用户定义的加载 (UDL)

15.5 - 监控外部表

Vertica 在系统表中记录有关外部表的信息。您可以使用这些表来跟踪外部数据和针对它的查询。

TABLES 系统表包含有关所有表(包括本地表和外部表)的数据。TABLE_DEFINITION 列特定于外部表。您可以查询此列以查看当前正在使用的所有外部数据源,如下例所示:

=> SELECT table_name, create_time, table_definition FROM tables WHERE table_definition != '';
table_name   |          create_time          |                     table_definition
---------------+-------------------------------+-----------------------------------------------------------
customers_orc | 2018-03-21 11:07:30.159442-04 | COPY from '/home/dbadmin/sample_orc_files/0*' ORC
miscprod      | 2018-06-26 17:40:04.012121-04 | copy from '/home/dbadmin/data/prod.csv'
students      | 2018-06-26 17:46:50.695024-04 | copy from '/home/dbadmin/students.csv'
numbers       | 2018-06-26 17:53:52.407441-04 | copy from '/home/dbadmin/tt.dat'
catalog       | 2018-06-26 18:12:28.598519-04 | copy from '/home/dbadmin/data/prod.csv' delimiter ','
inventory     | 2018-06-26 18:13:06.951802-04 | copy from '/home/dbadmin/data/stores.csv' delimiter ','
test          | 2018-06-27 16:31:39.170866-04 | copy from '/home/dbadmin/data/stores.csv' delimiter ','
                (7 rows)

EXTERNAL_TABLE_DETAILS 表提供了更多详细信息,包括文件大小。Vertica 在查询时计算此表中的值,这可能成本很高,因此请考虑按架构或表限制查询。

=> SELECT table_name, source_format, total_file_size_bytes FROM external_table_details;
table_name   | source_format | total_file_size_bytes
---------------+---------------+-----------------------
customers_orc | ORC           |             619080883
miscprod      | DELIMITED     |                   254
students      | DELIMITED     |                   763
numbers       | DELIMITED     |                    30
catalog       | DELIMITED     |                   254
inventory     | DELIMITED     |                    74
test          | DELIMITED     |                    74
(7 rows)

如果外部表的大小随时间发生显著变化,则应重新运行 ANALYZE_EXTERNAL_ROW_COUNT() 以收集更新的统计信息。请参阅提高外部表的查询性能

LOAD_SOURCES 表显示当前正在进行的加载的信息。此表不记录有关加载 ORC 或 Parquet 数据的信息。

15.6 - 外部表故障排除

在创建或查询外部表时,可能会遇到以下问题。有关一般数据加载故障排除,请参阅对数据加载进行故障排除

找不到文件或权限被拒绝

如果针对外部表的查询出现文件或权限错误,请确保执行查询的用户在 Vertica 和文件系统中都具有必要的权限。请参阅创建外部表中的权限部分。

错误 7226:找不到分区列

查询分区数据支持的外部表时,您可能会看到一条错误消息,指出缺少分区列:

ERROR 7226: Cannot find partition column [region] in parquet source
[/data/table_int/int_original/000000_0]

如果您创建外部表,然后更改分区结构(例如通过重命名列进行更改),则必须重新创建外部表。如果您看到此错误,请更新您的表以匹配磁盘上的分区。

有关分区结构的详细信息,请参阅分区文件路径

错误 6766:是一个目录

查询数据时,您可能会看到一条错误消息,指出输入文件是一个目录:

ERROR 6766: Error reading from orc parser input stream
[/tmp/orc_glob/more_nations]: Is a directory

如果表的 COPY FROM 子句中的 glob 与空目录匹配,则会发生此错误。此错误仅针对 Linux 文件系统中的文件发生;HDFS 中的空目录将被忽略。

要更正错误,请使 glob 更加具体。例如,使用 *.orc 代替 *。