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 arrayarray_contains
: Kiểm tra xem array có chứa giá trị truyền vào khôngsort_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 Mapmap_key
: Trả về array gồm các key của Mapmap_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ư
- brickhouse
- ...
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