0%

Impala数据类型的一些Tips

Impala只支持有限的数据隐式转换。
1、Impala不会隐式的进行String到numeric 或 Boolean类型的数据转换。
2、支持低精度到高精度数字的隐式转换,但是不支持高精度到低精度的隐式转换。
3、支持String到Timestamp的隐式转换

BIGINT

8字节整型数据类型。范围: -9223372036854775808 .. 9223372036854775807。可以通过 MIN_BIGINT()MAX_BIGINT() 来查看BIGINT的边界。

语法: column_name BIGINT
类型转换: 可以自动转换为 FLOATDOUBLECAST() 函数可以显式的转换为 TINYINTSMALLINTINTSTRINGTIMESTAMP .
默认情况下,转换的timestamp时间默认为UTC时区。 ‑‑use_local_tz_for_unix_timestamp_conversions=true 则为本地时区。
1、CAST转换任何非数值的值为bigint时,返回NULL
2、用于分区,效率会比string更高
3、与HBASE完全兼容

BOOLEAN

TRUEFALSE 两个取值范围。
语法: column_name BOOLEAN
impala不会对任何数据类型转换为布尔型。
cast非0值都是true, cast 0值都是false。
不能将 STRING 转换成 BOOLEAN , 但是可以将 BOOLEAN 转换成 STRING 的 0 和 1。
不能用作分区键的类型。

CHAR

固定长度数据类型,如果插入数据的长度不够,将用空格填充(不会存在数据文件中,仅仅查询的时候填充);如果超出长度,会截断超出的数据。可以定义的最大长度255。
语法: column_name CHAR(length)
1)当存储的数据小于定义的数据长度时,查询结果会在末尾添加空格补齐。
2)字符开头的空格会保存在数据文件中
3)两个Char数据类型的数据,如果只是末尾空格不同,那么这两个数据的比较结果相同
4)可以作为分区键,但是效率比数值型的分区键低
CHAR_LENGTH() 包含末尾空格的长度; LENGTH() 不包含末尾空格的长度。
不可用于HBASE
Parquettext 格式的数据文件中, 保存超过定义长度的字符时,数据文件会保存所有的字符,但是impala查询时,会截断到定义的长度。
char 类型并没有获得impala的编译支持,所以性能比 stringvarchar

DECIMAL

固定范围和精度的数据类型。
语法: DECIMAL[(precision[, scale])]
precision确定了整个数据的数字总数,范围为1到38,默认为9。
scale确定了小数位数,默认为0。

Precision决定了占用的内存大小

Text, RCFile和SequenceFile以基于ASCII格式保存:
1、开始的0不会保存,末尾的0会保存
2、小数点和负数都会多一个额外的字节
Parquet和Arvo格式的表采用二进制格式保存

关于decimal类型的计算
如果产生的精度大于38, 那么impala会截断后面的部分,但是至少保留6位小数。

使用函数时,精度和小数位的情况:
1、 SUM 精度为38,小数位与输入的列保持一致
2、 AVG 精度为38,小数位max(Scale of input column, 6)
3、 UNION 精度- max (L1, L2) + max (S1, S2),小数位max (S1, S2)

其他类型CAST为decimal时,如果精度不够,会报错。小数位不够会进行截断。
以下情况会进行DECIMAL到其它数据类型的隐式转换:
1) DECIMALdouble 或者 float 的转换,但是反过来不行。
2)低精度的 DECIMAL 可以隐式的转换为高精度的 DECIMAL
3)精度足够的前提下, 整数可以隐式的转换为 DECIMAL

字面量和表达式

1、没有小数位的数字。优先当做最小的整型类型,如果整型不够,则作为DECIMAL,超过DECIMAL(38, 0)的时候,当做DOUBLE类型。
2、带数字的数值。小于38位,当做DECIMAL类型;否则作为DOUBLE类型
3、Decimal支持指数计数法的数值

关于精度的修改
Impala可以通过 ALTER TABLE ... REPLACE COLUMNS 来修改列的精度。
1)对于基于文本的文件格式(Text, RCFile, SequenceFile)
* 如果修改后的精度可以匹配列中的值,则修改成功
* 如果不匹配,当 ABORT_ON_ERROR=true 修改操作会返回错误;如果 ABORT_ON_ERROR=flase Impala会返回 NULL 值,并且给出警告信息。
2)对于二进制文件格式(Parquet和Avro)
修改语句会成功,但是在查询修改后的列的时候会抛出错误信息。因为这些文件的元数据信息是保存在文件内部的。

DOUBLE

