メインコンテンツまでスキップ
に最終更新

カスタムアナライザー

カスタムアナライザーは、文字フィルター(char_filter)、トークナイザー、トークンフィルター(token_filter)を組み合わせ、業務上の要件に応じてテキストを検索可能な語に分割する方法を細かく定義できます。組み込みアナライザーの制約を補い、検索の関連性とデータ分析の精度に直接影響します。

カスタムアナライザーの図

適用シナリオ​

シナリオ推奨する組み合わせ
多言語が混在するテキストの検索icuトークナイザー + icu_normalizerフィルター
電話番号やコードの前方一致edge_ngramトークナイザー
複雑な英語テキストの分割(キャメルケース、ハイフンなど)standardトークナイザー + word_delimiterフィルター
オートコンプリートや入力候補edge_ngramトークナイザー + lowercaseフィルター
完全一致(元の語を保持)keywordトークナイザー + lowercase / asciifoldingフィルター

基本コンポーネント​

カスタムアナライザーは、元のテキストに順番に適用される以下の4種類のオブジェクトで構成されます。

Original text ──► [char_filter] ──► [tokenizer] ──► [token_filter] ──► Terms
コンポーネント役割個数
char_filterトークン化前に文字を前処理する(置換や正規化など)0 ~ N
tokenizerテキストを語に分割する1
token_filter分割後の語を処理する(小文字化やASCIIへの変換など)0 ~ N
analyzer上記のコンポーネントを組み合わせ、完全なトークン化パイプラインを構成する1

カスタムアナライザーの作成​

1. 文字フィルター(char_filter)​

CREATE INVERTED INDEX CHAR_FILTER IF NOT EXISTS x_char_filter
PROPERTIES (
"type" = "char_replace"
-- See below for other parameters
);

サポートされる文字フィルターの種類は次のとおりです。

  • char_replace: トークン化前に、指定した文字を別の文字に置換します。

    • char_filter_pattern: 置換対象の文字のリスト
    • char_filter_replacement: 置換後の文字(デフォルト: スペース)
  • icu_normalizer: ICUによる正規化を使ってテキストを前処理します。

    • name: 正規化形式(デフォルトはnfkc_cf)。選択肢: nfc、nfkc、nfkc_cf、nfd、nfkd
    • mode: 正規化モード(デフォルトはcompose)。選択肢: compose、decompose
    • unicode_set_filter: 正規化する文字集合を指定します([a-z]など)。

2. トークナイザー​

CREATE INVERTED INDEX TOKENIZER IF NOT EXISTS x_tokenizer
PROPERTIES (
"type" = "standard"
);

サポートされるトークナイザーの種類は次のとおりです。

種類説明主なパラメーター
standardUnicodeのテキスト分割規則に従う標準的なトークン化。多くの言語に適していますなし
ngramN-gramに分割しますmin_gram、max_gram、max_ngram_diff、token_chars
edge_ngram単語の先頭から始まるN-gramを生成しますmin_gram、max_gram、token_chars
keywordテキスト全体を単一の語として出力します。多くの場合、token_filterと組み合わせますなし
char_group指定した文字で分割しますtokenize_on_chars
basic英語、数字、中国語、Unicodeの簡易的なトークン化extra_chars
icuICUによる国際化対応のトークン化。複数言語の複雑な文字体系をサポートしますなし
ik_smart完全な単語を優先するIKの中国語トークン化。4.1.5以降、カスタムアナライザーで使用できますなし
ik_max_word重複する可能性のある語をより多く生成する、IKの細粒度な中国語トークン化。4.1.5以降、カスタムアナライザーで使用できますなし

パラメーターの説明:

  • min_gram: 最小長(デフォルトは1)
  • max_gram: 最大長(デフォルトは2)
  • token_chars: 保持する文字カテゴリ(デフォルトはすべて保持)。選択肢: letter、digit、whitespace、punctuation、symbol
  • tokenize_on_chars: 文字のリストまたはカテゴリ。カテゴリにはwhitespace、letter、digit、punctuation、symbol、cjkを指定できます
  • extra_chars: 分割に使用する追加のASCII文字([]().など)

Ngramの長さの範囲に関する制限(4.1.5以降)​

