# groonga - オープンソースのカラムストア機能付き全文検索エンジン

- English page

# 4.7. match_columnsパラメータ

## 4.7.1. 複数のカラムに対する全文検索

Groongaでは、複数のカラムを対象とした全文検索を行うことができます。例えば、ブログのテーブルで、タイトルと内容とがそれぞれ別のカラムに入ったものがあるとしましょう。「タイトルもしくは内容に特定の単語を含む」検索を行いたいとします。

この場合、2つのインデックス作成方式があります。1つは、それぞれのカラムに1つずつインデックスを付与する方式です。もう1つは、複数のカラムに対して1つのインデックスを付与する方式です。Groongaでは、どちらの形式のインデックスが存在している場合でも、同一の記法で全文検索を行うことができます。

### 4.7.1.1. カラムごとにインデックスを付与する場合

カラムごとにインデックスを作成する方法はこの通りです。

まず、 `Blog1` テーブルを作成し、 `title` カラムと `message` カラムを追加します。 `title` カラムにブログのタイトルを保存し、 `message` カラムにブログの本文を保存します。

インデックス用の `IndexBlog1` テーブルも作り、 `title` カラムのインデックス用に `index_title` カラム、 `message` カラムのインデック用に `index_message` カラムと、それぞれ1カラムごとに1つずつ追加しています。

実行例:

    table_create --name Blog1 --flags TABLE_HASH_KEY --key_type ShortText
    # [[0, 1337566253.89858, 0.000355720520019531], true]
    column_create --table Blog1 --name title --flags COLUMN_SCALAR --type ShortText
    # [[0, 1337566253.89858, 0.000355720520019531], true]
    column_create --table Blog1 --name message --flags COLUMN_SCALAR --type ShortText
    # [[0, 1337566253.89858, 0.000355720520019531], true]
    table_create --name IndexBlog1 --flags TABLE_PAT_KEY|KEY_NORMALIZE --key_type ShortText --default_tokenizer TokenBigram
    # [[0, 1337566253.89858, 0.000355720520019531], true]
    column_create --table IndexBlog1 --name index_title --flags COLUMN_INDEX|WITH_POSITION --type Blog1 --source title
    # [[0, 1337566253.89858, 0.000355720520019531], true]
    column_create --table IndexBlog1 --name index_message --flags COLUMN_INDEX|WITH_POSITION --type Blog1 --source message
    # [[0, 1337566253.89858, 0.000355720520019531], true]
    load --table Blog1
    [
    {"_key":"grn1","title":"groonga test","message":"groonga message"},
    {"_key":"grn2","title":"baseball result","message":"rakutan eggs 4 - 4 groonga moritars"},
    {"_key":"grn3","title":"groonga message","message":"none"}
    ]
    # [[0, 1337566253.89858, 0.000355720520019531], 3]

`match_columns` オプションで、検索対象のカラムを複数指定することが出来ます。検索する文字列は `query` オプションで指定します。これを使うことで、タイトルと本文を全文検索することができます。

実際にブログエントリを検索してみましょう。

