目次

Mroonga9.12 検索チュートリアル

Version 9.12 (MariaDB 10.4.12)

y2sunlight 2020-11-02

Mroonga に戻る

関連記事

リンク

本編では、Mroonga公式サイトのチュートリアルを参考にしてMroongaについて使い方を検索SQLを中心に説明したいと思います。



チュートリアル用データベースの作成

ここでは phpmyadmin を使ってチュートリアルを進めます。

最初にMroongaのテスト用のデータベースを作ります。[SQL]メニューを選択して以下を実行します。

-- Database : fulltext_test
CREATE DATABASE IF NOT EXISTS `fulltext_test`
  DEFAULT CHARACTER SET utf8 COLLATE utf8_bin;
 
-- Privileges
GRANT SELECT, INSERT, DELETE, UPDATE, ALTER ON `fulltext_test`.* TO
   'fulltext_test'@localhost;

phpmyadmin の表示更新して、データベース [fulltext_test] の[SQL]メニューを選択します。以降、この状態でSQL文を入力しながら本章のチュートリアルを進めていきます。


ストレージモードとラッパーモード

Mroonga には以下の2つの動作モードがあります。

詳しくは、Mroonga のドキュメントをご覧ください。


ストレージモードの利用

通常のストレージエンジンを指定する構文( ENGINE = Mroonga )を使って以下のようにSQLを書きます。

CREATE TABLE diaries (
  id INT PRIMARY KEY AUTO_INCREMENT,
  content VARCHAR(255),
  FULLTEXT INDEX (content)
) ENGINE = Mroonga COLLATE utf8_unicode_ci;
 
INSERT INTO diaries (content) VALUES ("今日の天気は晴れでしょう。");
INSERT INTO diaries (content) VALUES ("今日の天気は雨でしょう。");
INSERT INTO diaries (content) VALUES ("明日の東京都の天気は晴れでしょう。");
INSERT INTO diaries (content) VALUES ("明日の京都の天気は雨でしょう。");


ラッパーモードの利用

ラップする対象となるストレージエンジン(全文検索機能以外のデータストア機能を使用するエンジン)は、コメントを利用して COMMENT = 'engine "InnoDB"' のように指定します。

CREATE TABLE diaries_wrapper (
  id INT PRIMARY KEY AUTO_INCREMENT,
  content VARCHAR(255),
  FULLTEXT INDEX (content)
) ENGINE = Mroonga COMMENT = 'engine "InnoDB"' COLLATE utf8_unicode_ci;
 
INSERT INTO diaries_wrapper (content) VALUES ("今日の天気は晴れでしょう。");
INSERT INTO diaries_wrapper (content) VALUES ("今日の天気は雨でしょう。");
INSERT INTO diaries_wrapper (content) VALUES ("明日の東京都の天気は晴れでしょう。");
INSERT INTO diaries_wrapper (content) VALUES ("明日の京都の天気は雨でしょう。");


トランザクションの利用

ストレージモードで作成した diaries テーブルに対して以下のトランザクションを実行します。

START TRANSACTION;
INSERT INTO diaries (content) VALUES ("明後日は全国的に晴れるでしょう。");
ROLLBACK;

ストレージモードのテーブルは、トランザクションが効かないので以下の警告が出力され、diaries テーブルに新しい行がインサートされます。

Warning: #1196 トランザクション対応ではない表への変更はロールバックされません。

同じことをラッパーモードで作成した diaries_wrapper テーブルに対して行います。

START TRANSACTION;
INSERT INTO diaries_wrapper (content) VALUES ("明後日は全国的に晴れるでしょう。");
ROLLBACK;

この場合、トランザクションはロールバックされ結果的に新しい行はインサートされません。

diaries_wrapper テーブルは全文検索機能のみ Mroonga を利用し、その他の機能は create 時に指定したストレージエンジンである innoDB を使っているのでトランザクションが有効に機能しています。


全文検索のSQL構文

MATCH AGAINST 関数

全文検索のSQLには、MATCH AGAINST 関数を使います。

MATCH (col1,col2,...) AGAINST (検索文字列 [search_modifier])
 
search_modifier:
{
   IN NATURAL LANGUAGE MODE  // 自然言語検索
 | IN BOOLEAN MODE           // ブール検索
}

