4.3. ストレージモード — Mroonga vmrn_version documentation

本文へスキップ
`Ctrl`+`K`

# 4.3. ストレージモード

ここでは Mroonga におけるストレージモードの利用方法を説明します。

## 4.3.1. 全文検索の利用方法

インストールが確認できたら、テーブルを1つ作成してみましょう。 `ENGINE = Mroonga` とMroongaを指定するところがポイントです。

    CREATE TABLE diaries (
      id INT PRIMARY KEY AUTO_INCREMENT,
      content VARCHAR(255),
      FULLTEXT INDEX (content)
    ) ENGINE = Mroonga DEFAULT CHARSET utf8;
    -- Query OK, 0 rows affected (0.10 sec)

`INSERT` でデータを投入してみましょう。

    INSERT INTO diaries (content) VALUES ("It'll be fine tomorrow.");
    -- Query OK, 1 row affected (0.01 sec)

    INSERT INTO diaries (content) VALUES ("It'll rain tomorrow");
    -- Query OK, 1 row affected (0.00 sec)

全文検索を実行してみます。

    SELECT * FROM diaries WHERE MATCH(content) AGAINST("+fine" IN BOOLEAN MODE);
    -- +----+-----------------------------------------+
    -- | id | content                                 |
    -- +----+-----------------------------------------+
    -- |  1 | It'll be fine tomorrow. |
    -- +----+-----------------------------------------+
    -- 1 row in set (0.00 sec)

おぉぉー。検索できましたね。

## 4.3.2. 検索スコアの取得方法

注釈

1.0.0以前のMroongaではMySQLの標準的な検索スコアの取得方法ではなく、 `_score` という専用のカラムを作成するという独自の方法でした。1.0.0からはMySQLの標準的な取得方法になっています。

全文検索を行う際、指定したキーワードにより内容が一致するレコードを上位に表示したいというような場合があります。そうしたケースでは検索スコアを利用します。

検索スコアはMySQLの標準的な方法 [1] で取得できます。つまり、SELECTの取得するカラム名を指定するところやORDER BYのところにMATCH...AGAINSTを指定します。

それでは実際にやってみましょう。

    INSERT INTO diaries (content) VALUES ("It's fine today. It'll be fine tomorrow as well.");
    -- Query OK, 1 row affected (0.00 sec)

    INSERT INTO diaries (content) VALUES ("It's fine today. But it'll rain tomorrow.");
    -- Query OK, 1 row affected (0.00 sec)

    SELECT *,
           MATCH (content) AGAINST ("+fine" IN BOOLEAN MODE)
      FROM diaries
     WHERE MATCH (content) AGAINST ("+fine" IN BOOLEAN MODE)
     ORDER BY
           MATCH (content) AGAINST ("+fine" IN BOOLEAN MODE) DESC;
    -- +----+--------------------------------------------------+---------------------------------------------------+
    -- | id | content                                          | MATCH (content) AGAINST ("+fine" IN BOOLEAN MODE) |
    -- +----+--------------------------------------------------+---------------------------------------------------+
    -- |  3 | It's fine today. It'll be fine tomorrow as well. |                                                 2 |
    -- |  1 | It'll be fine tomorrow.                          |                                                 1 |
    -- |  4 | It's fine today. But it'll rain tomorrow.        |                                                 1 |
    -- +----+--------------------------------------------------+---------------------------------------------------+
    -- 3 rows in set (0.00 sec)

検索対象の文字列 `fine` をより多く含む、すなわち検索スコアの高い `id = 3` のメッセージが上に来ていることが確認できます。また、SELECT句にMATCH AGAINSTを記述しているため、検索スコアも取得できています。

属性名を変更したい場合は `AS` を使って下さい。

    SELECT *,
           MATCH (content) AGAINST ("+fine" IN BOOLEAN MODE) AS score
      FROM diaries
     WHERE MATCH (content) AGAINST ("+fine" IN BOOLEAN MODE)
     ORDER BY
           MATCH (content) AGAINST ("+fine" IN BOOLEAN MODE) DESC;
    -- +----+--------------------------------------------------+-------+
    -- | id | content                                          | score |
    -- +----+--------------------------------------------------+-------+
    -- |  3 | It's fine today. It'll be fine tomorrow as well. |     2 |
    -- |  1 | It'll be fine tomorrow.                          |     1 |
    -- |  4 | It's fine today. But it'll rain tomorrow.        |     1 |
    -- +----+--------------------------------------------------+-------+
    -- 3 rows in set (0.00 sec)

## 4.3.3. 全文検索用パーサの変更

MySQLは全文検索用のパーサ [2] を指定する以下のような構文を持っています。:

    FULLTEXT INDEX (content) WITH PARSER parser_name

しかし、この構文を利用する場合は、あらかじめすべてのパーサをMySQLに登録しておく必要があります。一方、Groongaはトークナイザー（MySQLでいうパーサ）を動的に追加することができます。そのため、Mroongaでもこの構文を採用するとGroonga側に動的に追加されたトークナイザーに対応できなくなります。Groongaに動的に追加されるトークナイザーにはMeCabを用いたトークナイザーもあり、この制限に縛られることは利便性を損なうと判断し、以下のようなコメントを用いた独自の構文を採用することにしました。:

    FULLTEXT INDEX (content) COMMENT 'tokenizer "TokenMecab"'