実行例:

    select --table Blog1 --match_columns title||message --query groonga
    # [
    #   [
    #     0,
    #     1337566253.89858,
    #     0.000355720520019531
    #   ],
    #   [
    #     [
    #       [
    #         3
    #       ],
    #       [
    #         [
    #           "_id",
    #           "UInt32"
    #         ],
    #         [
    #           "_key",
    #           "ShortText"
    #         ],
    #         [
    #           "message",
    #           "ShortText"
    #         ],
    #         [
    #           "title",
    #           "ShortText"
    #         ]
    #       ],
    #       [
    #         1,
    #         "grn1",
    #         "groonga message",
    #         "groonga test"
    #       ],
    #       [
    #         3,
    #         "grn3",
    #         "none",
    #         "groonga message"
    #       ],
    #       [
    #         2,
    #         "grn2",
    #         "rakutan eggs 4 - 4 groonga moritars",
    #         "baseball result"
    #       ]
    #     ]
    #   ]
    # ]
    select --table Blog1 --match_columns title||message --query message
    # [
    #   [
    #     0,
    #     1337566253.89858,
    #     0.000355720520019531
    #   ],
    #   [
    #     [
    #       [
    #         2
    #       ],
    #       [
    #         [
    #           "_id",
    #           "UInt32"
    #         ],
    #         [
    #           "_key",
    #           "ShortText"
    #         ],
    #         [
    #           "message",
    #           "ShortText"
    #         ],
    #         [
    #           "title",
    #           "ShortText"
    #         ]
    #       ],
    #       [
    #         3,
    #         "grn3",
    #         "none",
    #         "groonga message"
    #       ],
    #       [
    #         1,
    #         "grn1",
    #         "groonga message",
    #         "groonga test"
    #       ]
    #     ]
    #   ]
    # ]
    select --table Blog1 --match_columns title --query message
    # [
    #   [
    #     0,
    #     1337566253.89858,
    #     0.000355720520019531
    #   ],
    #   [
    #     [
    #       [
    #         1
    #       ],
    #       [
    #         [
    #           "_id",
    #           "UInt32"
    #         ],
    #         [
    #           "_key",
    #           "ShortText"
    #         ],
    #         [
    #           "message",
    #           "ShortText"
    #         ],
    #         [
    #           "title",
    #           "ShortText"
    #         ]
    #       ],
    #       [
    #         3,
    #         "grn3",
    #         "none",
    #         "groonga message"
    #       ]
    #     ]
    #   ]
    # ]

### 4.7.1.2. 複数のカラムにまたがったインデックスを付与する場合

Groongaでは複数のカラムにまたがったインデックスもサポートしています。

インデックスカラムが1つしかないというのが違いです。 `title` と `message` の2つのカラムに対するインデックスが共通になっています。

共通のインデックスを用いても、 `title` カラムのみでの検索、 `message` カラムのみでの検索、 `title` もしくは `message` カラムでの検索、全ての検索を行うことができます。

実行例:

    table_create --name Blog2 --flags TABLE_HASH_KEY --key_type ShortText
    # [[0, 1337566253.89858, 0.000355720520019531], true]
    column_create --table Blog2 --name title --flags COLUMN_SCALAR --type ShortText
    # [[0, 1337566253.89858, 0.000355720520019531], true]
    column_create --table Blog2 --name message --flags COLUMN_SCALAR --type ShortText
    # [[0, 1337566253.89858, 0.000355720520019531], true]
    table_create --name IndexBlog2 --flags TABLE_PAT_KEY|KEY_NORMALIZE --key_type ShortText --default_tokenizer TokenBigram
    # [[0, 1337566253.89858, 0.000355720520019531], true]
    column_create --table IndexBlog2 --name index_blog --flags COLUMN_INDEX|WITH_POSITION|WITH_SECTION --type Blog2 --source title,message
    # [[0, 1337566253.89858, 0.000355720520019531], true]
    load --table Blog2
    [
    {"_key":"grn1","title":"groonga test","message":"groonga message"},
    {"_key":"grn2","title":"baseball result","message":"rakutan eggs 4 - 4 groonga moritars"},
    {"_key":"grn3","title":"groonga message","message":"none"}
    ]
    # [[0, 1337566253.89858, 0.000355720520019531], 3]

実際に前と同じ例で検索してみましょう。結果は上の例と同じになります。

