CUBE SUGAR CONTAINER

技術系のこと書きます。

Apache Hive の STRUCT 型を試す

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 VIEWexplode() 関数を組み合わせれば良い。

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)

ばっちり。

プログラミング Hive

プログラミング Hive

  • 作者: Edward Capriolo,Dean Wampler,Jason Rutherglen,佐藤直生,嶋内翔,Sky株式会社玉川竜司
  • 出版社/メーカー: オライリージャパン
  • 発売日: 2013/06/15
  • メディア: 大型本
  • この商品を含むブログ (3件) を見る