浮点数据类型。real 别名。
语法: column_name DOUBLE
数据范围:4.94065645841246544e-324d .. 1.79769313486231570e+308的正数或者负数。
15到17个有效数据位,占8个字节。
只能通过CAST显式的将其转换为其他数据类型。

FLOAT

浮点数据类型。
语法: column_name FLOAT
数据范围:1.40129846432481707e-45 .. 3.40282346638528860e+38的正数或者负数
6到9个有效数据位,占4个字节。
可以自动的从 FLOATDOUBLE 进行转换,转换成其他数据类型需要用CAST显式转换。

INT

4字节的整型数字。INTEGER 别名。
语法: column_name INT
数据范围: -2147483648 .. 2147483647
可以自动的转换为 BIGINTDOUBLEFLOAT ,其他数据类型需要用CAST显式转换。
可以通过 min_int()max_int() 来检查int的数据边界。
把一个非数字的值转换为INT, 会返回空

SMALLINT

2字节的整型数值。
语法: column_name SMALLINT
数据范围:-32768 .. 32767
可以自动的转换为 BIGINTINT 和浮点数据类型,其他数据类型需要用CAST显式转换。
可以通过 min_smallint()max_smallint() 来查看数据边界。
把一个非数字的值转换为SMALLINT, 会返回空。

TINYINT

1字节的整型数值
语法: column_name TINYINT
数据范围:128 .. 127
可以自动的转换为 SMALLINTBIGINTINT 和浮点数据类型,其他数据类型需要用CAST显式转换。
可以通过 min_tinyint()max_tinyint() 来查看数据边界。
把一个非数字的值转换为TINYINT, 会返回空。

STRING

string相较于varchar和char拥有更好的性能。

语法: column_name STRING

string的长度需要考虑以下因素:
1)包含string的数据行,最大2GB
2)Parquet文件,STRING最大1GB
3)小于等于32KB的字符串查询不会存在显著的性能问题和内存问题,当大于32KB时,性能会显著的下降。
4)行大小会受到 spill-to-disk 支持的限制:

  a. hash-join中来着右边的行
  b. spills to disk的hash-join两边的行
  c. 行被没有限制的 `SORT` 操作排序
  d. 分组聚合中
  CDH 5.12以及以下版本限制是8MB,以上版本受 `MAX_ROW_SIZE` 的查询参数控制,默认512KB

字符集:
为了支持所有的impala子系统,字符集被限制为ASCII。虽然也可以保存UTF-8的字符集,但是在部分情况下不能保证正常工作:
1)字符串操作函数
2)比较操作
3) ORDER BY 子句
4)作为分区键

类型转换
1)impala不会自动的转换string为数值类型,可以自动转换为 TIMESTAMP
2)通过CAST()强制转换成数值类型
3)cast无法转换string为boolean
4)可以cast一个boolean为string,true返回1,false返回0

TIMESTAMP

保存日期和时间值,没有时区信息。占用16个字节。

语法:

1
2
3
4
column_name TIMESTAMP

timestamp [+ | -] INTERVAL interval
DATE_ADD (timestamp, INTERVAL interval time_unit)

数据范围: 1400-01-01 to 9999-12-31, 超出范围返回NULL

INTERVAL表达式:

timestamp_value + INTERVAL 3 WEEKS - INTERVAL 1 DAY + INTERVAL 10 MICROSECONDS

  • YEAR[S]
  • MONTH[S]
  • WEEK[S]
  • DAY[S]
  • HOUR[S]
  • MINUTE[S]
  • SECOND[S]
  • MILLISECOND[S]
  • MICROSECOND[S]
  • NANOSECOND[S]

时区:

默认情况下,impala存储和解释的timestamp为UTC时区。
当设置启动参数 -use_local_tz_for_unix_timestamp_conversionsTRUE 时,将视作本地时区。

Impala和HIVE对时区的处理
根据文件格式的不同,处理方式不一样
1)Text
Impala和Hive处理相同,不会进行时区转换。
2)Parquet
当Hive写数据文件时,会把本地机器的时区时间转换成UTC时间进行写入。但是Impala不会,默认情况下读写都不会进行时区的转换。这种处理差异,可能会导致Hive和Impala的查询结果不一致。
为了解决不兼容问题或者必须通过编码处理,可以通过设置以下参数来避免:
• --use_local_tz_for_unix_timestamp_conversions=true
• --convert_legacy_hive_parquet_utc_timestamps=true 这个参数可以使impala识别到Hive写入的数据,并执行UTC到local-time-zone的转换