全文検索の対象は FULLTEXT型 のカラムです。このカラムを MATCH( col1,col2,… ) のように指定し、AGAINST には、検索する文字列を指定します。

本来、MySQL(MariaDB)の全文検索には次の3種類の search_modifier があり AGAINST の中で指定します。search_modifier が指定されていない場合は、自然言語検索になります。

本章では、自然言語検索とブール検索について説明します。クエリー拡張した自然言語検索についてはMroongaのドキュメントに記載がなく、ここでは割愛します。詳しくは、MySQLのドキュメントを参照して下さい。


自然言語検索

自然言語検索では、 検索文字列が自然言語でのフレーズ (フリーテキスト内に含まれるフレーズ) として解釈されます。特別な演算子はありません。ストップワードリストがあれば適用されます。

自然言語検索では検索文字列との類似度が評価され、類似度の高いものだけが出力されます。詳しくはMroongaのドキュメントをご覧ください。

例1

SELECT * FROM diaries WHERE MATCH(content) AGAINST('明日の天気');

デフォルトパーサーは 2Gram なので、検索文字列が1文字おき2文字毎に機械的に分解されます。結果的に、検索文字列中の '明日'、'日の'、'の天'、'天気' にヒットする次の行が出力されます。

明日の東京都の天気は晴れでしょう。
明日の京都の天気は雨でしょう。

例2

SELECT * FROM diaries WHERE MATCH(content) AGAINST('明日の京都の天気');

例1と同様に、検索文字列中の '明日'、'日の'、'の京'、'京都'、'都の' 、'の天'、'天気' にヒットする次の行が出力されます。

明日の京都の天気は雨でしょう。

それでは、次の例は、どうなるでしょう?

例3

SELECT * FROM diaries WHERE MATCH(content) AGAINST('明日 京都 天気');

例2の名詞だけを空白文字で区切って検索文字列にしています。結果は以下のようになります。

明日の東京都の天気は晴れでしょう。
明日の京都の天気は雨でしょう。

「東京都」も「京都」を含んでいるのでこのような結果になります。例2で「明日の東京都の天気は晴れでしょう。」が出力されなかったのは、この文章には検索文字列内の「の京」が含まれていないからです。

これはパーサーが 2Gram の時に発生する現象で、検索ノイズと呼ばれるものです。この例の場合は、格助詞「の」が検索結果に影響を与えているのが分かります。この例の場合は形態素解析パーサー(MeCab)を使用することにより回避できます。それは形態素解析パーサーが日本語の文法を解釈して単語分けをするからです。いずれのパーサーを使用するかは、両者には一長一短があるので、適用する分野により良く吟味して決める必要があります。


ブール検索

ブール検索では、検索文字列が全文検索用の特別な演算子を伴う文字列として解釈されます。文字列には、検索対象の単語(検索ワード)の前に演算子を含めることができます。ストップワードリストがあれば適用されます。

ブール検索では類似度による評価はありません。MySQL(MariaDB)の規則に従い自然言語検索(IN NATURAL LANGUAGE MODE)がデフォルトですが、ブール検索(IN BOOLEAN MODE)の方がWeb検索エンジンのクエリーと似ているので親しみやすいかもしれません。

Mrrongaのブール検索についての詳細は、こちらのドキュメントをご覧ください。

代表的なブール演算子は以下の通りです:

例1

SELECT * FROM diaries WHERE MATCH(content) AGAINST('明日 天気 東京' IN BOOLEAN MODE);

3つの検索ワード(明日,天気,東京)の何れかを含む以下の行が出力されます。

明日の東京都の天気は晴れでしょう。
明日の京都の天気は雨でしょう。
今日の天気は晴れでしょう。
今日の天気は雨でしょう。

例2

SELECT * FROM diaries WHERE MATCH(content) AGAINST('+明日 +天気 +東京' IN BOOLEAN MODE);

3つの検索ワード(明日,天気,東京)の全てを含む以下の行が出力されます。

明日の東京都の天気は晴れでしょう。

例3

SELECT * FROM diaries WHERE MATCH(content) AGAINST('+明日 +天気 -東京' IN BOOLEAN MODE);

2つの検索ワード(明日,天気)を含み、(東京)を含まない行が出力されます。

明日の京都の天気は雨でしょう。

