【问题标题】:Understanding Hive table creation notation了解 Hive 表创建表示法
【发布时间】:2020-09-23 00:49:43
【问题描述】:

我遇到了需要转换为 Redshift/MySql 等效的 Hive 表。 我无法理解 Hive 查询结构,希望能得到一些帮助:

CREATE TABLE IF NOT EXISTS table_1 (
    id BIGINT,
    price DOUBLE,
    asset string
)
PARTITIONED BY (
    pt STRING
);
ALTER TABLE table_1 DROP IF EXISTS PARTITION (pt== '${yyyymmdd}');

INSERT OVERWRITE TABLE table_1 PARTITION (pt= '${yyyymmdd}') 
select aa.id,aa.price,aa.symbol from
...
...
from
 table_2 table 

我无法理解 PARTITIONED BY 子句。如果我理解正确,这与 MySQL 表分区不同,并且是 Hive 特定的动态分区。 分区不定义列或键,按当前日期进行分区。

这是否意味着 table_1 是按日期分区的?每天都有单独的分区吗?

然后在后面的代码中有类似于

的符号
inner join table_new table on table.pt = '${yyyymmdd}' and ...

在这种情况下,这是否意味着只有插入到 yyyymmdd 的行被选择用于连接?

谢谢。

【问题讨论】:

  • Hive 中的分区只是一个 HDFS 文件夹

标签: hive create-table hive-partitions hiveddl


【解决方案1】:

Hive 中的分区默认情况下是 HDFS 中的一个文件夹,名称为 key=value + Hive 元存储中的元数据。您可以更改分区位置并在任何文件夹顶部创建分区。

这个PARTITIONED BY (pt STRING) 定义了string 类型的分区列pt,而不是日期。分区值存储在元数据中。 pt 列不存在于表数据文件中,它仅在 PARTITIONED BY 中定义,所有分区值都存储在元数据中。如果您动态加载分区,则会创建名称为 pt='value' 的分区文件夹。

这句话动态创建分区:

INSERT OVERWRITE TABLE table_1 PARTITION (pt) 
select id, price, symbol
       coln as pt            --partition column should be the last one
  from ...

而这句话加载单个STATIC分区:

INSERT OVERWRITE TABLE table_1 PARTITION (pt= '${yyyymmdd}') 
select aa.id,aa.price,aa.symbol 
  from

没有选择分区列,分区值在

PARTITION  (pt= '${yyyymmdd}')

'${yyyymmdd}' 这是一个名为yyyymmdd 的参数,它使用--hivevar 传递给脚本,如下所示:

 hive --hivevar yyyymmdd=20200604 -f myscript.sql 

在这种情况下,您可以将任何字符串作为分区值传递,尽管参数名称 yyyymmdd 表明它的格式。

Hive 中的 BTW 日期格式为 'yyyy-MM-dd' 'yyyy-MM-dd' 格式的字符串可以隐式转换为 DATE。

【讨论】:

  • 非常感谢@leftjoin。我无法理解的是“INSERT OVERWRITE TABLE table_1 PARTITION (pt='20200604') 创建了一个带有 HDFS 文件夹 20200604 的新表。如果我再做一个“INSERT OVERWRITE ... (pt=20200605)”会发生什么?在新文件夹20200605中新建表?20200604文件夹中是否还存在之前的表?
  • 那么 Hive 分区是一种拥有相同架构的多个表的方法,由分区键/值分隔?
  • 它不会创建新表。它在同一个表中创建新分区。默认情况下,分区文件夹位于表文件夹内。但是您可以更改分区并将位置更改为任何其他位置。
  • 这是否意味着“INSERT OVERWRITE TABLE table_1 PARTITION (pt=20200605)”会覆盖在“INSERT OVERWRITE TABLE table_1 PARTITION (pt=20200604)”中创建的数据?假设 pt 是日期?
  • “INSERT OVERWRITE TABLE table_1 PARTITION (pt=20200605)”和“ALTER TABLE table_1 DROP IF EXISTS PARTITION (pt=20200605)”有什么区别?谢谢。
【解决方案2】:

我将尝试一次性解释 Hive 中的分区是什么。首先是

何时使用表分区

  • 表分区适用于:

    • 读取整个数据集耗时过长
    • 查询几乎总是按分区列过滤
    • 分区列有合理数量的不同值
  • ETL 过程的数据生成按文件名或目录名拆分数据

  • 分区列值不在数据本身中
  • 不要对具有许多唯一值的列进行分区
  • 示例:按名字对客户进行分区

创建分区表

要创建分区表,请在 CREATE TABLE 语句中使用 PARTITIONED BY 子句。 必须指定分区列的名称和类型 在 PARTITIONED BY 子句中,并且仅在 PARTITIONED BY 子句中。 它们不得同时出现在所有其他列的列表中。

