Mở đầu

Apache Hive là một kho lưu trữ dữ liệu (data warehouse) giúp xử lý các tập dữ liệu lớn lưu trữ phân tán trên nền tảng Hadoop. Hive dùng SQL nên dễ tiếp cận với đa số mọi người (đã quá quen thuộc với SQL).

Trong quá trình làm việc với Hive, chủ yếu mình thường dùng các kiểu dữ liệu cơ bản như

  • Numeric Types
  • Date/Time Types
  • String Types
  • Boolean Types

Nhưng, trong dự án gần đây thì mình có dùng đến Complex Types của Hive. Sau đây là 1 vài tóm tắt về nó.

I. Complex Types

Complex Types của Hive bao gồm các kiểu dữ liệu sau

  • arrays: ARRAY<data_type>
  • maps: MAP<primitive_type, data_type>
  • structs: STRUCT<col_name : data_type [COMMENT col_comment],...>
  • union: UNIONTYPE<data_type, data_type,...>

1. Array

Built-in Operators

Đầu tiên ta sẽ thử với kiểu Array. Ta tạo 1 table để test với kiểu dữ liệu của các phần tử là 4 kiểu cơ bản. Constructor Function của array có dạng array(val1, val2,...) sẽ tạo 1 array với các phần tử truyền vào. Dùng nó để chèn dữ liệu test vào.

CREATE TABLE array_test(
  col_a array<int>
  ,col_b array<string>
  ,col_c array<timestamp>
  ,col_d array<boolean>
);

INSERT INTO TABLE array_test
VALUES (
  array(1,2,3)
  ,array("c", "b", "a")
  ,array(current_timestamp)
  ,array(true, false)
);

Lỗi !!!

FAILED: SemanticException [Error 10293]: Unable to create temp file for insert values 
Expression of type TOK_FUNCTION not supported in insert/values

Nguyên nhân là do ta không thể chèn trực tiếp Complex Types trực tiếp vào 1 bảng Hive. Ngoài các chèn trực tiếp bằng cách dùng insert/values ta còn có thể dùng chèn thông qua insert/select dữ liệu từ một bảng khác

INSERT INTO TABLE tablename VALUES values_row [, values_row ...]
INSERT INTO TABLE tablename select_statement FROM from_statement

Ta sẽ tạo 1 dummy table cho mục đích này. Và thử lại việc chèn dữ liệu

CREATE TABLE dummy(a string);

# Cần chèn 1 row dữ liệu cho dummy table này
INSERT INTO TABLE dummy VALUES ('a');

# Nếu không select_statement FROM dummy ở câu querry phía dưới sẽ không hoạt động như mong muốn
INSERT INTO TABLE array_test
SELECT
  array(1,2,3)
  ,array("c", "b", "a")
  ,array(current_timestamp)
  , array(true, false)
FROM dummy;

Query ID = mendy_20161106225212_b82ff420-e431-43b9-b283-1ef0d4db1a8a
Total jobs = 1
Launching Job 1 out of 1

Status: Running (Executing on YARN cluster with App id application_1472439789844_24559)

--------------------------------------------------------------------------------
        VERTICES      STATUS  TOTAL  COMPLETED  RUNNING  PENDING  FAILED  KILLED
--------------------------------------------------------------------------------
Map 1 ..........   SUCCEEDED      1          1        0        0       0       0
--------------------------------------------------------------------------------
VERTICES: 01/01  [==========================>>] 100%  ELAPSED TIME: 0.61 s
--------------------------------------------------------------------------------
Loading data to table test_db.array_test
Table test_db.array_test stats: [numFiles=1, numRows=1, totalSize=47, rawDataSize=46]
OK
Time taken: 1.356 seconds
# Kiểm tra lại
hive> select * from array_test;
OK
[1,2,3] ["c","b","a"]   ["2016-11-06 22:52:12.393"]     [true,false]
Time taken: 0.68 seconds, Fetched: 1 row(s)

Vậy truy cập các element trong array như thế nào? Toán tử A[n] trả về phần tử thứ n trong array. Phần tử đầu tiên có index là 0.

hive> select col_a[1], col_b[2], col_c[3], col_d[0] from array_test;
OK
2       a       NULL    true

