Groongaで一時カラム・動的カラムを使って計算結果でソートや集計する方法

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

今年も様々な機能が追加・改良されましたが、割とドキュメントが追い付いていない感じで、リリースノートやコマンドのテストケースを読むと追加された新機能の存在や使い方を知ることができます。

ここでは、一時カラム（動的カラム）の機能について紹介します。

# 一時カラムについて
Groongaでは一時カラム（動的カラム）が作成できるようになりました。今まで``output_columns``で関数をかけながら出力をすることができましたが、関数をかけた後の値を使って、ソートやフィルターなどをかけることはできませんでした。一時カラムを使うと、計算結果後の値を利用して、ソートやフィルターなどをかけることができます。

以下は一時カラムを使う例です。``price``に``discount_rate``を反映させた``discount_price``という一時カラムを作成して、その一時カラムでソートしています。

```bash
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 price COLUMN_SCALAR UInt32
[[0,0.0,0.0],true]
column_create books discount_rate COLUMN_SCALAR Float
[[0,0.0,0.0],true]
load --table books
[
{"title": "自動車教習本", "price": 1000, "discount_rate": 0.0},
{"title": "働く車", "price": 500, "discount_rate": 0.1},
{"title": "自転車の乗り方", "price": 1200, "discount_rate": 0.2}
]
[[0,0.0,0.0],3]
select books  \
 --columns[discount_price].stage filtered \
 --columns[discount_price].type Float \
 --columns[discount_price].flags COLUMN_SCALAR \
 --columns[discount_price].value 'price - (price * discount_rate)' \
 --sort_keys discount_price \
 --output_columns title,discount_price,price
[
  [
    0,
    0.0,
    0.0
  ],
  [
    [
      [
        3
      ],
      [
        [
          "title",
          "ShortText"
        ],
        [
          "discount_price",
          "Float"
        ],
        [
          "price",
          "UInt32"
        ]
      ],
      [
        "働く車",
        450.0,
        500
      ],
      [
        "自転車の乗り方",
        960.0,
        1200
      ],
      [
        "自動車教習本",
        1000.0,
        1000
      ]
    ]
  ]
]
```

構文がややこしいのですが、``columns[label_name].~``とすることで、新たにオンメモリ上の一時カラムを計算結果テーブルに紐づけることができ、``output_columns``や``sort_keys``、``drilldowns``などで計算結果後の一時カラムを指定ができるようになります。SQLでいう``AS``で別名をつけて指定するようなものです。``AS``と違い型情報も与えなければいけません。

## ``columns[label_name].type``
``column_create``の``type``と同様にカラムの型を指定します。例えば、``UInt32``や``ShortText``またはテーブル名などです。

## ``columns[label_name].flags``
``column_create``の``flags``と同様にカラムのフラグを指定します。例えば、``COLUMN_SCALAR``や``COLUMN_VECTOR``などです。

## ``columns[label_name].value``
スクリプト構文の式を書きます。たとえば、``(title + "a")``とかくと``title``カラムに文字列"a"が追加されますし、``snippet_html(title)``など関数を書くこともできます。

## ``columns[label_name].stage``
``stage``とは一時カラムを作成するタイミングのことで、これにより関数の評価対象と使える処理の対象が変わってきます。

### initial
``query``と``filter``の絞り込み前、最初の段階の検索対象テーブルを元にして一時カラムを作成します。``query``、``filter``、``drilldown``、``sort_keys``, ``output_columns``のすべてで使用することができます。``value``は検索対象テーブルの全件のレコードに対して評価されます。
### filtered
``query``と``filter``が適用された後の検索結果を元にして一時カラムを作成します。``drilldown``、``sort_keys``, ``output_columns``で使用することができます。``value``は検索結果の絞り込み件数のレコードに対して評価されます。
### output
``sort_keys``、``limit``、``offset``が適用された後のソート結果、出力数制限の結果を元にして一時カラムを作成します。``output_columns``で使用することができます。``value``は出力するlimit件数のレコードに対して評価されます。

# おわりに

一時カラムは、用途に応じて適切な``stage``を選択する必要があります。``initial``や``filtered``では、多くのレコードがシーケンシャルに処理されます。

``snippet``や``highlight``などの実際に出力するものだけに適用すればいいものは、``output``ステージを選択するべきです。

``drilldown``や``sort_keys``に使いたい場合は、``filtered``を使用するべきです。

検索結果でも100万件を超えるような場合、時間がかかりすぎるので事前に計算したカラムを用意することをお勧めします。