注釈

`FULLTEXT INDEX` に `COMMENT` を指定できるのはMySQL 5.5からになります。MySQL 5.1を利用している場合は後述の `mroonga_default_tokenizer` 変数を利用してください。

トークナイザーに指定できるのは以下の値です。Mroongaで言う「トークナイザー」とMySQLで言う「パーサー」は同じものです。

利用可能なトークナイザー一覧
トークナイザー | 説明
--- | ---
`off` | (`非推奨`) トークナイズしません。 `off` は `content` をそのまま扱いたい場合に使います。例えば、この値は前方一致検索のために指定します。
`none` | Added in version 8.07: トークナイズしません。"off"は `content` をそのまま扱いたい場合に使います。例えば、この値は前方一致検索のために指定します。
`TokenBigram` | バイグラムでトークナイズする。ただし、連続したアルファベット・連続した数字・連続した記号はそれぞれ1つのトークンとして扱う。そのため、3文字以上のトークンも存在する。これはノイズを減らすためである。 デフォルト値。
`TokenMecab` | MeCabを用いてトークナイズする。groongaがMeCabサポート付きでビルドされている必要がある。
`TokenBigramSplitSymbol` | バイグラムでトークナイズする。 `TokenBigram` と異なり、記号が連続していても特別扱いして1つのトークンとして扱わず通常のバイグラムの処理を行う。 TokenBigramではなく `TokenBigramSplitSymbol` を利用すると「 `Is it really!?!?!?` 」の「 `!?!?!?` 」の部分に「 `!?` 」でマッチする。 `TokenBigram` の場合は「 `!?!?!?` 」でないとマッチしない。
`TokenBigramSplitSymbolAlpha` | バイグラムでトークナイズする。 `TokenBigramSplitSymbol` に加えて、連続したアルファベットも特別扱いせずに通常のバイグラムの処理を行う。 `TokenBigram` ではなく `TokenBigramSplitSymbolAlpha` を利用すると「 `Is it really?` 」に「 `real` 」でマッチする。 `TokenBigram` の場合は「 `really` 」でないとマッチしない。
`TokenBigramSplitSymbolAlphaDigit` | バイグラムでトークナイズする。 `TokenBigramSplitSymbolAlpha` に加えて、連続した数字も特別扱いせずに通常のバイグラムの処理を行う。つまり、すべての字種を特別扱いせずにバイグラムの処理を行う。 `TokenBigram` ではなく `TokenBigramSplitSymbolAlphaDigit` を利用すると「 `090-0123-4567` 」に「 `567` 」でマッチする。 `TokenBigram` の場合は「 `4567` 」でないとマッチしない。
`TokenBigramIgnoreBlank` | バイグラムでトークナイズする。 `TokenBigram` と異なり、空白を無視して処理する。 `TokenBigram` ではなく `TokenBigramIgnoreBlank` を利用すると「 `み な さ ん 注 目` 」に「 `みなさん` 」でマッチする。 `TokenBigram` の場合は「 `み な さ ん` 」でないとマッチしない。
`TokenBigramIgnoreBlankSplitSymbol` | バイグラムでトークナイズする。 `TokenBigramSplitSymbol` と異なり、空白を無視して処理する。 `TokenBigramSplitSymbol` ではなく `TokenBigramIgnoreBlankSplitSymbol` を利用すると「 `! !? ??` 」に「 `???` 」でマッチする。 `TokenBigramSplitSymbol` の場合は「 `? ??` 」でないとマッチしない。
`TokenBigramIgnoreBlankSplitSymbolAlpha` | バイグラムでトークナイズする。 `TokenBigramSplitSymbolAlpha` と異なり、空白を無視して処理する。 `TokenBigramSplitSymbolAlpha` ではなく `TokenBigramIgnoreBlankSplitSymbolAlpha` を利用すると「 `I am a pen.` 」に「 `ama` 」でマッチする。 `TokenBigramSplitSymbolAlpha` の場合は「 `am a` 」でないとマッチしない。
`TokenBigramIgnoreBlankSplitSymbolAlphaDigit` | バイグラムでトークナイズする。 `TokenBigramSplitSymbolAlphaDigit` と異なり、空白を無視して処理する。 `TokenBigramSplitSymbolAlphaDigit` ではなく `TokenBigramIgnoreBlankSplitSymbolAlphaDigit` を利用すると「 `090 0123 4567` 」に「 `9001` 」でマッチする。 `TokenBigramSplitSymbolAlphaDigit` の場合は「 `90 01` 」でないとマッチしない。
`TokenDelimit` | 空白区切りでトークナイズする。 「 `movie horror topic` 」をトークナイズすると「 `movie` 」、「 `horror` 」、「 `topic` 」になります。
`TokenDelimitNull` | null文字（ `\\0` ）区切りでトークナイズする。 「 `movie\\0horror\\0topic` 」をトークナイズすると「 `movie` 」、「 `horror` 」、「 `topic` 」になります。
`TokenUnigram` | ユニグラムでトークナイズする。ただし、連続したアルファベット・連続した数字・連続した記号はそれぞれ1つのトークンとして扱う。そのため、2文字以上のトークンも存在する。これはノイズを減らすためである。
`TokenTrigram` | トリグラムでトークナイズする。ただし、連続したアルファベット・連続した数字・連続した記号はそれぞれ1つのトークンとして扱う。そのため、4文字以上のトークンも存在する。これはノイズを減らすためである。