実行例:

    select --table Blog2 --match_columns title||message --query groonga
    # [
    #   [
    #     0,
    #     1337566253.89858,
    #     0.000355720520019531
    #   ],
    #   [
    #     [
    #       [
    #         3
    #       ],
    #       [
    #         [
    #           "_id",
    #           "UInt32"
    #         ],
    #         [
    #           "_key",
    #           "ShortText"
    #         ],
    #         [
    #           "message",
    #           "ShortText"
    #         ],
    #         [
    #           "title",
    #           "ShortText"
    #         ]
    #       ],
    #       [
    #         1,
    #         "grn1",
    #         "groonga message",
    #         "groonga test"
    #       ],
    #       [
    #         2,
    #         "grn2",
    #         "rakutan eggs 4 - 4 groonga moritars",
    #         "baseball result"
    #       ],
    #       [
    #         3,
    #         "grn3",
    #         "none",
    #         "groonga message"
    #       ]
    #     ]
    #   ]
    # ]
    select --table Blog2 --match_columns title||message --query message
    # [
    #   [
    #     0,
    #     1337566253.89858,
    #     0.000355720520019531
    #   ],
    #   [
    #     [
    #       [
    #         2
    #       ],
    #       [
    #         [
    #           "_id",
    #           "UInt32"
    #         ],
    #         [
    #           "_key",
    #           "ShortText"
    #         ],
    #         [
    #           "message",
    #           "ShortText"
    #         ],
    #         [
    #           "title",
    #           "ShortText"
    #         ]
    #       ],
    #       [
    #         1,
    #         "grn1",
    #         "groonga message",
    #         "groonga test"
    #       ],
    #       [
    #         3,
    #         "grn3",
    #         "none",
    #         "groonga message"
    #       ]
    #     ]
    #   ]
    # ]
    select --table Blog2 --match_columns title --query message
    # [
    #   [
    #     0,
    #     1337566253.89858,
    #     0.000355720520019531
    #   ],
    #   [
    #     [
    #       [
    #         1
    #       ],
    #       [
    #         [
    #           "_id",
    #           "UInt32"
    #         ],
    #         [
    #           "_key",
    #           "ShortText"
    #         ],
    #         [
    #           "message",
    #           "ShortText"
    #         ],
    #         [
    #           "title",
    #           "ShortText"
    #         ]
    #       ],
    #       [
    #         3,
    #         "grn3",
    #         "none",
    #         "groonga message"
    #       ]
    #     ]
    #   ]
    # ]

注釈

"インデックスはどちらがよい方法なのか"と疑問に思うかも知れません。それは場合によります。

- カラムごとのインデックス - マルチカラムインデックスよりも更新性能が良い傾向があります。一方、ディスク使用効率はあまり良くありません。

- マルチカラムインデックス - バッファを共有するためディスク使用効率が良いです。一方、更新性能があまり良くありません。

## 4.7.2. インデックス名を指定した全文検索

執筆中です。

## 4.7.3. カラムインデックスによる関連テーブルをまたぐ検索

複数のテーブルがカラムインデックスで関連付けられているなら、参照カラム名を指定して複数のテーブルにまたがって検索することができます。

具体的な例を示します。

ブログ記事や記事のコメントを保存するテーブルがあります。記事を保存するテーブルには記事とコメントのためのカラムがあります。そしてそのコメントカラムはCommentsテーブルを参照しています。コメントを保存するテーブルにはコメントと記事テーブルに対するカラムインデックスが設定されています。

特定のキーワードをコメントに含む記事を探すには、コメントテーブルを全文検索する必要があり、全文検索結果を含むレコードをさらに検索する必要があります。

しかし、カラムインデックスを指定することで一度にレコードを検索することができます。

サンプルのスキーマ定義はこちらです。

実行例:

    table_create Comments TABLE_HASH_KEY UInt32
    # [[0, 1337566253.89858, 0.000355720520019531], true]
    column_create Comments content COLUMN_SCALAR ShortText
    # [[0, 1337566253.89858, 0.000355720520019531], true]
    table_create Articles TABLE_NO_KEY
    # [[0, 1337566253.89858, 0.000355720520019531], true]
    column_create Articles content COLUMN_SCALAR Text
    # [[0, 1337566253.89858, 0.000355720520019531], true]
    column_create Articles comment COLUMN_SCALAR Comments
    # [[0, 1337566253.89858, 0.000355720520019531], true]
    table_create Lexicon TABLE_PAT_KEY|KEY_NORMALIZE ShortText --default_tokenizer TokenBigram
    # [[0, 1337566253.89858, 0.000355720520019531], true]
    column_create Lexicon articles_content COLUMN_INDEX|WITH_POSITION Articles content
    # [[0, 1337566253.89858, 0.000355720520019531], true]
    column_create Lexicon comments_content COLUMN_INDEX|WITH_POSITION Comments content
    # [[0, 1337566253.89858, 0.000355720520019531], true]
    column_create Comments article COLUMN_INDEX Articles comment
    # [[0, 1337566253.89858, 0.000355720520019531], true]