类型转换
字符串可以自动的转换成timestamp类型。当转换一个不能识别的字符串时,会返回空值。

VARCHAR

可变长度的字符类型,最大长度为65,535。当超出范围的时候会被截断。
语法: column_name VARCHAR(max_length)

Complex Types

Impala 2.3开始,Impala支持 ARRAY、MAP、STRUCT复杂类型,暂不支持HIVE的UNION类型。目前Impala只能查询其他组件产生的复杂数据类型列。

概述

ArrayMap 保存任意数量相同类型的元素;Struct 将固定数量的元素保存成一个元素,每个 field 的类型可以不同。
所有的复合数据类型可以嵌套其他复合数据类型,最多可以嵌套100层。

使用场景

1)由Hive或者其他组件产生的复杂数据类型列,将其转换成Parquet类型的数据文件,然后在Impala中进行处理。
2)由非SQL的编程语言或者NoSQL系统产生的类似复杂数据类型的数据。
3)在对多表的分析处理中,可以从数据本地化中获得巨大收益。将相关数据打包到复杂数据类型中,可以避免在join查询时网络传输带来的性能消耗。

使用的注意事项

1、与传统数据仓库模式的差异
传统的数据仓库在处理相关数据时一般有两种方案:
1)将其拆分到不同的规范化表中,这样可以避免重复数据,但是join操作会带来比较大的性能消耗
2)同一个表中增加列或者行来存储,这样性能比较好,但是可能带来重复数据的存储,以及在不确定数据量的情况,不好确定宽表的列数量。
复杂数据类型在性能和存储空间上提供了一个折中的选项,在物理上,相关的数据会在一个数据块上,避免了join带来的网络消耗。

2、物理存储
物理上,每行标量数据类型和复杂数据类型的列都存储在相邻位置,以确保他们能在同一主机上处理,避免数据传输带来的消耗。得益于Parquet文件的列式存储,对复杂数据类型的访问,也只会获取指定标量列的结果,避免了大量的IO消耗。
1)STRUCT数据类型的每个filed都像列一样存储,所有标量值都彼此相邻,并使用Parquet的space-saving技术对其进行编码、压缩。
2)ARRAY数据类型,所有值都相邻存储,使用伪列 ITEM 表示。 POS 伪列并不会进行物理的存储。
3)MAP数据类型,所有的伪列 KEYVALUE 都存储在相邻位置。

3、支持的文件格式
目前仅Parquet文件格式支持复杂数据类型。对于其他文件格式,如果包含嵌套的复杂数据类型,查询的时候会报错。甚至DROP掉这个列,在查询的时候依然会报错。唯一的列外就是可以在RCFILE上执行count(*)的操作。

4、复杂数据类型和规范表的选择
选择拆分为多个规范化表或者使用复杂数据类型,取决于很多因素:
1)如果是有传统数据库或者数仓背景,已有的工具更擅长处理规范化表,这个时候就更加倾向选择拆分为规范表。
2)复杂数据类型可以简化与Python、scala和Java的数据交互
3)现有的基础架构设施中产生的数据包含复杂类型结构,使用复杂数据类型可以简化这些数据的操作。
4)当数据中部分列需要变更时,包含复杂数据类型的列需要全部重新加载。如果经常涉及到这样的数据变更,拆分为规范化表更合适。
5)Parquet的列式存储、相邻数据存储在同一数据块、避免join操作,这些优势可以让查询性能更好。但是包含复杂数据类型的表会让同一数据块中包含更少的行,如果很少使用到复杂数据类型的列,那么应该避免这种存储方式。

5、与Hive中复杂数据类型的差异
1)由于Impala中的 STRUCT 列包含用户自定义的filed名称,Hive在往impala表中写入STRUCT列时使用 NAMED_STRUCT 函数进行构造。
2)Impala暂不支持hive中的 UNION 数据类型。
3)Impala中使用点和伪列名称或者field名称搭配join来引用复杂数据类型,HIVE中使用 LATERAL VIEWEXPLOED() 来展开数据。

6、复杂类型的一些限制
1)只能用在Parquet格式的表或者分区中。并且Impala不能向其中写入数据。
2)不能作为分区列
3)不能作为 ORDER BY , HAVINGGROUP BYWHERE 子句中,必须引用其中的标量值。
4)复杂数据类型的嵌套,最多100层
5)列名的定义最多4000个字符
6)使用小的或者中等大小的列,这些数据一般放在同一个数据块中,而HDFS的数据块大小一般为256MB到1GB
7)包含复杂数据类型列的表会增加额外的查询开销,哪怕查询结果没有查询复杂数据类型列
8) COMPUTE STATS 不会收集复杂数据类型列的统计信息
9)内置函数和自定义函数都不接受复杂数据类型列作为参数