4.1.5以降、ngramトークナイザーではmax_ngram_diffを指定することで、gramの最大長と最小長の差に関する従来の上限1を緩和できます。modeプロパティを指定しない通常のngramトークナイザーを作成する際には、次の条件が検証されます。

パラメーターデフォルト制約
min_gram1max_gram以下の正の整数。新しいngramトークナイザーでは最大1024。
max_gram2min_gram以上の正の整数。新しいngramトークナイザーでは最大1024。
max_ngram_diff10から255までの整数で、max_gram - min_gram <= max_ngram_diffを満たす必要があります。0を指定すると、固定長のN-gramだけが許可されます。

たとえば、min_gram=1とmax_gram=8を指定する場合、max_ngram_diffは少なくとも7にする必要があります。省略すると、作成時にThe difference between max_gram and min_gram in NGram Tokenizer must be less than or equal to: [ 1 ] but was [7]というエラーが発生します。使用方法全体については例5を参照してください。

これらの制限とmax_ngram_diffは、modeプロパティを指定しない通常のngramトークナイザーに適用されます。modeを指定したトークナイザーには別の検証が適用され、これらの制限は無視されます。edge_ngramはこのパラメーターを受け付けません。電話番号とオートコンプリートの例では、それぞれのmin_gramとmax_gramの設定を使用します。範囲を広げると、入力位置ごとに生成される語が増えます。不要なインデックスの肥大化を避けるため、検索要件に合わせて範囲を設定してください。

3. トークンフィルター​

CREATE INVERTED INDEX TOKEN_FILTER IF NOT EXISTS x_token_filter
PROPERTIES (
"type" = "word_delimiter"
);

サポートされるトークンフィルターの種類は次のとおりです。

種類役割
word_delimiter英数字以外の文字で分割し、正規化も行えます
ascii_folding非ASCII文字を対応するASCII文字に変換します
lowercaseトークンのテキストを小文字に変換します
icu_normalizerICUによる正規化を使ってトークンを処理します

word_delimiterの詳細​

デフォルトの動作:

  1. 英数字以外の文字を区切り文字として使用します(例: Super-DuperをSuper、Duperに分割)。
  2. トークンの先頭と末尾の区切り文字を削除します(例: XL---42+'Autocoder'をXL、42、Autocoderに分割)。
  3. 大文字と小文字が切り替わる位置で分割します(例: PowerShotをPower、Shotに分割)。
  4. 文字と数字の境界で分割します(例: XL500をXL、500に分割)。
  5. 英語の所有格を表す'sを削除します(例: Neil'sをNeilに変換)。

オプションのパラメーター:

パラメーターデフォルト説明
generate_number_partstrue数字部分を出力するかどうか
generate_word_partstrue単語部分を出力するかどうか
protected_wordsなし分割しない保護対象の単語
split_on_case_changetrue大文字と小文字が切り替わる位置で分割するかどうか
split_on_numericstrue文字と数字の境界で分割するかどうか
stem_english_possessivetrue英語の所有格を表す'sを削除するかどうか
type_tableなしカスタムの文字種マッピングテーブル

type_tableは次のマッピング種別をサポートします。

  • ALPHA(文字)
  • ALPHANUM(英数字)
  • DIGIT(数字)
  • LOWER(小文字)
  • SUBWORD_DELIM(英数字以外の区切り文字)
  • UPPER(大文字)

例: ["+ => ALPHA", "- => ALPHA"]を指定すると、+と-は文字として扱われ、分割位置には使用されません。

icu_normalizerのパラメーター​

  • name: 正規化形式(デフォルトはnfkc_cf)。選択肢: nfc、nfkc、nfkc_cf、nfd、nfkd
  • unicode_set_filter: 正規化する文字集合を指定します

4. アナライザー​

上記のコンポーネントを組み合わせて、完全なトークン化パイプラインを構成します。

CREATE INVERTED INDEX ANALYZER IF NOT EXISTS x_analyzer
PROPERTIES (
"tokenizer" = "x_tokenizer", -- A single tokenizer
"token_filter" = "x_filter1, x_filter2" -- One or more token_filters, executed in order
);