CREATE TABLE customers_by_country 
        (cust_id STRING, name STRING) 
PARTITIONED BY (country STRING)
ROW FORMAT DELIMITED FIELDS TERMINATED BY '\t';

上面显示的 CREATE TABLE 语句示例创建了表 customers_by_country, 它由名为 country 的 STRING 列分区。 请注意,国家列仅出现在 PARTITIONED BY 子句中, 而不是在它上面的列列表中。 此示例仅指定一个分区列,但您可以通过使用指定多个 PARTITIONED BY 子句中以逗号分隔的列列表。 除了这些特定的差异之外,这个 CREATE TABLE 语句是相同的 作为用于创建等效非分区表的语句。

表分区的实现方式大多是透明的 向使用 Hive 发出查询的用户。 分区列就是所谓的虚拟列,因为它的值不存储在数据文件中。 以下是customers_by_country 上DESCRIBE 命令的结果; 它显示分区列国家,就好像它是表中的普通列一样。 您可以在 SELECT 语句的任何常用子句中引用分区列。

name    type    comment

cust_id string   
name    string   
country string   

您可以动态或静态地在分区表中加载数据

使用动态分区加载数据

将数据加载到分区表中的一种方法是使用动态分区, 它会在您加载数据时使用分区列中的值自动定义分区。 (另一种方法是使用静态分区手动定义分区)

要使用动态分区,您必须使用 INSERT 语句加载数据。 在 INSERT 语句中,您必须使用 PARTITION 子句列出分区列。 您插入的数据必须包含分区列的值。 分区列必须是您要插入的数据中最右边的列, 并且它们的顺序必须与它们在 PARTITION 子句中出现的顺序相同。

INSERT OVERWRITE TABLE customers_by_country 
    PARTITION(country)
    SELECT cust_id, name, country FROM customers;

上面显示的示例使用了 INSERT ... SELECT 语句 使用动态分区将数据加载到 customers_by_country 表中。 请注意,分区列 country 已包含在内 在 PARTITION 子句中,并在 SELECT 列表中最后指定。

Hive 执行此语句时,会自动创建分区 为国家列,并根据国家列中的值将数据加载到这些分区中。 分区子目录中生成的数据文件不包含国家列的值。 由于根据数据文件所在的子目录知道国家/地区, 在数据文件中包含国家值也是多余的。

查看customers_by_country 目录的内容。 它现在应该为国家列中的每个值都有一个子目录。

  1. 查看其中一个目录中的文件。 请注意,该文件包含来自该国家/地区的客户的行, 没有其他人;另请注意,不包括国家/地区值。

注意: Hive 包含一项安全功能,可防止用户 避免意外创建或覆盖大量分区。 (有关此内容的更多信息,请参阅“使用分区的风险”。) 默认情况下,Hive 将属性 hive.exec.dynamic.partition.mode 设置为严格。 这会阻止您使用动态分区,但您仍然可以使用静态分区。

您可以通过设置在 Hive 中禁用此安全功能 属性hive.exec.dynamic.partition.mode 为非严格:

SET hive.exec.dynamic.partition.mode=nonstrict;

然后您可以使用 INSERT 语句动态加载数据。

在 Beeline 中设置的 Hive 属性仅适用于当前会话, 因此,下次您启动 Hive 会话时,此属性将设置回严格。 但如有必要,您或您的系统管理员可以永久配置属性。

当您在分区表上运行一些 SELECT 查询时,如果表足够大,您会注意到运行时间的显着差异。 请注意,您查询该表的方式与查询客户表的方式没有任何不同。

使用静态分区加载数据

将数据加载到分区表中的一种方法是使用静态分区, 您可以在其中手动定义不同的分区。

使用静态分区,您可以使用 ALTER TABLE ... ADD PARTITION 语句手动创建分区, 然后将数据加载到分区中。

例如,这个 ALTER TABLE 语句为巴基斯坦 (pk) 创建分区:

ALTER TABLE customers_by_country
ADD PARTITION (country='pk');

注意分区列名(即国家/地区)以及定义此分区的具体值, 即 pk,都在 ADD PARTITION 子句中指定。 这会在 customers_by_country 表目录中创建一个名为 country=pk 的分区目录。

巴基斯坦分区创建后,您可以使用 INSERT ... SELECT 语句将数据添加到分区中:

INSERT OVERWRITE TABLE customers_by_country 
    PARTITION(country='pk')
    SELECT cust_id, name FROM customers WHERE country='pk'

注意在 PARTITION 子句中,分区列名是国家, 和指定的具体值,即 pk,都指定,就像在用于创建分区的 ADD PARTITION 命令中一样。 另请注意,在 SELECT 语句中,分区列不包含在 SELECT 列表中。 最后,请注意 SELECT 语句中的 WHERE 子句仅选择来自巴基斯坦的客户。