ARRAY Complex Type

存放任意数量的有序元素。其中存储的可以是基本数据类型,也可是复杂数据类型。

语法

1
2
3
column_name ARRAY < type >

type ::= primitive_type | complex_type

type可以是其他标量类型或者复合类型。使用 ITEM or POS 伪列来访问其中的数据。
当Array中的数据是基本数据类型的时候,可以对其中的元素使用聚合函数,例如 count sum max 等。
1)例子:建表

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
CREATE TABLE array_demo
(
id BIGINT,
name STRING,
-- An ARRAY of scalar type as a top-level column.
pets ARRAY <STRING>,

-- An ARRAY with elements of complex type (STRUCT).
places_lived ARRAY < STRUCT <
place: STRING,
start_year: INT
>>,

-- An ARRAY as a field (CHILDREN) within a STRUCT.
-- (The STRUCT is inside another ARRAY, because it is rare
-- for a STRUCT to be a top-level column.)
marriages ARRAY < STRUCT <
spouse: STRING,
children: ARRAY <STRING>
>>,

-- An ARRAY as the value part of a MAP.
-- The first MAP field (the key) would be a value such as
-- 'Parent' or 'Grandparent', and the corresponding array would
-- represent 2 parents, 4 grandparents, and so on.
ancestors MAP < STRING, ARRAY <STRING> >
)
STORED AS PARQUET;

2)例子:查看表和复杂数据类型的结构

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
DESCRIBE array_demo;
+--------------+---------------------------+
| name | type |
+--------------+---------------------------+
| id | bigint |
| name | string |
| pets | array<string> |
| marriages | array<struct< |
| | spouse:string, |
| | children:array<string> |
| | >> |
| places_lived | array<struct< |
| | place:string, |
| | start_year:int |
| | >> |
| ancestors | map<string,array<string>> |
+--------------+---------------------------+

DESCRIBE array_demo.pets;
+------+--------+
| name | type |
+------+--------+
| item | string |
| pos | bigint |
+------+--------+

DESCRIBE array_demo.marriages;
+------+--------------------------+
| name | type |
+------+--------------------------+
| item | struct< |
| | spouse:string, |
| | children:array<string> |
| | > |
| pos | bigint |
+------+--------------------------+

DESCRIBE array_demo.places_lived;
+------+------------------+
| name | type |
+------+------------------+
| item | struct< |
| | place:string, |
| | start_year:int |
| | > |
| pos | bigint |
+------+------------------+

DESCRIBE array_demo.ancestors;
+-------+---------------+
| name | type |
+-------+---------------+
| key | string |
| value | array<string> |
+-------+---------------+

3)例子:查询

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
-- 包含基本数据类型的Array
-- 通过POS伪列获得索引位置,ITEM伪列获取值
SELECT id, name, pets.pos, pets.item FROM array_demo, array_demo.pets;

-- Array of structs.
-- 通过item获取到struct结构,再引用struct中的field name
SELECT id, name, places_lived.pos, places_lived.item.place, places_lived.item.start_year
FROM array_demo, array_demo.places_lived;

-- 上例中的 .item 是可选的,也可以直接引用struct中的field name
SELECT id, name, places_lived.pos, places_lived.place, places_lived.start_year
FROM array_demo, array_demo.places_lived;

-- 通过pos过滤数据
SELECT id, name, pets.item FROM array_demo, array_demo.pets
WHERE pets.pos in (0, 1, 3);

-- 通过item过滤数据
SELECT id, name, pets.item FROM array_demo, array_demo.pets
WHERE pets.item LIKE 'Mr. %';

-- 通过struct中的field过滤数据
SELECT id, name, places_lived.pos, places_lived.place, places_lived.start_year
FROM array_demo, array_demo.places_lived
WHERE places_lived.place like '%California%';

MAP

保存任意数量的key-value数据对。其中key必须是基本数据类型,value可以是基本数据类型也可以是复杂数据类型。Impala不会对key进行强制性的唯一约束。

语法:

1
2
3
column_name MAP < primitive_type, type >

type ::= primitive_type | complex_type

