はじめに
全文検索エンジンのGroongaは全文検索エンジンの機能だけでなく、カラム指向型のデータストアとしての機能も備えています。
昨年は、表記揺れを直してグループ集計するために
編集距離などで文字列の類似度から代表の単語と表記ゆれした単語を事前に抽出し__データ投入時__にデータを置き換えるというアプローチをとりました。(Groongaでのタグ検索と表記揺れとの戦い at Groonga Meatup 2015)
今年は、Groongaのドリルダウンの結果を使って多段のドリルダウンをする機能や、検索結果を所定の演算を施した結果で一時的なオンメモリのカラムを作り、さらにその一時カラムに対してフィルター、ソート集計する機能など、様々なドリルダウンのための機能が追加・改良されました。
そこで、これらの機能を使って、__グループ集計時__に表記揺れや別名など、複数の単語を1つの単語に名寄せして集計する方法を紹介します。
通常のドリルダウン例
以下は通常のドリルダウンの例です。books
というテーブルに自動車
、車
および自転車
というカテゴリーがあり、それぞれ1件であるという集計結果が得られています。
table_create categories TABLE_HASH_KEY ShortText
[[0,0.0,0.0],true]
table_create books TABLE_NO_KEY
[[0,0.0,0.0],true]
column_create books title COLUMN_SCALAR ShortText
[[0,0.0,0.0],true]
column_create books categories COLUMN_VECTOR categories
[[0,0.0,0.0],true]
load --table books
[
{"title": "自動車教習本", "categories": ["自動車"]},
{"title": "働く車", "categories": ["車"]},
{"title": "自転車の乗り方", "categories": ["自転車"]}
]
[[0,0.0,0.0],3]
select books \
--output_columns title,categories \
--drilldown categories
[
[
0,
0.0,
0.0
],
[
[
[
3
],
[
[
"title",
"ShortText"
],
[
"categories",
"categories"
]
],
[
"自動車教習本",
[
"自動車"
]
],
[
"働く車",
[
"車"
]
],
[
"自転車の乗り方",
[
"自転車"
]
]
],
[
[
3
],
[
[
"_key",
"ShortText"
],
[
"_nsubrecs",
"Int32"
]
],
[
"自動車",
1
],
[
"車",
1
],
[
"自転車",
1
]
]
]
]
ドリルダウン結果での一時カラムの作成
Groongaで一時カラム・動的カラムを使って計算結果でソートや集計する方法で紹介したGroongaの一時カラムはドリルダウン結果に対してもサポートされています。この一時カラムを使ってcategories
テーブルのsynonym
カラムに設定された別名に置き換えてみます。
column_create categories synonym COLUMN_SCALAR ShortText
[[0,0.0,0.0],true]
load --table categories
[
{"_key": "車", "synonym": "自動車"}
]
[[0,0.0,0.0],1]
select books \
--drilldowns[category].keys categories \
--drilldowns[category].columns[aggregated_category].stage initial \
--drilldowns[category].columns[aggregated_category].type ShortText \
--drilldowns[category].columns[aggregated_category].flags COLUMN_SCALAR \
--drilldowns[category].columns[aggregated_category].value 'synonym || _key' \
--drilldowns[category].output_columns _key,aggregated_category,_nsubrecs
[
[
0,
0.0,
0.0
],
[
[
[
3
],
[
[
"_id",
"UInt32"
],
[
"categories",
"categories"
],
[
"title",
"ShortText"
]
],
[
1,
[
"自動車"
],
"自動車教習本"
],
[
2,
[
"車"
],
"働く車"
],
[
3,
[
"自転車"
],
"自転車の乗り方"
]
],
{
"category": [
[
3
],
[
[
"_key",
"ShortText"
],
[
"aggregated_category",
"ShortText"
],
[
"_nsubrecs",
"Int32"
]
],
[
"自動車",
"自動車",
1
],
[
"車",
"自動車",
1
],
[
"自転車",
"自転車",
1
]
]
}
]
]
--drilldowns[category].columns[aggregated_category].value 'synonym || _key'
とすることで、categories
テーブルのsynonym
カラムに値が設定されている場合はその値、設定されていない場合は元の_key
が一時カラムに代入されるようになります。これで、車
というカテゴリーは自動車
というカテゴリーに設定されました。
なお、1レコードずつシーケンシャルに置き換えるため、グループ結果の種類数が莫大な場合は処理速度が厳しくなってくるでしょう。数万~数十万程度なら大丈夫かなという勘所です。
多段ドリルダウン
これでグループ結果のテーブルに別名に置き換えた一時カラムがメモリ上に設定されたので、この一時カラムを対象に再集計させます。
--drilldowns[label_name]
とすることでドリルダウン結果のテーブルに名前を付けることができ、--drilldowns[label_name].table
でそのテーブル名を指定することができます。
select books \
--drilldowns[category].keys categories \
--drilldowns[category].columns[aggregated_category].stage initial \
--drilldowns[category].columns[aggregated_category].type ShortText \
--drilldowns[category].columns[aggregated_category].flags COLUMN_SCALAR \
--drilldowns[category].columns[aggregated_category].value 'synonym || _key' \
--drilldowns[category].output_columns _key,aggregated_category,_nsubrecs \
--drilldowns[aggregated_category].table category \
--drilldowns[aggregated_category].keys aggregated_category \
--drilldowns[aggregated_category].calc_types SUM \
--drilldowns[aggregated_category].calc_target _nsubrecs \
--drilldowns[aggregated_category].output_columns _key,_sum
[
[
0,
0.0,
0.0
],
[
[
[
3
],
[
[
"_id",
"UInt32"
],
[
"categories",
"categories"
],
[
"title",
"ShortText"
]
],
[
1,
[
"自動車"
],
"自動車教習本"
],
[
2,
[
"車"
],
"働く車"
],
[
3,
[
"自転車"
],
"自転車の乗り方"
]
],
{
"category": [
[
3
],
[
[
"_key",
"ShortText"
],
[
"aggregated_category",
"ShortText"
],
[
"_nsubrecs",
"Int32"
]
],
[
"自動車",
"自動車",
1
],
[
"車",
"自動車",
1
],
[
"自転車",
"自転車",
1
]
],
"aggregated_category": [
[
2
],
[
[
"_key",
"ShortText"
],
[
"_sum",
"Int64"
]
],
[
"自動車",
2
],
[
"自転車",
1
]
]
}
]
]
これで、車
というカテゴリーを自動車
というカテゴリーで集約した結果で自動車
が2件、自転車
が1件というグループ件数が得られました。
なお、グループ結果前にdrilldown
ではないcolumns
のfiltered
ステージで調整してからdrilldown
しても同様の結果が得られるのですが(*)、
この場合、グループ集約前のレコードを全部置き換える必要があるので、演算処理の回数を減らすためにグループ集約後の一時カラムで値を置き換える方が良いでしょう。
* 上記の例だとvectorなので自作関数をかかないと無理かもしれません。
おわりに
以上、多段ドリルダウンと一時カラムの使用例を紹介しました。
一時カラムのところは、自作のユーザ定義関数を作って値を置き換えたりすることもできます。また、ウィンドウ関数の仕組みなども追加されており、グループ処理の自由度は格段にあがっています。
Groongaを全文検索だけでなく、グループ集計の用途としても検討してみてはいかがでしょうか。