4

我遇到了需要转换为 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为连接选择插入的行?

谢谢你。

4

2 回答 2

2

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

这定义了stringPARTITIONED BY (pt STRING)类型的分区列 pt ,而不是 date。分区值存储在元数据中。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。

于 2020-06-04T07:30:19.713 回答
1

我将尝试一次性解释 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 发出查询的用户几乎是透明的。分区列就是所谓的虚拟列,因为它的值不存储在数据文件中。以下是DESCRIBEcustomers_by_country 命令的结果;它显示分区列国家,就好像它是表中的普通列一样。您可以在 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 执行该语句时,它会自动为 country 列创建分区,并根据 country 列中的值将数据加载到这些分区中。分区子目录中生成的数据文件不包含国家列的值。由于根据数据文件所在的子目录知道国家/地区,因此将国家/地区值也包含在数据文件中将是多余的。

查看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 一样,您不太可能需要基于这些列的过滤查询。这些也是糟糕的选择。
  • 列工资也将有许多部门(如果您的工资按美分计算,而不是像我们的示例表那样按美元计算,则更是如此)。虽然您有时可能想要查询薪水范围,但您不太可能想要使用个人薪水。所以薪水是一个糟糕的选择。
  • 如果您的用例涉及经常按薪水等级查看数据,则更有限的薪水等级规范(如薪水等级表中的规范)可能是合理的。
  • office_id 列标识员工工作的办公室。即使您有一家在许多城市设有办事处的大公司,它的独特价值也会少得多。可以想象,您的用例也可能是经常根据办公室位置过滤您的员工​​数据。所以这将是一个不错的选择。您还可以使用多个列并创建嵌套分区。例如,客户数据集可能包括 country 和 state_or_province 列。您可以按国家/地区分区,然后按 state_or_province 进一步分区,因此来自加拿大安大略省的客户将位于 country=ca/state_or_province=on/ 分区目录中。这对于您希望按国家或按州或省访问的大量数据非常有帮助。然而,

创建太多分区的风险是为什么 Hive 包含属性 hive.exec.dynamic.partition.mode,默认设置为 strict ,必须先将其重置为 nonstrict 才能创建分区。

当您要动态加载数据时,与其自动和机械地重置该属性,不如借此机会考虑分区列,并检查加载数据时获得的唯一值的数量。

就这样。

于 2020-06-04T09:54:56.430 回答