了解 Hive table 创建符号

Understanding Hive table creation notation

我遇到了 Hive tables,我需要将其转换为 Redshift/MySql 等价物。 我在理解 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 table 分区,并且是 Hive 特定的动态分区。 分区未定义列或键,按当前日期分区。

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

然后在代码中有类似于

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

在这种情况下,这是否意味着只选择在 yyyymmdd 上插入的行进行连接?

谢谢。

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

PARTITIONED BY (pt STRING) 定义类型为 string 的分区列 pt,而非日期。分区值存储在元数据中。 pt 列不存在于 table 数据文件中,它仅在 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 ...

这句话加载单个静态分区:

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 表明它是格式。

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

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

何时使用 TABLE 分区

  • Table partitioninig 在以下情况下很好:

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

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

正在创建分区 TABLES

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

的语句

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

name    type    comment

cust_id string   
name    string   
country string   

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

正在使用动态分区加载数据

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

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

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

上面显示的示例使用了 INSERT … SELECT 语句 使用动态分区将数据加载到 customers_by_country table 中。 请注意,包含分区列国家/地区 在 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 会话时,这个 属性 将被设置回严格。 但您或您的系统管理员可以根据需要永久配置属性。

当您 运行 在分区 table 上进行一些 SELECT 查询时,如果 table 足够大,您会注意到 table 所花费的时间存在显着差异=203=]。 请注意,您查询 table 与查询客户 table.

没有任何不同

使用静态分区加载数据

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

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

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

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

注意分区列名,也就是国家,以及定义这个分区的具体值, 这是 pk,都在 ADD PARTITION 子句中指定。 这会在 customers_by_country table 目录中创建一个名为 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 命令。 但是无论您加载数据如何,您都有责任确保数据存储在正确的分区子目录中。 例如,巴基斯坦客户的数据必须存储在巴基斯坦分区子目录中, 和其他国家客户的数据必须存储在这些国家的分区子目录中。

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

使用分区的风险

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

选择分区时,您想在太多分区之间取得平衡 (导致小文件问题)和分区太少(提供性能好处不大)。 分区列或列应具有合理数量的值 对于分区——但你认为合理的东西很难量化。

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

再举一个例子,假设一名员工 table。 这有五个列:empl_id、first_name、last_name、薪水和 office_id。 在继续阅读之前,想一想,其中哪些可能适合分区

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

创建太多分区的风险是 Hive 包含 属性 的原因 hive.exec.dynamic.partition.mode,默认设置为strict,创建分区前必须重置为nonstrict。

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

仅此而已。