使用静态分区,您需要对每个分区重复这两个步骤: 首先创建分区,然后添加数据。 您实际上可以使用任何方法来加载数据;您不需要使用 INSERT 语句。 您可以改为使用 hdfs dfs 命令或 LOAD DATA INPATH 命令。 但是,无论您如何加载数据,您都有责任确保数据存储在正确的分区子目录中。 例如,巴基斯坦客户的数据必须存储在巴基斯坦分区子目录中, 其他国家/地区客户的数据必须存储在这些国家/地区的分区子目录中。

静态分区在加载数据时最有用 进入表已经根据分区列分为文件, 或者当数据以与分区列一致的方式增长时: 例如,假设您的公司在不同的国家开设了一家新店, 像新西兰 ('nz'),你会得到一个新客户的数据文件,这些客户都来自那个国家。 您可以轻松添加新分区并将该文件加载到其中。

使用分区的风险

使用分区时的一个主要风险是创建的分区会导致您遇到小文件问题。 发生这种情况时,对表进行分区实际上会降低查询性能 (与使用分区时的目标相反)因为它会导致创建太多小文件。 这在使用动态分区时更有可能发生,但它仍然可以 静态分区会发生 - 例如,如果您将新分区添加到销售表 每天包含前一天的销售额, 而且每天的数据都不是特别大。

在选择分区时,您希望在太多分区之间取得平衡 (导致小文件问题)和太少的分区(提供性能的好处很少)。 分区列或列应具有合理数量的值 对于分区 - 但是您应该认为合理的内容很难量化。

使用动态分区特别危险,因为如果你不小心, 在具有太多不同值的列上进行分区很容易。 想象一个用例,您经常在其中查找属于 您将在查询中指定的时间范围。 您可能认为在与时间相关的列上进行分区是个好主意。 但是 TIMESTAMP 列的时间可以达到纳秒,所以每一行都可以有一个唯一的值; 对于分区列来说,这将是一个糟糕的选择!甚至到分钟或小时都可以创造 分区太多,具体取决于数据的性质; 按较大的时间单位(如日、月甚至年)进行分区可能是更好的选择。

作为另一个例子,考虑一个雇员表。 这有五列:empl_id、first_name、last_name、salary 和 office_id。 在继续阅读之前,请考虑一下,其中哪些可能是合理的分区

  • empl_id 列是唯一标识符。 如果那是您的分区列,那么您将为每个员工都有一个单独的分区, 每个人都会有一行。 此外,您不太可能会进行大量查询以查找特定值, 甚至是特定范围的值。这是一个糟糕的选择。
  • first_name 列不会有每个员工一个,但可能会有很多列只有一行。
  • last_name 也是如此。 此外,与 empl_id 一样,您不太可能需要基于这些列的过滤查询。这些也是糟糕的选择。
  • 工资栏也会有很多划分 (如果你的薪水像我们的样本表那样按美分计算,而不是按美元计算,那就更是如此)。 虽然您有时可能想查询工资范围, 您不太可能想要使用个人工资。 所以薪水是一个糟糕的选择。
  • 更有限的salary_grades 规范,如salary_grades 表中的规范, 如果您的用例涉及经常按工资等级查看数据,这可能是合理的。
  • office_id 列标识员工工作的办公室。 即使您有一家在许多城市设有办事处的大公司,它的独特价值也会少得多。 可以想象,您的用例可能是频繁过滤 您的员工数据也基于办公地点。所以这将是一个不错的选择。 您还可以使用多个列并创建嵌套分区。 例如,客户数据集可能包括 country 和 state_or_province 列。 您可以按国家/地区划分,然后按 state_or_province 进一步划分,因此来自安大略省的客户, 加拿大将位于 country=ca/state_or_province=on/ 分区目录中。 这对于您希望按国家或按州或省访问的大量数据非常有帮助。 但是,使用多列会增加创建过多分区的风险,因此在这样做时必须格外小心。

创建过多分区的风险是 Hive 包含该属性的原因 hive.exec.dynamic.partition.mode,默认设置为严格,必须重置为非严格才能创建分区。

当您要动态加载数据时,而不是自动和机械地重置该属性, 以此为契机考虑分区列 并且可能检查加载数据时获得的唯一值的数量。

仅此而已。

【讨论】:

  • 非常感谢您的详细解释。这是第一次使用 Hive,并且很难围绕分区表示法思考 MySQL 中的分区。现在我意识到 Hive 中的分区与 MySQL 中的分区无关。谢谢。
  • 不客气 :),是的!但正如我在帖子中所说的那样,在 Hive 中进行分区要小心。另一方面,我在这篇文章中为 Hive 编写的内容与 Impala 几乎相同,因此如果您需要更快地运行查询,您可以在 Hive 或 Impala 中创建分区表,然后使用 Impala 运行查询。
  • 非常感谢 Chema 提供的信息。欣赏它。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多