サンプルデータはこちらです。

実行例:

    load --table Comments
    [
    {"_key": 1, "content": "I'm using groonga too!"},
    {"_key": 2, "content": "I'm using groonga and mroonga!"},
    {"_key": 3, "content": "I'm using mroonga too!"}
    ]
    # [[0, 1337566253.89858, 0.000355720520019531], 3]
    load --table Articles
    [
    {"content": "Groonga is fast!", "comment": 1},
    {"content": "Groonga is useful!"},
    {"content": "Mroonga is fast!", "comment": 3}
    ]
    # [[0, 1337566253.89858, 0.000355720520019531], 3]

特定のキーワードをコメントに含むレコードを検索するクエリを書くことができ、それによりレコードを参照する記事を取得します。

これまでに記述したレコードを検索するクエリ:

    select Articles --match_columns comment.content --query groonga --output_columns "_id, _score, *"

ArticlesテーブルのcommentカラムとCommentsテーブルのcontentカラムをピリオド(.)で連結し `--match_columns` の引数とする必要があります。

最初に、このクエリはCommentsテーブルのcontentを全文検索し、次にCommentsテーブルを検索した結果のレコードを参照するArticlesテーブルのレコードを取得します。(これにより、Commentsテーブルの'article'カラムインデックスを生成するクエリをコメントアウトすると、意図した検索結果が得られません。)

実行例:

    select Articles --match_columns comment.content --query groonga --output_columns "_id, _score, *"
    # [
    #   [
    #     0,
    #     1337566253.89858,
    #     0.000355720520019531
    #   ],
    #   [
    #     [
    #       [
    #         1
    #       ],
    #       [
    #         [
    #           "_id",
    #           "UInt32"
    #         ],
    #         [
    #           "_score",
    #           "Int32"
    #         ],
    #         [
    #           "comment",
    #           "Comments"
    #         ],
    #         [
    #           "content",
    #           "Text"
    #         ]
    #       ],
    #       [
    #         1,
    #         1,
    #         1,
    #         "Groonga is fast!"
    #       ]
    #     ]
    #   ]
    # ]

これで、特定のキーワードをコメントとして含む記事を検索できます。

このネストしたインデックスの検索という特徴には関連するテーブルが2つだけに制限されません。

前のものと似たサンプルのスキーマ定義です。違いは'返信'を表現するテーブルの追加と関連するテーブルが3つに増えたことです。

実行例:

    table_create Replies2 TABLE_HASH_KEY UInt32
    # [[0, 1337566253.89858, 0.000355720520019531], true]
    column_create Replies2 content COLUMN_SCALAR ShortText
    # [[0, 1337566253.89858, 0.000355720520019531], true]
    table_create Comments2 TABLE_HASH_KEY UInt32
    # [[0, 1337566253.89858, 0.000355720520019531], true]
    column_create Comments2 content COLUMN_SCALAR ShortText
    # [[0, 1337566253.89858, 0.000355720520019531], true]
    column_create Comments2 comment COLUMN_SCALAR Replies2
    # [[0, 1337566253.89858, 0.000355720520019531], true]
    table_create Articles2 TABLE_NO_KEY
    # [[0, 1337566253.89858, 0.000355720520019531], true]
    column_create Articles2 content COLUMN_SCALAR Text
    # [[0, 1337566253.89858, 0.000355720520019531], true]
    column_create Articles2 comment COLUMN_SCALAR Comments2
    # [[0, 1337566253.89858, 0.000355720520019531], true]
    table_create Lexicon2 TABLE_PAT_KEY|KEY_NORMALIZE ShortText --default_tokenizer TokenBigram
    # [[0, 1337566253.89858, 0.000355720520019531], true]
    column_create Lexicon2 articles_content COLUMN_INDEX|WITH_POSITION Articles2 content
    # [[0, 1337566253.89858, 0.000355720520019531], true]
    column_create Lexicon2 comments_content COLUMN_INDEX|WITH_POSITION Comments2 content
    # [[0, 1337566253.89858, 0.000355720520019531], true]
    column_create Lexicon2 replies_content COLUMN_INDEX|WITH_POSITION Replies2 content
    # [[0, 1337566253.89858, 0.000355720520019531], true]
    column_create Comments2 article COLUMN_INDEX Articles2 comment
    # [[0, 1337566253.89858, 0.000355720520019531], true]
    column_create Replies2 reply_to COLUMN_INDEX Comments2 comment
    # [[0, 1337566253.89858, 0.000355720520019531], true]