デフォルトのパーサは `configure` の `--with-default-tokenizer` オプションでビルド時に指定することができます。:

    ./configure --with-default-tokenizer TokenMecab ...

また、 `my.cnf` またはSQL内で `mroonga_default_tokenizer` 変数を指定することでも指定できます。 `my.cnf` で指定するとMySQLを再起動しても値は変更されたままですが、反映させるために再起動しなければいけません。一方、SQLで指定した場合はすぐに設定が反映されますが、MySQLが再起動すると設定は失われます。

my.cnf:

    [mysqld]
    mroonga_default_tokenizer=TokenMecab

SQL:

    SET GLOBAL mroonga_default_tokenizer = TokenMecab;
    -- Query OK, 0 rows affected (0.00 sec)

## 4.3.4. ノーマライザーの指定方法

Mroongaは、文書のエンコーディング(照合順序)に応じたノーマライザーを使用します。これはテキストをトークナイズするときとテーブルのキーを保存するときに使われます。

`utf8_general_ci` または `utf8mb4_general_ci` の場合、`NormalizerMySQLGeneralCI` ノーマライザーが使用されます。

`utf8_unicode_ci` または `utf8mb4_unicode_ci` の場合、`NormalizerMySQLUnicodeCI` ノーマライザーが使用されます。

`utf8_bin` の場合、ノーマライザーは使用されません。

以下は、 `utf8_unicode_ci` の照合順序を指定して `NormalizerMySQLUnicodeCI` ノーマライザーを使用する例です。

    SET NAMES utf8;
    -- Query OK, 0 rows affected (0.00 sec)

    CREATE TABLE diaries (
      day DATE PRIMARY KEY,
      content VARCHAR(64) NOT NULL,
      FULLTEXT INDEX (content)
    ) Engine=Mroonga DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
    -- Query OK, 0 rows affected (0.18 sec)

    INSERT INTO diaries VALUES ("2013-04-23", "ブラックコーヒーを飲んだ。");
    -- Query OK, 1 row affected (0.00 sec)

    SELECT *
      FROM diaries
     WHERE MATCH (content) AGAINST ("+ふらつく" IN BOOLEAN MODE);
    -- +------------+-----------------------------------------+
    -- | day        | content                                 |
    -- +------------+-----------------------------------------+
    -- | 2013-04-23 | ブラックコーヒーを飲んだ。 |
    -- +------------+-----------------------------------------+
    -- 1 row in set (0.00 sec)

    SELECT *
      FROM diaries
     WHERE MATCH (content) AGAINST ("+ﾌﾞﾗｯｸ" IN BOOLEAN MODE);
    -- +------------+-----------------------------------------+
    -- | day        | content                                 |
    -- +------------+-----------------------------------------+
    -- | 2013-04-23 | ブラックコーヒーを飲んだ。 |
    -- +------------+-----------------------------------------+
    -- 1 row in set (0.00 sec)

Mroongaは、Groongaのノーマライザーを指定する以下のような構文を持っています。:

    FULLTEXT INDEX (content) COMMENT 'normalizer "NormalizerAuto"'

詳細は Groongaのノーマライザーのドキュメント を確認してください。

以下は、`NormalizerAuto` のノーマライザーを使用する例です。

    SET NAMES utf8;
    -- Query OK, 0 rows affected (0.00 sec)

    CREATE TABLE diaries (
      day DATE PRIMARY KEY,
      content VARCHAR(64) NOT NULL,
      FULLTEXT INDEX (content) COMMENT 'normalizer "NormalizerAuto"'
    ) Engine=Mroonga DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
    -- Query OK, 0 rows affected (0.19 sec)

    INSERT INTO diaries VALUES ("2013-04-23", "ブラックコーヒーを飲んだ。");
    -- Query OK, 1 row affected (0.00 sec)

    SELECT *
      FROM diaries
     WHERE MATCH (content) AGAINST ("+ふらつく" IN BOOLEAN MODE);
    -- Empty set (0.00 sec)

    SELECT *
      FROM diaries
     WHERE MATCH (content) AGAINST ("+ﾌﾞﾗｯｸ" IN BOOLEAN MODE);
    -- +------------+-----------------------------------------+
    -- | day        | content                                 |
    -- +------------+-----------------------------------------+
    -- | 2013-04-23 | ブラックコーヒーを飲んだ。 |
    -- +------------+-----------------------------------------+
    -- 1 row in set (0.00 sec)