テーブルでのカスタムアナライザーの使用​

作成したカスタムアナライザーは、インデックスのPROPERTIESにあるanalyzerフィールドで参照します。

CREATE TABLE tbl (
`a` bigint NOT NULL AUTO_INCREMENT(1),
`ch` text NULL,
INDEX idx_ch (`ch`) USING INVERTED PROPERTIES("analyzer" = "x_custom_analyzer", "support_phrase" = "true")
)
table_properties;

注意事項:

  1. カスタムアナライザーは、インデックスのPROPERTIESにanalyzerで設定します。
  2. analyzerと併用できるプロパティはsupport_phraseのみです。

単一カラムへの複数のアナライザーインデックス​

Dorisでは、同じカラムに異なるトークナイザーを使用する複数の転置インデックスを作成できます。これにより、同じデータを異なるトークン化方法で検索できます。

ユースケース​

  • 多言語対応: 同じテキストカラムに、言語ごとのトークナイザーを使用します。
  • 検索精度と再現率の両立: 完全一致にはkeywordトークナイザー、あいまい検索にはstandardトークナイザーを使用します。
  • オートコンプリート: 前方一致にはedge_ngramトークナイザー、通常の検索にはstandardトークナイザーを使用します。

複数のインデックスの作成​

-- 1. Create tokenizers with different tokenization strategies
CREATE INVERTED INDEX ANALYZER IF NOT EXISTS std_analyzer
PROPERTIES ("tokenizer" = "standard", "token_filter" = "lowercase");

CREATE INVERTED INDEX ANALYZER IF NOT EXISTS kw_analyzer
PROPERTIES ("tokenizer" = "keyword", "token_filter" = "lowercase");

CREATE INVERTED INDEX TOKENIZER IF NOT EXISTS edge_ngram_tokenizer
PROPERTIES (
"type" = "edge_ngram",
"min_gram" = "1",
"max_gram" = "20",
"token_chars" = "letter"
);

CREATE INVERTED INDEX ANALYZER IF NOT EXISTS ngram_analyzer
PROPERTIES ("tokenizer" = "edge_ngram_tokenizer", "token_filter" = "lowercase");

-- 2. Create multiple indexes on the same column
CREATE TABLE articles (
id INT,
content TEXT,
-- Standard tokenizer for tokenized search
INDEX idx_content_std (content) USING INVERTED
PROPERTIES("analyzer" = "std_analyzer", "support_phrase" = "true"),
-- Keyword tokenizer for exact matching
INDEX idx_content_kw (content) USING INVERTED
PROPERTIES("analyzer" = "kw_analyzer"),
-- edge n-gram tokenizer for auto-completion
INDEX idx_content_ngram (content) USING INVERTED
PROPERTIES("analyzer" = "ngram_analyzer")
) ENGINE=OLAP
DUPLICATE KEY(id)
DISTRIBUTED BY HASH(id) BUCKETS 1
PROPERTIES ("replication_allocation" = "tag.location.default: 1");

クエリ時のアナライザーの指定​

USING ANALYZER句を使用して、使用するインデックスを指定します。

-- Insert test data
INSERT INTO articles VALUES
(1, 'hello world'),
(2, 'hello'),
(3, 'world'),
(4, 'hello world test');

-- Tokenized search: matches rows containing the term 'hello'
-- Returns: 1, 2, 4
SELECT id FROM articles WHERE content MATCH 'hello' USING ANALYZER std_analyzer ORDER BY id;

-- Exact match: matches only the exact 'hello' string
-- Returns: 2
SELECT id FROM articles WHERE content MATCH 'hello' USING ANALYZER kw_analyzer ORDER BY id;

-- Use edge n-gram for prefix matching
-- Returns: 1, 2, 4 (all rows starting with 'hel')
SELECT id FROM articles WHERE content MATCH 'hel' USING ANALYZER ngram_analyzer ORDER BY id;

組み込みトークナイザーを直接使用することもできます。

SELECT * FROM articles WHERE content MATCH 'hello' USING ANALYZER standard;
SELECT * FROM articles WHERE content MATCH 'hello' USING ANALYZER none;
SELECT * FROM articles WHERE content MATCH 'Hello' USING ANALYZER chinese;