# Nếu index > size của array thì giá trị trả về là NULL
Built-in Functions

Dưới đây là 1 số hàm được Hive hỗ trợ

  • size: Trả về số phần tử của array
  • array_contains: Kiểm tra xem array có chứa giá trị truyền vào không
  • sort_array: Sắp xếp array theo thứ tự tăng dần
# Test size
> select size(col_a), size(col_b), size(col_c), size(col_d) from array_test;
OK
3       3       1       2

# Test array_contains
> select array_contains(col_a, 1), array_contains(col_b, "d") from array_test;
OK
true    false

# Test sort_array
> select sort_array(col_a), sort_array(col_b) from array_test;
OK
[1,2,3] ["a","b","c"]

2. Map

Built-in Operators

Tương tự ta sẽ tạo table và thử constructor của Map: map(key1, value1, key2, value2,...) và cách truy cập phần tử của map bằng toán tử M[key]

# Tạo table test
CREATE TABLE map_test(
  col_a map<string, string>
  ,col_b map<string, string>
);

# Chèn dữ liệu test
INSERT INTO TABLE map_test
SELECT
  map("a_key", "a_val", "b_key", "b_val", "c_key", "c_val")
  ,map("3_key", "3_val", "2_key", "2_val", "1_key", "1_val")
FROM dummy;

# Check
> select * from map_test;
OK
{"a_key":"a_val","b_key":"b_val","c_key":"c_val"}     {"3_key":"3_val","4_key":"4_val","2_key":"2_val","1_key":"1_val"}

# Truy cập phần tử của map
# Nếu map không chứa key đó thì sẽ trả về NULL
>  select col_a["a_key"], col_b["1_key"], col_b["a_key"] from map_test;
OK
a_val   1_val   NULL
Built-in Functions

Dưới đây là 1 số hàm của Map

  • size: Trả về số phần tử của Map
  • map_key: Trả về array gồm các key của Map
  • map_values: Trả về array gồm các value của Map
# Test size
> select size(col_a), size(col_b) from map_test;
OK
3       4

# Test map_keys
> select map_keys(col_a), map_keys(col_b) from map_test;
OK
["a_key","b_key","c_key"]       ["3_key","4_key","2_key","1_key"]

# Test map_values
> select map_values(col_a), map_values(col_b) from map_test;
OK
["a_val","b_val","c_val"]       ["3_val","4_val","2_val","1_val"]

3. Struct

Với struct thì các hàm built-in của nó không có nhiều. Ít hơn hẳn so với map và array.

# Tạo table test
CREATE TABLE struct_test(
  col_a struct<a: string, b: string>
  ,col_b struct<c: int, d: int>
);

# Chèn dữ liệu test
# Có 2 constructor:
# 1. struct(val1, val2, val3, ...) -> tên của các trường sẽ là col1, col2, ....
# 2. named_struct(name1, val1, name2, val2, ...)
INSERT INTO TABLE struct_test
SELECT
  named_struct("a", "a_val", "b", "b_val")
  ,named_struct("c", 1, "d", 2)
FROM dummy;

> SELECT struct("a_val", "b_val") FROM dummy;
OK
{"col1":"a_val","col2":"b_val"}

# Check
> select * from struct_test;
OK
{"a":"a_val","b":"b_val"}       {"c":1,"d":2}

# Truy cập phần tử của struct
> select col_a.a, col_b.d from struct_test;
OK
a_val   2

4. Union

# Tạo table test
CREATE TABLE union_test(
  col_a UNIONTYPE<int, boolean, array<string>, map<string, string>>
)

# Chèn dữ liệu test
# create_union(tag, val1, val2, ...)
# tag nhằm chỉ định phần dữ liệu của union, bắt đầu từ 0
# Ví dụ: tag=0 -> int, tag=2 -> array<string>
INSERT INTO TABLE union_test
SELECT
  create_union(0, 1, true, array("a", "b"), map("a_key", "a_val", "b_key", "b_val"))
FROM dummy;

INSERT INTO TABLE union_test
SELECT
  create_union(1, 1, true, array("a", "b"), map("a_key", "a_val", "b_key", "b_val"))