## 4.3.5. トークンフィルターの指定方法

Mroongaは、Groongaのトークンフィルターを指定する以下のような構文を持っています。:

    FULLTEXT INDEX (content) COMMENT 'token_filters "TokenFilterStem"'

以下は、`TokenFilterStem` のトークンフィルターを使用する例です。

    SELECT mroonga_command('register token_filters/stem');
    -- +------------------------------------------------+
    -- | mroonga_command('register token_filters/stem') |
    -- +------------------------------------------------+
    -- | true                                           |
    -- +------------------------------------------------+
    -- 1 row in set (0.00 sec)

    CREATE TABLE memos (
      id INT NOT NULL PRIMARY KEY,
      content TEXT NOT NULL,
      FULLTEXT INDEX (content) COMMENT 'normalizer "NormalizerAuto", token_filters "TokenFilterStem"'
    ) Engine=Mroonga DEFAULT CHARSET=utf8;
    -- Query OK, 0 rows affected (0.18 sec)

    INSERT INTO memos VALUES (1, "I develop Groonga");
    -- Query OK, 1 row affected (0.00 sec)

    INSERT INTO memos VALUES (2, "I'm developing Groonga");
    -- Query OK, 1 row affected (0.00 sec)

    INSERT INTO memos VALUES (3, "I developed Groonga");
    -- Query OK, 1 row affected (0.00 sec)

    SELECT *
      FROM memos
     WHERE MATCH (content) AGAINST ("+develops" IN BOOLEAN MODE);
    -- +----+------------------------+
    -- | id | content                |
    -- +----+------------------------+
    -- |  1 | I develop Groonga      |
    -- |  2 | I'm developing Groonga |
    -- |  3 | I developed Groonga    |
    -- +----+------------------------+
    -- 3 rows in set (0.01 sec)

詳細は Groongaのトークンフィルターのドキュメント を確認してください。

以下は、`TokenFilterStopWord` のトークンフィルターを使用する例です。

    SELECT mroonga_command("register token_filters/stop_word");
    -- +-----------------------------------------------------+
    -- | mroonga_command("register token_filters/stop_word") |
    -- +-----------------------------------------------------+
    -- | true                                                |
    -- +-----------------------------------------------------+
    -- 1 row in set (0.00 sec)

    CREATE TABLE terms (
      term VARCHAR(64) NOT NULL PRIMARY KEY,
      is_stop_word BOOL NOT NULL
    ) Engine=Mroonga COMMENT='tokenizer "TokenBigram", token_filters "TokenFilterStopWord"' DEFAULT CHARSET=utf8;
    -- Query OK, 0 rows affected (0.12 sec)

    CREATE TABLE memos (
      id INT NOT NULL PRIMARY KEY,
      content TEXT NOT NULL,
      FULLTEXT INDEX (content) COMMENT 'table "terms"'
    ) Engine=Mroonga DEFAULT CHARSET=utf8;
    -- Query OK, 0 rows affected (0.17 sec)

    INSERT INTO terms VALUES ("and", true);
    -- Query OK, 1 row affected (0.00 sec)

    INSERT INTO memos VALUES (1, "Hello");
    -- Query OK, 1 row affected (0.00 sec)

    INSERT INTO memos VALUES (2, "Hello and Good-bye");
    -- Query OK, 1 row affected (0.00 sec)

    INSERT INTO memos VALUES (3, "Good-bye");
    -- Query OK, 1 row affected (0.00 sec)

    SELECT *
      FROM memos
     WHERE MATCH (content) AGAINST ('+"Hello and"' IN BOOLEAN MODE);
    -- +----+--------------------+
    -- | id | content            |
    -- +----+--------------------+
    -- |  1 | Hello              |
    -- |  2 | Hello and Good-bye |
    -- +----+--------------------+
    -- 2 rows in set (0.01 sec)

これは、全文検索用の語彙表テーブルを指定する方法を使用しています。

## 4.3.6. Groongaのカラムフラグの指定方法

Mroongaは、Groongaのカラムフラグを指定する以下のような構文を持っています。:

    content TEXT COMMENT 'flags "COLUMN_SCALAR|COMPRESS_ZLIB"'

以下は、`COMPRESS_ZLIB` フラグを使用する例です。

    CREATE TABLE entries (
      id INT UNSIGNED PRIMARY KEY,
      content TEXT COMMENT 'flags "COLUMN_SCALAR|COMPRESS_ZLIB"'
    ) Engine=Mroonga DEFAULT CHARSET=utf8;
    -- Query OK, 0 rows affected (0.12 sec)

Groongaのカラムフラグの詳細については Groongaのドキュメント を参照してください。

## 4.3.7. 位置情報検索の利用方法

ストレージモードでは全文検索だけではなく位置情報検索も高速に実行できます。ただし、MyISAMとは異なりデータとして格納できるのはPOINT型のみです。LINEなどの他のデータ型は保存できません。また、インデックスを用いた高速な検索に対応しているのはMBRContainsだけです。MBRDisjointなどには対応していません。

