MySQL では、IN BOOLEAN MODE
修飾子を使用することでブール全文検索を実行できます。 この修飾子を使用すると、検索文字列の先頭または末尾にある特定の文字が特別な意味を持ちます。 次のクエリーでは、+
および -
演算子は、一致が発生するために単語が存在しなければならないことと、単語が存在してはならないことをそれぞれ示します。 したがって、このクエリーでは、「MySQL」 という単語は含まれるが、「YourSQL」 という単語は含まれないすべての行が取得されます。
mysql> SELECT * FROM articles WHERE MATCH (title,body)
AGAINST ('+MySQL -YourSQL' IN BOOLEAN MODE);
+----+-----------------------+-------------------------------------+
| id | title | body |
+----+-----------------------+-------------------------------------+
| 1 | MySQL Tutorial | DBMS stands for DataBase ... |
| 2 | How To Use MySQL Well | After you went through a ... |
| 3 | Optimizing MySQL | In this tutorial, we show ... |
| 4 | 1001 MySQL Tricks | 1. Never run mysqld as root. 2. ... |
| 6 | MySQL Security | When configured properly, MySQL ... |
+----+-----------------------+-------------------------------------+
この機能を実装すると、MySQL では暗黙的ブール論理とも呼ばれる次のようなものが使用されます。
+
はAND
を表します-
はNOT
を表します[演算子なし] は暗黙的に
OR
を表します
ブール全文検索には、次のような特徴があります。
行は自動的に関連性の降順にソートされません。
InnoDB
テーブルでブールクエリーを実行するには、MATCH()
式のすべてのカラム上にFULLTEXT
インデックスが必要です。MyISAM
検索インデックスに対するブールクエリーは、FULLTEXT
インデックスなしでも機能します。ただし、この方法で実行される検索の速度は、非常に遅くなります。-
単語の最小長および最大長の全文パラメータは、組込み
FULLTEXT
パーサーおよび MeCab パーサープラグインを使用して作成されたFULLTEXT
インデックスに適用されます。innodb_ft_min_token_size
およびinnodb_ft_max_token_size
はInnoDB
検索インデックスに使用されます。ft_min_word_len
およびft_max_word_len
はMyISAM
検索インデックスに使用されます。単語の最小長および最大長の全文パラメータは、ngram パーサーを使用して作成された
FULLTEXT
インデックスには適用されません。ngram トークンサイズは、ngram_token_size
オプションで定義されます。 ストップワードリストが適用されます。これらは、
InnoDB
検索インデックスの場合はinnodb_ft_enable_stopword
、innodb_ft_server_stopword_table
、およびinnodb_ft_user_stopword_table
、MyISAM
検索インデックスの場合はft_stopword_file
によって制御されます。InnoDB
の全文検索では、'++apple'
の例と同様に、単一の検索単語で複数の演算子を使用するようサポートされていません。 単一の検索語で複数の演算子を使用すると、標準出力に構文エラーが返されます。 MyISAM 全文検索では、同じ検索が正常に処理され、検索語に隣接する演算子を除くすべての演算子が無視されます。InnoDB
の全文検索では、先頭のプラス記号またはマイナス記号のみがサポートされています。 たとえば、InnoDB
では'+apple'
がサポートされますが、'apple+'
はサポートされていません。 末尾にプラス記号またはマイナス記号を指定すると、InnoDB
で構文エラーがレポートされます。InnoDB
の全文検索では、ワイルドカード ('+*'
) を使用した先頭のプラス記号、プラス記号とマイナス記号の組み合わせ ('+-'
)、または先頭のプラス記号とマイナス記号の組み合わせ ('+-apple'
) はサポートされていません。 このような無効なクエリーでは、構文エラーが返されます。InnoDB
全文検索では、ブール全文検索での@
記号の使用はサポートされていません。@
記号は、@distance
近接検索演算子で使用するために予約されています。MyISAM
検索インデックスに適用される 50% のしきい値は使用されません。
ブール全文検索機能では、次の演算子がサポートされています。
-
+
先頭または末尾のプラス記号は、この単語が返される各行に存在しなければならないことを示します。
InnoDB
では、先頭のプラス記号のみがサポートされています。 -
-
先頭または末尾のマイナス記号は、この単語が返される行のいずれにも存在してはならないことを示します。
InnoDB
では、先頭のマイナス記号のみがサポートされています。注:
-
演算子は、本来ならほかの検索語句で一致が行われる行を除外することのみに使用します。 したがって、-
の前にある検索語句のみを含むブールモードの検索では、空の結果が返されます。 「除外された検索語句のいずれかを含む行を除いたすべての行」が返されるわけではありません。 -
(演算子なし)
デフォルトでは (
+
と-
のどちらも指定されてない場合)、この単語はオプションですが、それを含む行の評価は高くなります。 これは、IN BOOLEAN MODE
修飾子なしのMATCH() ... AGAINST()
の動作と似ています。 -
@
distance
この演算子は、
InnoDB
テーブルでのみ機能します。 2 つ以上の単語がすべて、相互に指定された距離内で始まっているかどうかが単語単位でテストされます。@
演算子の直前に、二重引用符で囲まれた文字列内の検索単語を指定します (たとえば、distance
MATCH(col1) AGAINST('"word1 word2 word3" @8' IN BOOLEAN MODE)
)。 -
> <
これらの 2 つの演算子は、行に割り当てられた関連性の値への単語の貢献度を変更する際に使用されます。
>
演算子は貢献度を上げ、<
演算子は貢献度を下げます。 次のリストのあとに示す例を参照してください。 -
( )
丸括弧は、単語を部分式にグループ化します。 丸括弧で囲まれたグループはネストできます。
-
~
先頭のチルダは否定演算子として機能するため、行の関連性への単語の貢献度がマイナスになります。 これは、「ノイズ」単語にマークを付ける際に便利です。 このような単語を含む行は、その他よりも低く評価されますが、
-
演算子を使用した場合のように、完全に除外されることはありません。 -
*
アスタリスクは、切り捨て (またはワイルドカード) 演算子として機能します。 その他の演算子とは異なり、影響を受ける単語に追加されます。
*
演算子の前の単語で始まれば、単語が一致します。切り捨て演算子を付けて単語が指定されている場合は、その単語が短すぎたり、ストップワードであったりしても、ブールクエリーから削除されません。 単語が短すぎるかどうかは、
InnoDB
テーブルの場合はinnodb_ft_min_token_size
設定、MyISAM
テーブルの場合はft_min_word_len
によって判断されます。 これらのオプションは、ngram パーサーを使用するFULLTEXT
インデックスには適用されません。ワイルドカード単語は、1 つ以上の単語の先頭に存在しなければならないプリフィクスとみなされます。 単語の最小長が 4 である場合は、
'+
の検索では、2 番目のクエリーで短すぎる検索語句word
+the*'the
が無視されるため、'+
の検索よりも少ない行が返される可能性があります。word
+the' -
"
二重引用符 (
"
) 文字内で囲まれたフレーズは、入力されたそのままのフレーズを含む行にのみ一致します。 全文エンジンでは、フレーズが複数の単語に分割され、それらの単語のFULLTEXT
インデックス内で検索が実行されます。 単語以外の文字は、正確に一致する必要がありません。フレーズ検索では、そのフレーズとまったく同じ単語が同じ順序で一致に含まれることのみが必要です。 たとえば、"test phrase"
は"test, phrase"
と一致します。フレーズにインデックス内にある単語が含まれない場合は、結果が空になります。 単語がテキスト内に存在しない場合、ストップワードである場合、またはインデックス付きの単語の最小長よりも短い場合の組み合わせが原因で、単語がインデックス内に存在しない可能性があります。
次の例では、ブール全文演算子を使用する一部の検索文字列を実演します。
-
'apple banana'
2 つの単語の 1 つ以上を含む行を検索します。
-
'+apple +juice'
両方の単語を含む行を検索します。
-
'+apple macintosh'
単語 「apple」 を含む行を検索しますが、行に 「macintosh」 も含まれる場合は行を高く評価されます。
-
'+apple -macintosh'
単語 「apple」 を含むが、「macintosh」 は含まない行を検索します。
-
'+apple ~macintosh'
単語 「apple」 を含む行を検索しますが、行に単語 「macintosh」 も含まれる場合は、行に含まれない場合よりも低く評価されます。 これは、「macintosh」 が存在すると、行がまったく返されない
'+apple -macintosh'
の検索よりも「ソフト」です。 -
'+apple +(>turnover <strudel)'
単語 「apple」 と 「turnover」、または 「apple」 と 「strudel」 (順序は不問) を含む行を検索しますが、「apple turnover」 を 「apple strudel」 よりも高く評価します。
-
'apple*'
「apple」、「apples」、「applesauce」、「applet」 などの単語を含む行を検索します。
-
'"some words"'
「some words」 とまったく同じフレーズを含む行を検索します (たとえば、「some words of wisdom」 を含む行は検索しますが、「some noise words」 は検索しません)。 句を囲む
"
文字は、句を区切る演算子文字であることに注意してください。 検索文字列自体を囲む引用符ではありません。
InnoDB
の全文検索は、Sphinx の全文検索エンジンをモデルにし、使用されるアルゴリズムは、BM25 および TF-IDF のランキングアルゴリズムに基づいています。 このような理由のため、InnoDB
のブール全文検索の関連性ランキングは、MyISAM
の関連性ランキングと異なる場合があります。
InnoDB
では、「term frequency-inverse document frequency」 (TF-IDF
) 重み付けシステムの偏差を使用して、指定された全文検索クエリーのドキュメントの関連性にランクが付けられます。 TF-IDF
の重み付けは、ドキュメントで単語が出現する頻度に基づき、コレクション内のすべてのドキュメントで単語が出現する頻度によってオフセットされます。 言い換えると、ある単語がドキュメントで出現する頻度が高くなるほど、その単語がドキュメントコレクションで出現する頻度が低くなり、ドキュメントのランクが高くなります。
関連性ランキングの計算方法
単語の出現頻度 (TF
) 値は、単語がドキュメントで出現する回数です。 単語の逆文書頻度 (IDF
) 値は、次の公式を使用して計算されます。ここで、total_records
はコレクション内のレコード数、matching_records
は検索語句が表示されるレコード数です。
${IDF} = log10( ${total_records} / ${matching_records} )
ドキュメントに単語が複数回含まれる場合は、IDF 値が TF 値で乗算されます。
${TF} * ${IDF}
TF
および IDF
値を使用する場合は、ドキュメントの関連性ランキングが次の公式を使用して計算されます。
${rank} = ${TF} * ${IDF} * ${IDF}
公式については、次の例で実演されています。
単一単語検索の関連性ランキング
この例では、単一単語検索の関連性ランキングの計算を実演します。
mysql> CREATE TABLE articles (
id INT UNSIGNED AUTO_INCREMENT NOT NULL PRIMARY KEY,
title VARCHAR(200),
body TEXT,
FULLTEXT (title,body)
) ENGINE=InnoDB;
Query OK, 0 rows affected (1.04 sec)
mysql> INSERT INTO articles (title,body) VALUES
('MySQL Tutorial','This database tutorial ...'),
("How To Use MySQL",'After you went through a ...'),
('Optimizing Your Database','In this database tutorial ...'),
('MySQL vs. YourSQL','When comparing databases ...'),
('MySQL Security','When configured properly, MySQL ...'),
('Database, Database, Database','database database database'),
('1001 MySQL Tricks','1. Never run mysqld as root. 2. ...'),
('MySQL Full-Text Indexes', 'MySQL fulltext indexes use a ..');
Query OK, 8 rows affected (0.06 sec)
Records: 8 Duplicates: 0 Warnings: 0
mysql> SELECT id, title, body, MATCH (title,body) AGAINST ('database' IN BOOLEAN MODE)
AS score FROM articles ORDER BY score DESC;
+----+------------------------------+-------------------------------------+---------------------+
| id | title | body | score |
+----+------------------------------+-------------------------------------+---------------------+
| 6 | Database, Database, Database | database database database | 1.0886961221694946 |
| 3 | Optimizing Your Database | In this database tutorial ... | 0.36289870738983154 |
| 1 | MySQL Tutorial | This database tutorial ... | 0.18144935369491577 |
| 2 | How To Use MySQL | After you went through a ... | 0 |
| 4 | MySQL vs. YourSQL | When comparing databases ... | 0 |
| 5 | MySQL Security | When configured properly, MySQL ... | 0 |
| 7 | 1001 MySQL Tricks | 1. Never run mysqld as root. 2. ... | 0 |
| 8 | MySQL Full-Text Indexes | MySQL fulltext indexes use a .. | 0 |
+----+------------------------------+-------------------------------------+---------------------+
8 rows in set (0.00 sec)
合計で 8 つのレコードがあり、そのうち 3 つが 「database」 という検索語句に一致します。 1 つ目のレコード (id 6
) には、検索語句が 6 回含まれ、関連性ランキングは 1.0886961221694946
です。 このランキング値は、6 の TF
値 (レコード id 6
には 「database」 という検索語句が 6 回出現します)、および次のように計算される 0.42596873216370745 の IDF
値 (ここで、8 はレコードの合計数、3 は検索語句が出現するレコードの数) を使用して計算されます。
${IDF} = log10( 8 / 3 ) = 0.42596873216370745
その後、TF
および IDF
値はランキング公式に入力されます。
${rank} = ${TF} * ${IDF} * ${IDF}
MySQL コマンド行クライアントで計算を実行すると、1.088696164686938 のランキング値が返されます。
mysql> SELECT 6*log10(8/3)*log10(8/3);
+-------------------------+
| 6*log10(8/3)*log10(8/3) |
+-------------------------+
| 1.088696164686938 |
+-------------------------+
1 row in set (0.00 sec)
SELECT ... MATCH ... AGAINST
ステートメントと MySQL コマンド行クライアントで返されるランキング値 (1.0886961221694946
と 1.088696164686938
) に、わずかな相違がある場合があります。 この相違は、整数と浮動小数点/倍精度間のキャストが (関連する精度および丸めの決定とともに) InnoDB
によって内部で実行される方法、およびその他の場所 (MySQL コマンド行クライアントやその他のタイプの計算機など) で実行される方法が原因で発生します。
複数単語検索の関連性ランキング
この例では、以前の例で使用された articles
テーブルおよびデータに基づいて、複数単語の全文検索の関連性ランキングの計算を実演します。
複数の単語で検索する場合は、次の公式に示すように、関連性ランキングの値が各単語の関連性ランキングの合計になります。
${rank} = ${TF} * ${IDF} * ${IDF} + ${TF} * ${IDF} * ${IDF}
2 つの語句 ('mysql tutorial') で検索を実行すると、次の結果が返されます。
mysql> SELECT id, title, body, MATCH (title,body) AGAINST ('mysql tutorial' IN BOOLEAN MODE)
AS score FROM articles ORDER BY score DESC;
+----+------------------------------+-------------------------------------+----------------------+
| id | title | body | score |
+----+------------------------------+-------------------------------------+----------------------+
| 1 | MySQL Tutorial | This database tutorial ... | 0.7405621409416199 |
| 3 | Optimizing Your Database | In this database tutorial ... | 0.3624762296676636 |
| 5 | MySQL Security | When configured properly, MySQL ... | 0.031219376251101494 |
| 8 | MySQL Full-Text Indexes | MySQL fulltext indexes use a .. | 0.031219376251101494 |
| 2 | How To Use MySQL | After you went through a ... | 0.015609688125550747 |
| 4 | MySQL vs. YourSQL | When comparing databases ... | 0.015609688125550747 |
| 7 | 1001 MySQL Tricks | 1. Never run mysqld as root. 2. ... | 0.015609688125550747 |
| 6 | Database, Database, Database | database database database | 0 |
+----+------------------------------+-------------------------------------+----------------------+
8 rows in set (0.00 sec)
1 つ目のレコード (id 8
) では、'mysql' が 1 回出現し、'tutorial' が 2 回出現します。 'mysql' に一致するレコードは 6 つ、'tutorial' に一致するレコードは 2 つあります。 MySQL コマンド行クライアントでは、これらの値を複数単語検索のランキング公式に挿入するときに、予期されるランキング値が返されます。
mysql> SELECT (1*log10(8/6)*log10(8/6)) + (2*log10(8/2)*log10(8/2));
+-------------------------------------------------------+
| (1*log10(8/6)*log10(8/6)) + (2*log10(8/2)*log10(8/2)) |
+-------------------------------------------------------+
| 0.7405621541938003 |
+-------------------------------------------------------+
1 row in set (0.00 sec)
上記の例では、SELECT ... MATCH ... AGAINST
ステートメントと MySQL コマンド行クライアントで返されるランキング値に、わずかな相違があることについて説明しました。