FROM dummy;

INSERT INTO TABLE union_test
SELECT
  create_union(2, 1, true, array("a", "b"), map("a_key", "a_val", "b_key", "b_val"))
FROM dummy;

INSERT INTO TABLE union_test
SELECT
  create_union(3, 1, true, array("a", "b"), map("a_key", "a_val", "b_key", "b_val"))
FROM dummy;

# Check
hive> select * from union_test;
OK
{0:1}
{1:true}
{2:["a","b"]}
{3:{"a_key":"a_val","b_key":"b_val"}}

Việc dùng create_union để chèn dữ liệu như trên không hiệu quả cho lắm. Tuy nhiên trên thực tế, với union việc load dữ liệu từ file vào thì rất dễ dàng. Tuy nhiên, với các Complex Types thì union chưa được như mong muốn.

# Tạo table test
CREATE TABLE union_test(
  col_a UNIONTYPE<int, boolean, array<string>, map<string, string>>
)
row format delimited
fields terminated by ','
COLLECTION ITEMS TERMINATED BY '|'
map keys terminated by '#'
LINES TERMINATED BY '\n';

# Tạo file dữ liệu
> vi unionfile
0|1
1|true
2|a|b|c
3|a_key#a_val|b_key#b_val,
0|2
1|false

# Load dữ liệu từ file
load data local inpath 'unionfile' overwrite into table union_test2;

# Check
hive> select * from union_test2;
OK
{0:1}
{1:true}
{2:["a|b|c"]}
{3:{"a_key":null,"a_val|b_key":null,"b_val":null}}
{0:2}
{1:false}

II. Vấn đề của Complex Types

Complex Types hiện tại đang khá ít Built-in Operators và Built-in Functions được hỗ trợ từ Hive. Do đó sẽ gặp khó khăn khi xử lý các dữ liệu kiểu này. Ví dụ trong dự án hiện tại, với kiểu dữ liệu Map<String, String> khi ta muốn thay đổi 1 key trong các key của Map đó, thì Hive chưa có hàm hỗ trợ việc này.

Ta có thể thay đổi bằng cách hard coding key như sau:

# Tạo table test
CREATE TABLE map_change_key(
  col_a map<string, string>
);
 
# Chèn dữ liệu test
INSERT INTO TABLE map_change_key
SELECT
  map("a_key", "a_val", "b_key", "b_val", "c_key", "c_val")
FROM dummy;

# List danh sách key
SELECT sort_array(map_keys(col_a)) FROM map_change_key;

OK
["a_key","b_key","c_key"]
Time taken: 0.17 seconds, Fetched: 1 row(s)

# Thay đổi key bằng cách hard coding key
INSERT OVERWRITE TABLE map_change_key
SELECT
  map(
    "a_key", col_a["a_key"],
    "b_key", col_a["b_key"],
    "c_key_changed", col_a["c_key"]
  )
FROM map_change_key;

# Check
SELECT sort_array(map_keys(col_a)) FROM map_change_key;

OK
["a_key","b_key","c_key_changed"]
Time taken: 0.171 seconds, Fetched: 1 row(s)

# => đã thay đổi

Tuy nhiên, trong dữ liệu thực tế thì số lượng key của Map có thể rất nhiều, từ vài chục đến hơn 1 trăm key hoặc hơn. Lúc đó thì việc thay đổi key bằng cách hard coding như trên sẽ rất mất thời gian cho việc viết script và kiểm tra kết quả. Script cũng sẽ rất dài và dễ dàng gặp sai sót.

Thay vì chờ Hive hỗ trợ các hàm mới, ta có thể tự viết UDF (User-Defined Functions) để thực hiện các xử lý mong muốn. Đón đọc blog sắp tới của anh Linh Trần để biết thêm chi tiết.

Ngoài ra, cũng có thể tham khảo các UDF đã được chia sẻ và nhiều người dùng như

Kết luận

  • Hiểu rõ các Complex Type của Hive giúp ta có nhiều lựa chọn hơn khi thiết kế
  • Hive chưa hỗ trợ nhiều các hàm built-in cho Complex Type

Tham khảo