位置情報検索を利用する場合のテーブル定義はMyISAMと同様にPOINT型のカラムを定義し、そのカラムに対してSPATIAL INDEXを指定します。

    CREATE TABLE shops (
      id INT PRIMARY KEY AUTO_INCREMENT,
      name VARCHAR(255),
      location POINT NOT NULL,
      SPATIAL INDEX (location)
    ) ENGINE = Mroonga;
    -- Query OK, 0 rows affected (0.06 sec)

データの登録方法もMyISAMのときと同様にGeomFromText()関数を使って文字列からPOINT型の値を作成します。

    INSERT INTO shops VALUES (null, 'Nezu''s Taiyaki', GeomFromText('POINT(139.762573 35.720253)'));
    -- Query OK, 1 row affected (0.00 sec)

    INSERT INTO shops VALUES (null, 'Naniwaya', GeomFromText('POINT(139.796234 35.730061)'));
    -- Query OK, 1 row affected (0.00 sec)

    INSERT INTO shops VALUES (null, 'Yanagiya Taiyaki', GeomFromText('POINT(139.783981 35.685341)'));
    -- Query OK, 1 row affected (0.00 sec)

池袋駅（139.7101 35.7292）が左上の点、東京駅（139.7662 35.6815）が右下の点となるような長方形内にあるお店を探す場合は以下のようなSELECTになります。

    SELECT id, name, AsText(location)
      FROM shops
     WHERE MBRContains(GeomFromText('LineString(139.7101 35.7292, 139.7662 35.6815)'), location);
    -- +----+-----------------------+------------------------------------------+
    -- | id | name                  | AsText(location)                         |
    -- +----+-----------------------+------------------------------------------+
    -- |  1 | Nezu's Taiyaki        | POINT(139.762572777778 35.7202527777778) |
    -- +----+-----------------------+------------------------------------------+
    -- 1 row in set (0.00 sec)

位置情報で検索できていますね！

## 4.3.8. レコードIDの取得方法

Groongaではテーブルにレコードを追加した際にレコードを一意に識別するための番号が割当てられます。

Mroongaではアプリケーションの開発を容易にするため、このレコードIDをSQLで取得できるようになっています。

レコードIDを取得するためには、テーブル定義時に `_id` という名前のカラムを作成して下さい。

    CREATE TABLE memos (
      _id INT,
      content VARCHAR(255),
      UNIQUE KEY (_id) USING HASH
    ) ENGINE = Mroonga;
    -- Query OK, 0 rows affected (0.04 sec)

`_id` カラムのデータ型は整数型(TINYINT、SMALLINT、MEDIUMINT、INT、BIGINT)である必要があります。

また_idカラムにはインデックスを作成することが可能ですが、HASH形式である必要があります。

INSERTでテーブルにレコードを追加してみましょう。_idカラムは仮想カラムとして実装されており、また_idの値であるレコードIDはGroongaにより割当てられるため、SQLによる更新時に値を指定することはできません。更新対象から外すか、値に `null` を使用する必要があります。

    INSERT INTO memos VALUES (null, "Saury for today's dinner.");
    -- Query OK, 1 row affected (0.00 sec)

    INSERT INTO memos VALUES (null, "Update mroonga tomorrow.");
    -- Query OK, 1 row affected (0.00 sec)

    INSERT INTO memos VALUES (null, "Buy some dumpling on the way home.");
    -- Query OK, 1 row affected (0.00 sec)

    INSERT INTO memos VALUES (null, "Thank God It's meat day.");
    -- Query OK, 1 row affected (0.00 sec)

レコードIDを取得するには、_idカラムを含むようにしてSELECTを行います。

    SELECT * FROM memos;
    -- +------+------------------------------------------+
    -- | _id  | content                                  |
    -- +------+------------------------------------------+
    -- |    1 | Saury for today's dinner.                |
    -- |    2 | Update mroonga tomorrow.                 |
    -- |    3 | Buy some dumpling on the way home.       |
    -- |    4 | Thank God It's meat day.                 |
    -- +------+------------------------------------------+
    -- 4 rows in set (0.00 sec)

また直前のINSERTにより割当てられたレコードIDについては、last_insert_grn_id関数により取得することもできます。

    INSERT INTO memos VALUES (null, "Just one bottle of milk in the fridge.");
    -- Query OK, 1 row affected (0.00 sec)

    SELECT last_insert_grn_id();
    -- +----------------------+
    -- | last_insert_grn_id() |
    -- +----------------------+
    -- |                    5 |
    -- +----------------------+
    -- 1 row in set (0.00 sec)

last_insert_grn_id関数はユーザ定義関数(UDF)としてMroongaに含まれていますが、インストール時にCREATE FUNCTIONでMySQLに追加していない場合には、以下の関数定義DDLを実行しておく必要があります。

    CREATE FUNCTION last_insert_grn_id RETURNS INTEGER SONAME 'ha_mroonga.so';

ご覧のように_idカラムやlast_insert_grn_id関数を通じてレコードIDを取得することができました。ここで取得したレコードIDは後続のUPDATEなどのSQL文で利用すると便利です。

    UPDATE memos SET content = "So much milk in the fridge." WHERE _id = last_insert_grn_id();
    -- Query OK, 1 row affected (0.00 sec)
    -- Rows matched: 1  Changed: 1  Warnings: 0

