Apache Hive には基本となる文字列や数値以外にも複合型 (Complex Type) というデータタイプがある。
以前、その中の一つとして ARRAY 型をこのブログでも扱った。
blog.amedama.jp
今回は、それに続いて複合型の中で STRUCT 型というデータタイプを試してみる。
これは、文字通り一般的なプログラミング言語でいう構造体 (Struct) に相当するもの。
この STRUCT 型を使うことで一つのカラムの中に複数のデータを格納できる。
使い勝手としては KVS によくあるカラムファミリーに近いかもしれない。
環境は次の通り。
$ cat /etc/redhat-release
CentOS Linux release 7.4.1708 (Core)
$ uname -r
3.10.0-693.5.2.el7.x86_64
$ hadoop version
Hadoop 2.8.3
Subversion https://git-wip-us.apache.org/repos/asf/hadoop.git -r b3fe56402d908019d99af1f1f4fc65cb1d1436a2
Compiled by jdu on 2017-12-05T03:43Z
Compiled with protoc 2.5.0
From source with checksum 9ff4856d824e983fa510d3f843e3f19d
This command was run using /home/vagrant/hadoop-2.8.3/share/hadoop/common/hadoop-common-2.8.3.jar
$ hive --version
Hive 2.3.2
Git git://stakiar-MBP.local/Users/stakiar/Desktop/scratch-space/apache-hive -r 857a9fd8ad725a53bd95c1b2d6612f9b1155f44d
Compiled by stakiar on Thu Nov 9 09:11:39 PST 2017
From source with checksum dc38920061a4eb32c4d15ebd5429ac8a
STRUCT 型を使ってテーブルを作る
例として、まずは名前の姓と名を STRUCT 型で分割して保存するテーブルを作ってみる。
STRUCT 型を定義するときは、次のように <>
の中に名前と型をカンマ区切りで羅列していく。
hive> CREATE TABLE users (
> name STRUCT<first: STRING,
> last: STRING>
> );
OK
Time taken: 0.135 seconds
STRUCT 型を使ったレコードを追加する
データを追加するときは一般的な INSERT INTO ... VALUES
が使えない。
hive> INSERT INTO TABLE users
> VALUES (NAMED_STRUCT("first", "John", "last", "Doe"));
FAILED: SemanticException [Error 10293]: Unable to create temp file for insert values Expression of type TOK_FUNCTION not supported in insert/values
代わりに SELECT
で作ったデータを使うことになる。
NAMED_STRUCT()
関数はフィールドに名前のついた STRUCT 型のデータを作るために使う。
hive> SELECT NAMED_STRUCT("first", "John", "last", "Doe");
OK
_c0
{"first":"John","last":"Doe"}
Time taken: 0.066 seconds, Fetched: 1 row(s)
上記のように SELECT
で作ったデータを INSERT INTO
に渡してやる。
hive> INSERT INTO TABLE users
> SELECT NAMED_STRUCT("first", "John", "last", "Doe");
...
OK
_c0
Time taken: 21.451 seconds
この通り、ちゃんとレコードが保存された。
hive> SELECT * FROM users;
OK
users.name
{"first":"John","last":"Doe"}
Time taken: 0.156 seconds, Fetched: 1 row(s)
STRUCT 型のフィールドを参照する
STRUCT 型に保存されたフィールドの中身を参照するときは、次のようにカラム名にドットでフィールド名をつなげてやる。
hive> SELECT name.first, name.last FROM users;
OK
first last
John Doe
Time taken: 0.143 seconds, Fetched: 1 row(s)
基本的な使い方は上記の通り。
一旦テーブルを削除しておこう。
hive> DROP TABLE users;
OK
Time taken: 0.119 seconds
ARRAY 型と組み合わせて使う
STRUCT 型は別の複合型と組み合わせて使うこともできる。
例えば ARRAY 型の中に STRUCT 型を含むようなテーブルを作ってみよう。
hive> CREATE TABLE users (
> name STRING,
> addresses ARRAY<STRUCT<country: STRING,
> city: STRING>>
> );
OK
Time taken: 0.114 seconds
データを追加するときは、次のように ARRAY()
関数と NAMED_STRUCT()
関数を組み合わせる。
hive> INSERT INTO TABLE users
> SELECT "Alice", ARRAY(NAMED_STRUCT("country", "japan", "city", "tokyo"),
> NAMED_STRUCT("country", "japan", "city", "osaka"));
...
OK
_c0 _c1
Time taken: 20.376 seconds
テーブルを確認すると、ちゃんと ARRAY 型の中に STRUCT 型のデータが収まっていることが分かる。
hive> SELECT * FROM users;
OK
users.name users.addresses
Alice [{"country":"japan","city":"tokyo"},{"country":"japan","city":"osaka"}]
Time taken: 0.133 seconds, Fetched: 1 row(s)
中身を展開して集計するときは普通に ARRAY 型を使うときと同じように LATERAL VIEW
と explode()
関数を組み合わせれば良い。
hive> SELECT *
> FROM users
> LATERAL VIEW explode(addresses) users AS address;
OK
users.name users.addresses users.address
Alice [{"country":"japan","city":"tokyo"},{"country":"japan","city":"osaka"}] {"country":"japan","city":"tokyo"}
Alice [{"country":"japan","city":"tokyo"},{"country":"japan","city":"osaka"}] {"country":"japan","city":"osaka"}
Time taken: 0.121 seconds, Fetched: 2 row(s)
hive> SELECT name, address.country, address.city
> FROM users
> LATERAL VIEW explode(addresses) users AS address;
OK
name country city
Alice japan tokyo
Alice japan osaka
Time taken: 0.045 seconds, Fetched: 2 row(s)
外部ファイルからデータを読み込む
外部ファイルからデータを読み込むときは、次のようにフィールドやコレクションの区切り文字を指定しておく。
hive> CREATE TABLE users (
> name STRUCT<first: STRING,
> last: STRING>
> )
> ROW FORMAT DELIMITED
> FIELDS TERMINATED BY ','
> COLLECTION ITEMS TERMINATED BY '$'
> STORED AS TEXTFILE;
OK
Time taken: 0.143 seconds
フィールドの区切り文字として $
を使った CSV ファイルを用意しておく。
$ cat << 'EOF' > users.csv
Yamada$Taro
Suzuki$Ichiro
EOF
あとは上記を Hive で読み込むだけ。
hive> LOAD DATA LOCAL INPATH '/home/vagrant/users.csv' INTO TABLE users;
Loading data to table default.users
OK
Time taken: 0.944 seconds
この通り、ちゃんとデータが格納された。
hive> SELECT * FROM users;
OK
users.name
{"first":"Yamada","last":"Taro"}
{"first":"Suzuki","last":"Ichiro"}
Time taken: 0.287 seconds, Fetched: 2 row(s)
次はもうちょっと複雑な例を示す。
その前に、一旦テーブルを削除しておこう。
hive> DROP TABLE users;
OK
Time taken: 0.193 seconds
次は、先ほどと同じように ARRAY 型と STRUCT 型を組み合わせたパターンでも外部ファイルから読み込んでみる。
このときのポイントとしては MAP KEYS TERMINATED BY
も指定しておくところ。
hive> CREATE TABLE users (
> name STRING,
> addresses ARRAY<STRUCT<country: STRING,
> city: STRING>>
> )
> ROW FORMAT DELIMITED
> FIELDS TERMINATED BY ','
> COLLECTION ITEMS TERMINATED BY '$'
> MAP KEYS TERMINATED BY ':'
> STORED AS TEXTFILE;
OK
Time taken: 0.06 seconds
今度はフィールドの区切り文字は :
を使いつつリストの区切り文字として $
を指定してやる。
$ cat << 'EOF' > users.csv
Alice,japan:tokyo$japan:osaka
Bob,america:newyork$america:california
EOF
上記のファイルを読み込んでみよう。
hive> LOAD DATA LOCAL INPATH '/home/vagrant/users.csv' INTO TABLE users;
Loading data to table default.users
OK
Time taken: 0.432 seconds
すると、以下のようにちゃんと保存されている。
hive> SELECT * FROM users;
OK
users.name users.addresses
Alice [{"country":"japan","city":"tokyo"},{"country":"japan","city":"osaka"}]
Bob [{"country":"america","city":"newyork"},{"country":"america","city":"california"}]
Time taken: 0.134 seconds, Fetched: 2 row(s)
ばっちり。