既存のテーブルへのインデックスの追加​

-- Add a new index that uses a different tokenizer
ALTER TABLE articles ADD INDEX idx_content_chinese (content)
USING INVERTED PROPERTIES("parser" = "chinese");

-- Wait for the schema change to complete
SHOW ALTER TABLE COLUMN WHERE TableName='articles';

インデックスの構築​

インデックスを追加した後は、既存データに対してインデックスを構築する必要があります。

-- Build a specific index (non-cloud mode)
BUILD INDEX idx_content_chinese ON articles;

-- Build all indexes (cloud mode)
BUILD INDEX ON articles;

-- Check the build progress
SHOW BUILD INDEX WHERE TableName='articles';

主な注意事項​

  1. トークナイザーの同一性: tokenizerとtoken_filterの設定が同じトークナイザーは、同一と見なされます。同じカラムに、トークナイザーの構成が同一のインデックスを複数作成することはできません。
  2. インデックスの選択動作:
    • USING ANALYZERを指定した場合、指定したトークナイザーに対応するインデックスが存在し、構築済みであれば、そのインデックスが使用されます。
    • インデックスが構築されていない場合、クエリはインデックスを使わない実行経路に切り替わります(結果は正しいものの、性能は低下します)。
    • USING ANALYZERを指定しない場合は、利用可能ないずれかのインデックスが使用されることがあります。
  3. 性能上の考慮事項:
    • インデックスを追加するたびに、ストレージ使用量と書き込み時の負荷が増えます。
    • 実際のクエリパターンに基づいてトークナイザーを選択してください。
    • クエリパターンが予測できる場合は、インデックスの数を抑えることを検討してください。

管理とメンテナンス​

表示​

SHOW INVERTED INDEX TOKENIZER;
SHOW INVERTED INDEX TOKEN_FILTER;
SHOW INVERTED INDEX ANALYZER;

削除​

DROP INVERTED INDEX TOKENIZER IF EXISTS x_tokenizer;
DROP INVERTED INDEX TOKEN_FILTER IF EXISTS x_token_filter;
DROP INVERTED INDEX ANALYZER IF EXISTS x_analyzer;

完全な使用例​

例1: 電話番号の前方一致​

edge_ngramを使用して電話番号のすべての先頭部分を生成し、入力途中でも検索できるようにします。

CREATE INVERTED INDEX TOKENIZER IF NOT EXISTS edge_ngram_phone_number_tokenizer
PROPERTIES
(
"type" = "edge_ngram",
"min_gram" = "3",
"max_gram" = "10",
"token_chars" = "digit"
);

CREATE INVERTED INDEX ANALYZER IF NOT EXISTS edge_ngram_phone_number
PROPERTIES
(
"tokenizer" = "edge_ngram_phone_number_tokenizer"
);

CREATE TABLE tbl (
`a` bigint NOT NULL AUTO_INCREMENT(1),
`ch` text NULL,
INDEX idx_ch (`ch`) USING INVERTED PROPERTIES("support_phrase" = "true", "analyzer" = "edge_ngram_phone_number")
) ENGINE=OLAP
DUPLICATE KEY(`a`)
DISTRIBUTED BY RANDOM BUCKETS 1
PROPERTIES (
"replication_allocation" = "tag.location.default: 1"
);

SELECT tokenize('13891972631', '"analyzer"="edge_ngram_phone_number"');

結果:

[
{"token":"138"},
{"token":"1389"},
{"token":"13891"},
{"token":"138919"},
{"token":"1389197"},
{"token":"13891972"},
{"token":"138919726"},
{"token":"1389197263"}
]

例2: 複雑な英語テキストの細粒度なトークン化​

standardトークナイザーとword_delimiterを組み合わせてテキストをより細かく分割し、さらに大文字と小文字の正規化とASCIIへの変換を行います。

CREATE INVERTED INDEX TOKEN_FILTER IF NOT EXISTS word_splitter
PROPERTIES
(
"type" = "word_delimiter",
"split_on_numerics" = "false",
"split_on_case_change" = "false"
);