## 4.3.9. スニペット（キーワード周辺のテキスト）の取得方法

Mroongaは周辺テキスト付きでキーワードを取得する機能を提供しています。この機能は mroonga_snippet() UDFとして実装されています。

## 4.3.10. 類似文書検索の利用方法

類似文書検索では、クエリーにキーワードの代わりに文書の内容を指定します。

類似文書検索は、関連する文書を見つけるのに便利です。

実行例で使用するスキーマ定義はこちら。

    CREATE TABLE similarities (
      id INT PRIMARY KEY AUTO_INCREMENT,
      title VARCHAR(32),
      content VARCHAR(255),
      FULLTEXT INDEX (content)
    ) ENGINE = Mroonga DEFAULT CHARSET utf8;

実行例で使用するサンプルデータはこちら。

    INSERT INTO similarities (title, content)
           VALUES ('Groonga similar search', 'Groonga is an open-source fulltext search engine and column store.');
    INSERT INTO similarities (title, content)
           VALUES ('Mroonga similar search', 'Mroonga is an open-source storage engine for fast fulltext search with MySQL.');
    INSERT INTO similarities (title, content)
           VALUES ('Rroonga library', 'A library to use Groonga features from Ruby.');

以下は文書の内容による類似文書検索の出力例です。

    SELECT title FROM similarities WHERE MATCH(content) AGAINST ('There are many open-source fulltext search engine.' IN NATURAL LANGUAGE MODE);

AGAINST('...' IN NATURAL LANGUAGE MODE) に文書の内容を指定する必要があることに注意してください。

    SELECT title FROM similarities WHERE MATCH(content) AGAINST ('There are many open-source fulltext search engine.' IN NATURAL LANGUAGE MODE);
    -- +------------------------+
    -- | title                  |
    -- +------------------------+
    -- | Groonga similar search |
    -- | Mroonga similar search |
    -- +------------------------+
    -- 2 rows in set (0.00 sec)

類似度合いを向上させるには、言語特有のパーサーを使うとか、タグ付けされたデータを使って望ましくない検索結果を取り除く必要があります。

例えば、日本語の文書の類似文書検索をするなら、TokenMecab をパーサーに指定するのがおすすめです。以下は TokenMecab を使うスキーマ定義の例です。:

    CREATE TABLE similarities (
      id INT PRIMARY KEY AUTO_INCREMENT,
      title VARCHAR(32),
      content VARCHAR(255),
      FULLTEXT INDEX (content) COMMENT 'tokenizer "TokenMecab"'
    ) ENGINE = Mroonga DEFAULT CHARSET utf8;

## 4.3.11. Groongaコマンドの実行方法

ストレージモードではMroongaはすべてのデータをGroongaのデータベースに保存します。Mroongaを使うことでSQLでGroongaのデータベースにアクセスできるようになります。SQLはとても強力ですが、ファセット検索などのようないくつかの操作が得意ではありません。

最近ではファセット検索は一般的なものになりました。amazon.comやebay.comのように多くのオンラインショッピングサイトがファセット検索をサポートしています。ファセット検索はユーザが検索結果を絞り込む前に絞り込み検索をし、その絞り込み検索の結果を表示します。ユーザは絞りこまれた結果から自分が探しているものを選ぶだけです。ファセット検索を使うとユーザは以下のようなメリットがあります。

- ユーザはどうやって検索結果を絞り込むかを考える必要はありません。単に表示された絞り込み結果から選ぶだけです。

- ユーザは「見つかりませんでした」ページを見ることがありません。ファセット検索では絞り込んだ結果のうち、ヒットする項目がある結果だけを表示します。

絞り込み検索は検索結果に対して複数の `GROUP BY` 操作を実行する必要があります。SQLでファセット検索をやろうとすると、複数の `SELECT` リクエストが必要になります。これは効率的ではありません。

Groongaは1回のgroongaコマンドでファセット検索をできます。これは効率的です。Groongaには `select` コマンドというファセット検索に対応した検索コマンドがあります。Groongaではファセット検索は「ドリルダウン（drilldown）」と呼んでいます。Groongaの `select` コマンドの詳細については Groongaのドキュメント を参照してください。

Mroongaは `mroonga_command()` 関数を提供しています。この関数を使えばSQLの中で好きなGroongaコマンドを実行できます。しかし、使うのは `select` コマンドだけにしておくべきです。スキーマやデータを変更するコマンドを使うと一貫性が壊れてしまうかもしれません。

実行例で使用するスキーマ定義はこちら。

    CREATE TABLE diaries (
      id INT PRIMARY KEY AUTO_INCREMENT,
      content VARCHAR(255),
      date DATE,
      year YEAR,
      `year_month` VARCHAR(9),
      tag VARCHAR(32),
      FULLTEXT INDEX (content)
    ) ENGINE = Mroonga DEFAULT CHARSET utf8;