サンプルデータはこちらです。

実行例:

    load --table Replies2
    [
    {"_key": 1, "content": "I'm using rroonga too!"},
    {"_key": 2, "content": "I'm using groonga and mroonga and rroonga!"},
    {"_key": 3, "content": "I'm using nroonga too!"}
    ]
    # [[0, 1337566253.89858, 0.000355720520019531], 3]
    load --table Comments2
    [
    {"_key": 1, "content": "I'm using groonga too!", "comment": 1},
    {"_key": 2, "content": "I'm using groonga and mroonga!", "comment": 2},
    {"_key": 3, "content": "I'm using mroonga too!"}
    ]
    # [[0, 1337566253.89858, 0.000355720520019531], 3]
    load --table Articles2
    [
    {"content": "Groonga is fast!", "comment": 1},
    {"content": "Groonga is useful!", "comment": 2},
    {"content": "Mroonga is fast!", "comment": 3}
    ]
    # [[0, 1337566253.89858, 0.000355720520019531], 3]

これまでに記述したレコードを検索するクエリ:

    select Articles2 --match_columns comment.content --query mroonga --output_columns "_id, _score, *"
    select Articles2 --match_columns comment.comment.content --query mroonga --output_columns "_id, _score, *"

最初のクエリはComments2テーブルから'mroonga'を検索します。2つめのクエリはReplies2とComments2テーブルからカラムインデックスによる参照を用いて'mroonga'を検索します。

実行例:

    select Articles2 --match_columns comment.content --query mroonga --output_columns "_id, _score, *"
    # [
    #   [
    #     0,
    #     1337566253.89858,
    #     0.000355720520019531
    #   ],
    #   [
    #     [
    #       [
    #         2
    #       ],
    #       [
    #         [
    #           "_id",
    #           "UInt32"
    #         ],
    #         [
    #           "_score",
    #           "Int32"
    #         ],
    #         [
    #           "comment",
    #           "Comments2"
    #         ],
    #         [
    #           "content",
    #           "Text"
    #         ]
    #       ],
    #       [
    #         2,
    #         1,
    #         2,
    #         "Groonga is useful!"
    #       ],
    #       [
    #         3,
    #         1,
    #         3,
    #         "Mroonga is fast!"
    #       ]
    #     ]
    #   ]
    # ]
    select Articles2 --match_columns comment.comment.content --query mroonga --output_columns "_id, _score, *"
    # [
    #   [
    #     0,
    #     1337566253.89858,
    #     0.000355720520019531
    #   ],
    #   [
    #     [
    #       [
    #         1
    #       ],
    #       [
    #         [
    #           "_id",
    #           "UInt32"
    #         ],
    #         [
    #           "_score",
    #           "Int32"
    #         ],
    #         [
    #           "comment",
    #           "Comments2"
    #         ],
    #         [
    #           "content",
    #           "Text"
    #         ]
    #       ],
    #       [
    #         2,
    #         1,
    #         2,
    #         "Groonga is useful!"
    #       ]
    #     ]
    #   ]
    # ]

結果として、最初のクエリはComments2テーブルに'mroonga'をキーワードとして含むレコードが2つあるので、該当する記事2つにマッチします。

一方、2つめのクエリはReplies2テーブルに'mroonga'というキーワードにマッチするレコードが1つしかなく、Comments2テーブルでそのキーワードを含むレコードを参照するコメントが1つなので、該当する記事は1つだけとなります。

## 4.7.4. インデックスの重み

執筆中です。