CREATE INVERTED INDEX ANALYZER IF NOT EXISTS lowercase_delimited
PROPERTIES
(
"tokenizer" = "standard",
"token_filter" = "asciifolding, word_splitter, lowercase"
);

CREATE TABLE tbl (
`a` bigint NOT NULL AUTO_INCREMENT(1),
`ch` text NULL,
INDEX idx_ch (`ch`) USING INVERTED PROPERTIES("support_phrase" = "true", "analyzer" = "lowercase_delimited")
) ENGINE=OLAP
DUPLICATE KEY(`a`)
DISTRIBUTED BY RANDOM BUCKETS 1
PROPERTIES (
"replication_allocation" = "tag.location.default: 1"
);

SELECT tokenize('The server at IP 192.168.1.15 sent a confirmation to user_123@example.com, requiring a quickResponse before the deadline.', '"analyzer"="lowercase_delimited"');

結果:

[
{"token":"the"},
{"token":"server"},
{"token":"at"},
{"token":"ip"},
{"token":"192"},
{"token":"168"},
{"token":"1"},
{"token":"15"},
{"token":"sent"},
{"token":"a"},
{"token":"confirmation"},
{"token":"to"},
{"token":"user"},
{"token":"123"},
{"token":"example"},
{"token":"com"},
{"token":"requiring"},
{"token":"a"},
{"token":"quickresponse"},
{"token":"before"},
{"token":"the"},
{"token":"deadline"}
]

例3: 元の語を保持した完全一致​

keywordトークナイザーでテキスト全体を保持し、その後にlowercaseとasciifoldingで正規化します。これは、文字列の大文字と小文字を区別しない完全一致によく使用されます。

CREATE INVERTED INDEX ANALYZER IF NOT EXISTS keyword_lowercase
PROPERTIES
(
"tokenizer" = "keyword",
"token_filter" = "asciifolding, lowercase"
);

CREATE TABLE tbl (
`a` bigint NOT NULL AUTO_INCREMENT(1),
`ch` text NULL,
INDEX idx_ch (`ch`) USING INVERTED PROPERTIES("support_phrase" = "true", "analyzer" = "keyword_lowercase")
) ENGINE=OLAP
DUPLICATE KEY(`a`)
DISTRIBUTED BY RANDOM BUCKETS 1
PROPERTIES (
"replication_allocation" = "tag.location.default: 1"
);

SELECT tokenize('hÉllo World', '"analyzer"="keyword_lowercase"');

結果:

[
{"token":"hello world"}
]

例4: IK Smartと細粒度な中国語トークン化(4.1.5以降)​

4.1.5以降、カスタムアナライザーでは、組み込みのik_smartトークナイザーとik_max_wordトークナイザーを直接参照できます。また、いずれかをtypeに指定した名前付きトークナイザーを作成し、文字フィルターやトークンフィルターと組み合わせることもできます。どちらのIKトークナイザーにも追加のトークナイザーパラメーターはありません。両方とも、BEの設定項目inverted_index_dict_pathが指すディレクトリ内のikディレクトリにある辞書を使用します。トークン化とインデックス構築に関わるすべてのBEが、これらのコンポーネントをサポートし、辞書を読み取れる必要があります。

以下の例では、直接参照する方法と名前付きトークナイザーを使用する方法を示します。これらのコンポーネントの作成にはグローバルなADMIN権限が必要です。テーブルの作成、データロード、クエリの実行には、対象のデータベースとテーブルに対する対応する権限も必要です。

CREATE INVERTED INDEX ANALYZER IF NOT EXISTS cn_ik_smart
PROPERTIES ("tokenizer" = "ik_smart");

CREATE INVERTED INDEX TOKENIZER IF NOT EXISTS cn_ik_max_tokenizer
PROPERTIES ("type" = "ik_max_word");

CREATE INVERTED INDEX ANALYZER IF NOT EXISTS cn_ik_max
PROPERTIES ("tokenizer" = "cn_ik_max_tokenizer");

作成に成功するとQuery OKが返されます。コンポーネントがBEに同期されたら、TOKENIZEで出力を比較します。以下の結果は組み込み辞書を使用したものです。辞書を変更すると、生成されるトークンも変わる場合があります。