実行例で使用するサンプルデータはこちら。

    INSERT INTO diaries (content, date, year, `year_month`, tag)
           VALUES ('Groonga is an open-source fulltext search engine and column store.',
                   '2013-04-08',
                   '2013',
                   '2013-04',
                   'groonga');
    INSERT INTO diaries (content, date, year, `year_month`, tag)
           VALUES ('Mroonga is an open-source storage engine for fast fulltext search with MySQL.',
                   '2013-04-09',
                   '2013',
                   '2013-04',
                   'MySQL');
    INSERT INTO diaries (content, date, year, `year_month`, tag)
           VALUES ('Tritonn is a patched version of MySQL that supports better fulltext search function with Senna.',
                   '2013-03-29',
                   '2013',
                   '2013-03',
                   'MySQL');

各レコードには `tag` として `groonga` あるいは `MySQL` が付いています。各レコードは `year` と `year_month` カラムも持っています。ファセット検索のキーとして `tag` 、 `year` 、 `year_month` を使えます。

Groongaはファセット検索のことをドリルダウンと呼んでいます。そのため、Groongaでのパラメータ名は `--drilldown` となっています。Groongaは検索結果をJSONで返します。そのため、 `mroonga_command()` も検索結果をJSONで返します。これはSQLらしくありません。JSON形式の検索結果は自分でパースしないといけません。

以下は利用可能なファセット検索キーをすべて使った例です。（結果のJSONは整形済み）

    SELECT mroonga_command("select diaries --output_columns _id --limit 0 --drilldown tag,year,year_month") AS faceted_result;
    -- +-----------------------------+
    -- | faceted_result              |
    -- +-----------------------------+
    -- | [[[3],                      |
    -- |   [["_id","UInt32"]]],      |
    -- |  [[2],                      |
    -- |   [["_key","ShortText"],    |
    -- |    ["_nsubrecs","Int32"]],  |
    -- |   ["groonga",1],            |
    -- |   ["MySQL",2]],             |
    -- |  [[1],                      |
    -- |   [["_key","Time"],         |
    -- |    ["_nsubrecs","Int32"]],  |
    -- |   [1356998400.0,3]],        |
    -- |  [[2],                      |
    -- |   [["_key","ShortText"],    |
    -- |    ["_nsubrecs","Int32"]],  |
    -- |   ["2013-04",2],            |
    -- |   ["2013-03",1]]]           |
    -- +-----------------------------+
    -- 1 row in set (0.00 sec)

最初の要素 `[[3], [["_id","UInt32"]]]` は通常の検索の結果です。これはファセット検索の結果ではありません。

2番目、3番目、4番目の要素がファセット検索の結果です。

    [[2],
     [["_key","ShortText"],
      ["_nsubrecs","Int32"]],
     ["groonga",1],
     ["MySQL",2]]

    [[1],
     [["_key","Time"],
      ["_nsubrecs","Int32"]],
     [1356998400.0,3]]

    [[2],
     [["_key","ShortText"],
      ["_nsubrecs","Int32"]],
     ["2013-04",2],
     ["2013-03",1]]

ファセット検索の結果の順序は `--drilldown` に指定した値に対応します。この例では、 `--drilldown` の値に `tag` と `year` と `year_month` を指定しています。そのため、最初のファセット検索の結果は `tag` 用の結果で、2番目は `year` 用の結果で、 3番目は `year_month` 用の結果です。

各ファセット検索の結果は次のフォーマットになります。このフォーマットは通常の検索の結果と同じです。

    [[${THE_NUMBER_OF_RECORDS}],
     [[${OUTPUT_COLUMN_NAME_0}, ${OUTPUT_COLUMN_TYPE_0}],
      [${OUTPUT_COLUMN_NAME_1}, ${OUTPUT_COLUMN_TYPE_1}],
      ...,
      [${OUTPUT_COLUMN_NAME_N}, ${OUTPUT_COLUMN_TYPE_N}]]
     [${OUTPUT_COLUMN_VALUE_0_FOR_RECORD_0},
      ${OUTPUT_COLUMN_VALUE_1_FOR_RECORD_0},
      ...,
      ${OUTPUT_COLUMN_VALUE_N_FOR_RECORD_0}],
     [${OUTPUT_COLUMN_VALUE_0_FOR_RECORD_1},
      ${OUTPUT_COLUMN_VALUE_1_FOR_RECORD_1},
      ...,
      ${OUTPUT_COLUMN_VALUE_N_FOR_RECORD_1}],
     ...
     [${OUTPUT_COLUMN_VALUE_0_FOR_RECORD_M},
      ${OUTPUT_COLUMN_VALUE_1_FOR_RECORD_M},
      ...,
      ${OUTPUT_COLUMN_VALUE_N_FOR_RECORD_M}]]

ファセット検索の結果の `_key` カラムの値はファセット検索で使用したキーを示します。

たとえば、最初のファセット検索の結果（ `tag` のファセット検索の結果）はマッチしたレコード（今回のケースではすべてのレコード）は `tag` の値として `groonga` と `MySQL` があることを示しています。

    [[2],
     [["_key","ShortText"],
      ["_nsubrecs","Int32"]],
     ["groonga",1],
     ["MySQL",2]]