例4

SELECT * FROM diaries WHERE MATCH(content) AGAINST('+明日 +天気 +京都' IN BOOLEAN MODE);

3つの検索ワード(明日,天気,東京)の全てを含む以下の行が出力されます。

明日の東京都の天気は晴れでしょう。
明日の京都の天気は雨でしょう。

自然言語検索と同様に、「東京都」も「京都」を含んでいるので「明日の東京都の天気は晴れでしょう。」が検索ノイズとして出力されます。


検索スコアの取得方法

検索スコアを SELECT リストや ORDER BY 句の中で使用したい場合があります。この場合でも、WHERE句と同様に MATCH AGAINST 関数が利用できます。

例1

SELECT *, MATCH(content) AGAINST('明日 東京 天気') AS score
  FROM diaries
  WHERE MATCH(content) AGAINST('明日 東京 天気')

自然言語検索での例です。結果は以下のようになります。

content                         score
------------------------------------------
明日の東京都の天気は晴れでしょう。 1048577

自然言語検索での検索スコアの計算方法についてはMroongaのドキュメント「Mroongaにおける検索スコア」をご覧ください。

例2

SELECT *, MATCH(content) AGAINST('明日 東京 天気' IN BOOLEAN MODE) AS score
  FROM diaries
  WHERE MATCH(content) AGAINST('明日 東京 天気' IN BOOLEAN MODE)

ブール検索での例です。結果は以下のようになります。

content                         score
------------------------------------------
明日の東京都の天気は晴れでしょう。 3
明日の京都の天気は雨でしょう。   2
今日の天気は晴れでしょう。     1
今日の天気は雨でしょう。      1

この例で見る限り、ブール検索での検索スコアはヒットした単語数のように思われますが、実際は単語数に重みを乗じた加重和になります。詳しくはMroongaのドキュメント「w プラグマ」をご覧ください。

例3

SELECT *, MATCH(content) AGAINST('明日 東京 天気' IN BOOLEAN MODE) AS score
  FROM diaries
  WHERE MATCH(content) AGAINST('明日 東京 天気' IN BOOLEAN MODE)
  ORDER BY MATCH(content) AGAINST('明日 東京 天気' IN BOOLEAN MODE)

ORDER BY 句の例です。結果は以下のようになります。

content                         score
------------------------------------------
今日の天気は雨でしょう。      1
今日の天気は晴れでしょう。     1
明日の京都の天気は雨でしょう。   2
明日の東京都の天気は晴れでしょう。 3


様々な検索機能

検索語のハイライト

次のユーザ定義関数を SELECT リスト内で使って、検索語をハイライトするHTMLを出力できます。

mroonga_highlight_html(text, query AS query)
-- または
mroonga_highlight_html(text, keyword1, ..., keywordN)

SELECT mroonga_highlight_html(content, '明日 天気 東京' AS query) AS '検索結果'
  FROM diaries
  WHERE MATCH(content) AGAINST('明日 天気 東京' IN BOOLEAN MODE)

または、以下でも同様の結果になります:

SELECT mroonga_highlight_html(content, '明日','天気','東京') AS '検索結果'
  FROM diaries
  WHERE MATCH(content) AGAINST('明日 天気 東京' IN BOOLEAN MODE)

検索結果は以下のようになります:

<span class="keyword">明日</span><span class="keyword">東京</span>都の<span class="keyword">天気</span>は晴れでしょう。
<span class="keyword">明日</span>の京都の<span class="keyword">天気</span>は雨でしょう。
今日の<span class="keyword">天気</span>は晴れでしょう。
今日の<span class="keyword">天気</span>は雨でしょう。

検索結果内の指定されたキーワードを強調表示します。各キーワードを<span class = "keyword"></span> で囲み、HTMLの特殊文字はエスケープされるので、結果をそのままHTMLで出力できます。

詳しくは、Mroongaのドキュメントをご覧ください。


スニペット

スニペット(snippet)とは「切れ端」という意味で、検索結果として表示されるページへの短い説明文のことを指します。次のユーザ定義関数を SELECT リスト内で使って、検索語周辺のテキストを検索語をハイライトした状態でHTML出力できます。

mroonga_snippet_html(text, keyword1, ..., keywordN)

