Groongaで一時カラムと多段ドリルダウンを使って名寄せ集計する方法

# はじめに
全文検索エンジンのGroongaは全文検索エンジンの機能だけでなく、カラム指向型のデータストアとしての機能も備えています。

昨年は、表記揺れを直してグループ集計するために
編集距離などで文字列の類似度から代表の単語と表記ゆれした単語を事前に抽出し__データ投入時__にデータを置き換えるというアプローチをとりました。（[Groongaでのタグ検索と表記揺れとの戦い at Groonga Meatup 2015](http://blog.createfield.com/entry/2015/11/30/120556)）

今年は、Groongaのドリルダウンの結果を使って多段のドリルダウンをする機能や、検索結果を所定の演算を施した結果で一時的なオンメモリのカラムを作り、さらにその一時カラムに対してフィルター、ソート集計する機能など、様々なドリルダウンのための機能が追加・改良されました。

そこで、これらの機能を使って、__グループ集計時__に表記揺れや別名など、複数の単語を１つの単語に名寄せして集計する方法を紹介します。

# 通常のドリルダウン例

以下は通常のドリルダウンの例です。``books``というテーブルに``自動車``、``車``および``自転車``というカテゴリーがあり、それぞれ１件であるという集計結果が得られています。

```bash
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で一時カラム・動的カラムを使って計算結果でソートや集計する方法](http://qiita.com/naoa/items/be37cf8522e3b7240f7f)で紹介したGroongaの一時カラムはドリルダウン結果に対してもサポートされています。この一時カラムを使って``categories``テーブルの``synonym``カラムに設定された別名に置き換えてみます。

```bash
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``が一時カラムに代入されるようになります。これで、``車``というカテゴリーは``自動車``というカテゴリーに設定されました。

なお、１レコードずつシーケンシャルに置き換えるため、グループ結果の種類数が莫大な場合は処理速度が厳しくなってくるでしょう。数万～数十万程度なら大丈夫かなという勘所です。

# 多段ドリルダウン
これでグループ結果のテーブルに別名に置き換えた一時カラムがメモリ上に設定されたので、この一時カラムを対象に再集計させます。
``--drilldowns[label_name]``とすることでドリルダウン結果のテーブルに名前を付けることができ、``--drilldowns[label_name].table``でそのテーブル名を指定することができます。

```bash
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を全文検索だけでなく、グループ集計の用途としても検討してみてはいかがでしょうか。