ファセット検索の結果の `_nsubrecs` カラムの値は対応するファセット検索のキーを持つレコードが何件あるかを示します。

たとえば、最初のファセット検索の結果（ `tag` のファセット検索の結果）は `tag` の値として `groonga` を持つレコードは1件あり、 `tag` の値として `MySQL` を持つレコードは2件あることを示しています。

    [[2],
     [["_key","ShortText"],
      ["_nsubrecs","Int32"]],
     ["groonga",1],
     ["MySQL",2]]

詳細は Groongaのselectコマンドのドキュメント を確認してください。

## 4.3.12. 正規表現検索の利用方法

ストレージモードでは、すでに述べたようにMroongaからGroongaの機能が使えます。つまりGroongaの機能を使って正規表現で検索できます。

Mroongaで正規表現を使うにはいくつか条件があります。

- `COMMENT` にて `TokenRegexp` トークナイザーを指定してインデックスを作成する

- `WHERE MATCH ... AGAINST` にて `*SS` プラグマを使う

以下は正規表現による検索の出力例です。

    CREATE TABLE paths (
      content text,
      FULLTEXT INDEX content_index (content) COMMENT 'tokenizer "TokenRegexp", normalizer "NormalizerAuto"'
    ) ENGINE=Mroonga DEFAULT CHARSET=utf8mb4;

    INSERT INTO paths VALUES ('/usr/bin/groonga');
    INSERT INTO paths VALUES ('/var/log/auth.log');
    INSERT INTO paths VALUES ('/var/log/messages');
    INSERT INTO paths VALUES ('/tmp/local/var/log/auth.log');

    SELECT * FROM paths WHERE MATCH(content) AGAINST ('*SS content @~ "\\\\A/var/log/auth"' IN BOOLEAN MODE);
    -- +-------------------+
    -- | content           |
    -- +-------------------+
    -- | /var/log/auth.log |
    -- +-------------------+
    -- 1 row in set (0.024 sec)

*SS プラグマを使うことにより、 `content @~ "\\\\A/var/log/auth"` で `/var/log/auth` ではじまるレコードを検索できます。 `@~` はGroongaで正規表現検索するためのオペレーターで、 `"\\\\A/var/log/auth"` は前方一致検索を行うので、 `/var/log/auth.log` だけがマッチします。 `/tmp/local/var/log/auth.log` がマッチしないのは、`/var/log/auth` ではじまっていないからです。

構文の詳細は Groongaの正規表現のドキュメント を確認してください。

## 4.3.13. ログ出力

Mroongaではデフォルトでログの出力を行うようになっています。

ログファイルはMySQLのデータディレクトリ直下に `groonga.log` というファイル名で出力されます。

以下はログの出力例です。

    2010-10-07 17:32:39.209379|n|b1858f80|mroonga 1.10 started.
    2010-10-07 17:32:44.934048|d|46953940|hash get not found (key=test)
    2010-10-07 17:32:44.936113|d|46953940|hash put (key=test)

ログのデフォルトの出力レベルはNOTICE（必要な情報のみ出力。デバッグ情報などは出力しない）となっております。

ログの出力レベルは `mroonga_log_level` というシステム変数で確認することができます（グローバル変数）。またSET文で動的に出力レベルを変更することもできます。

    SHOW VARIABLES LIKE 'mroonga_log_level';
    -- +-------------------+--------+
    -- | Variable_name     | Value  |
    -- +-------------------+--------+
    -- | mroonga_log_level | NOTICE |
    -- +-------------------+--------+
    -- 1 row in set (0.00 sec)

    SET GLOBAL mroonga_log_level=DUMP;
    -- Query OK, 0 rows affected (0.00 sec)

    SHOW VARIABLES LIKE 'mroonga_log_level';
    -- +-------------------+-------+
    -- | Variable_name     | Value |
    -- +-------------------+-------+
    -- | mroonga_log_level | DUMP  |
    -- +-------------------+-------+
    -- 1 row in set (0.00 sec)

設定可能なログレベルは以下の通りです。

- NONE

- EMERG

- ALERT

- CRIT

- ERROR

- WARNING

- NOTICE

- INFO

- DEBUG

- DUMP

詳細は mroonga_log_level を参照してください。

またFLUSH LOGSでログの再オープンを行うことができます。MySQLサーバを停止せずにログのローテートを行いたいような場合には、以下の手順で実行すると良いでしょう。

1. `groonga.log` ファイルの名前を変更（OSコマンドのmvなどで）

2. MySQLサーバに対して"FLUSH LOGS"を実行（mysqlコマンドあるいはmysqladminコマンドにて）

## 4.3.14. 次のステップ

これでストレージモードでMroongaを使えるようになりました！Mroongaをもっと速くしたい場合は 最適化 も参照してください。

脚注

[1]

MySQL 5.1 リファレンスマニュアル :: 11 関数と演算子 :: 11.7 全文検索関数

[2]

Groongaではトークナイザーと呼んでいる。