建表示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
create TABLE map_demo
(
country_id BIGINT,

-- Numeric facts about each country, looked up by name.
-- For example, 'Area':1000, 'Population':999999.
-- Using a MAP instead of a STRUCT because there could be
-- a different set of facts for each country.
metrics MAP <STRING, BIGINT>,

-- MAP whose value part is an ARRAY.
-- For example, the key 'Famous Politicians' could represent an array of 10 elements,
-- while the key 'Famous Actors' could represent an array of 20 elements.
notables MAP <STRING, ARRAY <STRING>>,

-- MAP that is a field within a STRUCT.
-- (The STRUCT is inside another ARRAY, because it is rare
-- for a STRUCT to be a top-level column.)
-- For example, city #1 might have points of interest with key 'Zoo',
-- representing an array of 3 different zoos.
-- City #2 might have completely different kinds of points of interest.
-- Because the set of field names is potentially large, and most entries could be blank,
-- a MAP makes more sense than a STRUCT to represent such a sparse data structure.
cities ARRAY < STRUCT <
name: STRING,
points_of_interest: MAP <STRING, ARRAY <STRING>>
>>,

-- MAP that is an element within an ARRAY. The MAP is inside a STRUCT field to associate
-- the mountain name with all the facts about the mountain.
-- The "key" of the map (the first STRING field) represents the name of some fact whose value
-- can be expressed as an integer, such as 'Height', 'Year First Climbed', and so on.
mountains ARRAY < STRUCT < name: STRING, facts: MAP <STRING, INT > > >
)
STORED AS PARQUET;

STRUCT

逻辑上把多个field的数据放在一起。通常作为ARRAY或者MAP的值。

语法:

1
2
3
column_name STRUCT < name : type [COMMENT 'comment_string'], ... >

type ::= primitive_type | complex_type

建表示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
CREATE TABLE struct_demo
(
id BIGINT,
name STRING,

-- A STRUCT as a top-level column. Demonstrates how the table ID column
-- and the ID field within the STRUCT can coexist without a name conflict.
employee_info STRUCT < employer: STRING, id: BIGINT, address: STRING >,

-- A STRUCT as the element type of an ARRAY.
places_lived ARRAY < STRUCT <street: STRING, city: STRING, country: STRING >>,

-- A STRUCT as the value portion of the key-value pairs in a MAP.
memorable_moments MAP < STRING, STRUCT < year: INT, place: STRING, details: STRING >>,

-- A STRUCT where one of the fields is another STRUCT.
current_address STRUCT < street_address: STRUCT <street_number: INT, street_name: STRING, street_type: STRING>, country: STRING, postal_code: STRING >
)
STORED AS PARQUET;

查询示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
SELECT id, employee_info.id AS employee_id FROM struct_demo;

SELECT id, employee_info.id AS employee_id, employee_info.employer
FROM struct_demo;

SELECT id, name, street, city, country
FROM struct_demo, struct_demo.places_lived;

SELECT id, name, places_lived.pos, places_lived.street, places_lived.city, places_lived.country
FROM struct_demo, struct_demo.places_lived;

SELECT id, name, pl.pos, pl.street, pl.city, pl.country
FROM struct_demo, struct_demo.places_lived AS pl;

SELECT id, name, places_lived.pos, places_lived.street, places_lived.city, places_lived.country
FROM struct_demo, struct_demo.places_lived;

SELECT id, name, pos, street, city, country
FROM struct_demo, struct_demo.places_lived;

SELECT id, name, memorable_moments.key,
memorable_moments.value.year,
memorable_moments.value.place,
memorable_moments.value.details
FROM struct_demo, struct_demo.memorable_moments
WHERE memorable_moments.key IN ('Birthday','Anniversary','Graduation');

SELECT id, name, mm.key, mm.value.year, mm.value.place, mm.value.details
FROM struct_demo, struct_demo.memorable_moments AS mm
WHERE mm.key IN ('Birthday','Anniversary','Graduation');

SELECT id, name, memorable_moments.key, memorable_moments.value.year,
memorable_moments.value.place, memorable_moments.value.details
FROM struct_demo, struct_demo.memorable_moments
WHERE key IN ('Birthday','Anniversary','Graduation');

SELECT id, name, key, value.year, value.place, value.details
FROM struct_demo, struct_demo.memorable_moments
WHERE key IN ('Birthday','Anniversary','Graduation');

SELECT id, name, key, year, place, details
FROM struct_demo, struct_demo.memorable_moments
WHERE key IN ('Birthday','Anniversary','Graduation');

SELECT id, name,
current_address.street_address.street_number,
current_address.street_address.street_name,
current_address.street_address.street_type,
current_address.country,
current_address.postal_code
FROM struct_demo;