SELECT TOKENIZE('中华人民共和国', '"analyzer"="cn_ik_smart"');
[{"token":"中华人民共和国"}]
SELECT TOKENIZE('中华人民共和国', '"analyzer"="cn_ik_max"');
[
{"token":"中华人民共和国"},
{"token":"中华人民"},
{"token":"中华"},
{"token":"华人"},
{"token":"人民共和国"},
{"token":"人民"},
{"token":"共和国"},
{"token":"共和"},
{"token":"国"}
]

テーブルの転置インデックスでアナライザーを使用します。

CREATE TABLE ik_articles (
id INT,
content STRING,
INDEX idx_content(content) USING INVERTED
PROPERTIES("analyzer" = "cn_ik_smart")
)
DUPLICATE KEY(id)
DISTRIBUTED BY HASH(id) BUCKETS 1
PROPERTIES ("replication_allocation" = "tag.location.default: 1");

INSERT INTO ik_articles VALUES (1, '中华人民共和国');

SELECT id FROM ik_articles
WHERE content MATCH '中华人民共和国' USING ANALYZER cn_ik_smart
ORDER BY id;
+----+
| id |
+----+
| 1 |
+----+

中国語の完全な単語を維持して検索したい場合はik_smartを使用し、単語の一部との一致を増やしたい場合はik_max_wordを使用します。さらにトークンを処理する必要がある場合は、アナライザーのtoken_filterプロパティでトークンフィルターを追加してください。

例5: Ngramの長さの範囲を拡張する(4.1.5以降)​

このトークナイザーは、max_ngram_diff=7を明示的に設定することで、長さが1から8までのN-gramを生成します。

CREATE INVERTED INDEX TOKENIZER IF NOT EXISTS ngram_1_8_tokenizer
PROPERTIES (
"type" = "ngram",
"min_gram" = "1",
"max_gram" = "8",
"max_ngram_diff" = "7"
);

CREATE INVERTED INDEX ANALYZER IF NOT EXISTS ngram_1_8_analyzer
PROPERTIES ("tokenizer" = "ngram_1_8_tokenizer");

作成とBEへの同期が成功したら、次を実行します。

SELECT TOKENIZE('abcdefgh', '"analyzer"="ngram_1_8_analyzer"');
[
{"token":"a"}, {"token":"ab"}, {"token":"abc"}, {"token":"abcd"},
{"token":"abcde"}, {"token":"abcdef"}, {"token":"abcdefg"}, {"token":"abcdefgh"},
{"token":"b"}, {"token":"bc"}, {"token":"bcd"}, {"token":"bcde"},
{"token":"bcdef"}, {"token":"bcdefg"}, {"token":"bcdefgh"},
{"token":"c"}, {"token":"cd"}, {"token":"cde"}, {"token":"cdef"},
{"token":"cdefg"}, {"token":"cdefgh"},
{"token":"d"}, {"token":"de"}, {"token":"def"}, {"token":"defg"}, {"token":"defgh"},
{"token":"e"}, {"token":"ef"}, {"token":"efg"}, {"token":"efgh"},
{"token":"f"}, {"token":"fg"}, {"token":"fgh"},
{"token":"g"}, {"token":"gh"}, {"token":"h"}
]

制限事項​

  1. tokenizerとtoken_filterのtypeおよびパラメーターには、現在サポートされているトークナイザーとトークンフィルターのみを使用できます。それ以外を指定すると、テーブルの作成に失敗します。
  2. analyzerを削除できるのは、どのテーブルでも使用されていない場合に限ります。
  3. tokenizerまたはtoken_filterを削除できるのは、どのanalyzerでも使用されていない場合に限ります。
  4. カスタムアナライザーのDDLは、実行から10秒後にBEへ同期されます。その後のデータインポートではエラーは発生しません。

注意事項​

  1. カスタムanalyzerに複数のコンポーネントを組み込むと、トークン化の性能が低下する場合があります。
  2. select tokenizeトークン化関数はカスタムアナライザーに対応しており、トークン化結果のデバッグに使用できます。
  3. 同じインデックスには、事前定義済みのbuilt_in_analyzerとカスタムanalyzerのどちらか一方のみを設定できます。