SELECT mroonga_snippet_html(content, '明日','天気','東京') AS '検索結果'
  FROM diaries
  WHERE MATCH(content) AGAINST('明日 天気 東京' IN BOOLEAN MODE)

検索結果は以下のようになります:

<div class="snippet"><span class="keyword">明日</span><span class="keyword">東京</span>都の<span class="keyword">天気</span>は晴れでしょう。</div>
<div class="snippet"><span class="keyword">明日</span>の京都の<span class="keyword">天気</span>は雨でしょう。</div>
<div class="snippet">今日の<span class="keyword">天気</span>は晴れでしょう。</div>
<div class="snippet">今日の<span class="keyword">天気</span>は雨でしょう。</div>

検索結果内の指定されたキーワードを強調表示して、キーワード周辺のテキストを表示します。全体を <div class = "snippet"></div> で、各キーワードを <span class = "keyword"></span> で囲み、HTMLの特殊文字はエスケープされるので、結果をそのままHTMLで出力できます。

より詳細な設定をしたい場合は、次の mroonga_snippet 関数を使用します。

SELECT
  mroonga_snippet(
    content              -- カラム名やイミディエイト
    , 100                -- スニペットの最大長(バイト単位)
    , 3                  -- スニペットの最大要素数
    , 'utf8_unicode_ci'  -- エンコーティング(照合順序)
    , 1                  -- 先頭の空白を無視する(1)か否(0)か
    , 1                  -- HTMLエスケープを行うか(1)か否(0)か
    , '...'              -- スニペットの開始テキスト
    , '...'              -- スニペットの終了テキスト
    , '明日', '<span class="keyword">', '</span>'  -- キワード,キワードの開始テキスト,ワードの終了テキスト
    , '天気', '<span class="keyword">', '</span>'  -- 同上
    , '東京', '<span class="keyword">', '</span>'  -- 同上
  ) AS snippet
  FROM diaries
  WHERE MATCH(content) AGAINST('明日 天気 東京' IN BOOLEAN MODE)

検索結果は以下のようになります:

...<span class="keyword">明日</span><span class="keyword">東京</span>都の<span class="keyword">天気</span>は晴れでしょう。...
...<span class="keyword">明日</span>の京都の<span class="keyword">天気</span>は雨でしょう。...
...今日の<span class="keyword">天気</span>は晴れでしょう。...
...今日の<span class="keyword">天気</span>は雨でしょう。...

詳しくは、以下のMroongaのドキュメントをご覧ください。


クエリ展開

Mroongaのクエリ展開機能は、同義語や関連語を展開して検索する場合に利用できます。

まずは、以下のような関連語テーブルを準備します。以下の例では、検索対象周辺の都道府県を持つ関連語テーブルを例として使用しますが、同義語テーブルなどが一般的かもしれません。

CREATE TABLE area (
  name VARCHAR(255),
  pref VARCHAR(255),
  INDEX (name)
) ENGINE = Mroonga COLLATE utf8_unicode_ci;
INSERT INTO area VALUES ('東京', '東京 神奈川 埼玉');
INSERT INTO area VALUES ('神奈川', '神奈川 東京 静岡');
INSERT INTO area VALUES ('大阪', '大阪 京都 兵庫');
INSERT INTO area VALUES ('京都', '京都 大阪 滋賀');

このようなテーブルを準備することによって、「東京」、「神奈川」、「埼玉」の何れが検索語として指定されても「東京」がヒットするようになります。尚、この例のように、pref には自分自身の都道府県の名前も含める必要があります。

mroonga_query_expand関数

次のユーザ定義関数を MATCH AGAINST 関数内で使って、クエリ展開機能(この例では関連語展開検索)が利用できます。

mroonga_query_expand(table, term, synonyms, query);

SELECT * FROM diaries
  WHERE MATCH(content) AGAINST(mroonga_query_expand("area", "name", "pref", "+明日 +天気 +東京") IN BOOLEAN MODE)
-- または
SELECT * FROM diaries
  WHERE MATCH(content) AGAINST(mroonga_query_expand("area", "name", "pref", "+明日 +天気 +神奈川") IN BOOLEAN MODE)

上の2つは以下の同じ検索結果を表示します。

明日の東京都の天気は晴れでしょう。

詳しくは、Mroongaのドキュメントを参照して下さい。