Built-in tokenizers handle general cases, but fall short when your data has specific patterns — phone numbers that need prefix matching, log entries with mixed identifiers, or Chinese text that must support Pinyin search. Custom tokenization lets you combine character filters, tokenizers, and token filters to precisely control how SelectDB splits text into searchable tokens, directly shaping search relevance and analytics accuracy.

How it works
Every custom analyzer runs text through up to three stages in order:
Character filter (
char_filter) — transforms the raw input before tokenization (for example, replacing special characters or stripping HTML tags).Tokenizer — splits the transformed text into tokens.
Token filter — post-processes each token (for example, lowercasing or Pinyin conversion).
An analyzer bundles one tokenizer with zero or more token filters into a reusable unit. You then attach the analyzer to an inverted index on a table column.
Create components
Character filter
A character filter preprocesses input text before tokenization. Use it to normalize raw input — for example, replacing punctuation with spaces, or stripping HTML tags before indexing web content.
CREATE INVERTED INDEX CHAR_FILTER IF NOT EXISTS x_char_filter
PROPERTIES (
"type" = "char_replace"
-- For other parameters, see below
);Supported types:
`char_replace` — replaces specified characters with a target character before tokenization.
Parameter Description Default char_filter_patternList of characters to replace — char_filter_replacementReplacement character Space `html_strip` — strips HTML tags from input text before tokenization.
Tokenizer
CREATE INVERTED INDEX TOKENIZER IF NOT EXISTS x_tokenizer
PROPERTIES (
"type" = "standard"
);Choose a tokenizer based on your use case:
Word tokenizers — split full text into words
`standard` — follows Unicode text segmentation rules; suitable for most languages.
"quick brown"→["quick", "brown"]`basic` — simple tokenization for English, numbers, Chinese, and Unicode characters.
extra_chars: Additional ASCII delimiters, for example[]()..
Partial-word tokenizers — generate token fragments for prefix or substring matching
`ngram` — generates n-grams by sliding a window over each token.
"quick"→["qu", "ui", "ic", "ck"]min_ngram: Minimum n-gram length. Default:1.max_ngram: Maximum n-gram length. Default:2.token_chars: Character classes to keep. Valid values:letter,digit,whitespace,punctuation,symbol. Default: all.
`edge_ngram` — generates n-grams anchored to the start of each token; ideal for prefix search (for example, search-as-you-type).
"quick"→["q", "qu", "qui", "quic", "quick"]min_ngram: Minimum length. Default:1.max_ngram: Maximum length. Default:2.token_chars: Same asngram.
Structured-text tokenizers — preserve or split structured identifiers
`keyword` — outputs the entire input as a single token; typically combined with token filters.
"user_123@example.com"→["user_123@example.com"]`char_group` — splits text on specified characters or character classes.
tokenize_on_chars: Delimiters or character classes. Supported classes:whitespace,letter,digit,punctuation,symbol,cjk.
Multilingual tokenizers
`icu` — International Components for Unicode (ICU) tokenizer; handles complex scripts across multiple languages.
`pinyin` — converts Chinese characters to Pinyin for Chinese name and full-text search. Supported since version 4.0.2. Phrase queries are not supported.
Parameter Description Default keep_first_letterKeep only the first letter of each character's Pinyin (for example, Liu Dehua→ldh)truekeep_separate_first_letterEmit each first letter as a separate token (for example, LiuDeHua→l,d,h). May increase query ambiguity.falselimit_first_letter_lengthMaximum length of the first-letter output 16keep_full_pinyinKeep full Pinyin per character (for example, Liu Dehua→liu,de,hua)truekeep_joined_full_pinyinJoin all full Pinyin into one token (for example, Liu Dehua→liudehua)falsekeep_none_chineseKeep non-Chinese letters and digits truekeep_none_chinese_togetherKeep non-Chinese letters as a single token (for example, DJstaysDJ, notD,J). Requireskeep_none_chinese.truekeep_none_chinese_in_first_letterInclude non-Chinese letters in the first-letter result (for example, LiuDeHuaAT2016→ldhat2016)truekeep_none_chinese_in_joined_full_pinyinInclude non-Chinese characters in the joined Pinyin string (for example, LiuDehua2016→liudehua2016)falsenone_chinese_pinyin_tokenizeTokenize non-Chinese letters that form valid Pinyin syllables (for example, alibaba→a,li,ba,ba). Requireskeep_none_chineseandkeep_none_chinese_together.truekeep_originalKeep the original input as a token falselowercaseConvert non-Chinese letters to lowercase truetrim_whitespaceTrim leading and trailing whitespace trueremove_duplicated_termRemove duplicate tokens to save index space (for example, de de→de). May affect positional queries.falseignore_pinyin_offsetNot in use true
Token filter
CREATE INVERTED INDEX TOKEN_FILTER IF NOT EXISTS x_token_filter
PROPERTIES (
"type" = "word_delimiter"
);`word_delimiter` — splits tokens at word boundaries and normalizes them.
Default behavior:
Splits at non-alphanumeric characters:
Super-Duper→Super,DuperStrips leading and trailing delimiters:
XL---42+'Autocoder'→XL,42,AutocoderSplits at case transitions:
PowerShot→Power,ShotSplits at letter-to-digit transitions:
XL500→XL,500Removes English possessive
's:Neil's→Neil
Optional parameters:
Parameter Default Description generate_number_partstrueEmit numeric subwords generate_word_partstrueEmit alpha subwords protected_words— Words to exclude from splitting split_on_case_changetrueSplit at case transitions split_on_numericstrueSplit at digit-letter transitions stem_english_possessivetrueRemove trailing 'stype_table— Custom character-type mappings. Maps non-alphanumeric characters to a type to prevent delimiter treatment. Example: ["+ => ALPHA", "- => ALPHA"]. Supported types:ALPHA,ALPHANUM,DIGIT,LOWER,SUBWORD_DELIM,UPPER`asciifolding` — converts non-ASCII characters to ASCII equivalents (for example,
é→e).`lowercase` — converts all tokens to lowercase.
`stop` — removes stop words from the token stream.
`pinyin` (Pinyin filter) — converts Chinese characters to Pinyin after tokenization. Accepts the same parameters as the Pinyin tokenizer above.
Analyzer
An analyzer combines one tokenizer with one or more token filters, executed in order.
CREATE INVERTED INDEX ANALYZER IF NOT EXISTS x_analyzer
PROPERTIES (
"tokenizer" = "x_tokenizer", -- exactly one tokenizer
"token_filter" = "x_filter1, x_filter2" -- one or more token filters, run in order
);View and delete components
-- View
SHOW INVERTED INDEX TOKENIZER;
SHOW INVERTED INDEX TOKEN_FILTER;
SHOW INVERTED INDEX ANALYZER;
-- Delete
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;Delete an analyzer before deleting the tokenizer or token filters it references. An analyzer in use by a table cannot be deleted.
Apply a custom analyzer to a table
Set the analyzer parameter in the index properties when creating a table. The analyzer parameter can only be paired with support_phrase — no other index properties are allowed alongside it.
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;Verify tokenization output
Before indexing data, use the tokenize() function to confirm that your analyzer produces the expected tokens. This lets you catch configuration issues early without rebuilding an index.
select tokenize('<input_text>', '"analyzer"="<analyzer_name>"');Replace <input_text> with the text to test and <analyzer_name> with the name of your custom analyzer.
Usage notes
Use either a built-in analyzer (
built_in_analyzer) or a custom analyzer (analyzer) on an index — not both.Custom tokenization definitions are synchronized to the backend within 10 seconds. Data imports started before synchronization completes may fail.
Nesting multiple custom analyzers may reduce tokenization performance.
Limitations
The
typevalue and its parameters for each tokenizer and token filter must match a supported type. Unsupported combinations cause table creation to fail.An analyzer cannot be deleted while it is in use by a table.
A tokenizer or token filter cannot be deleted while it is referenced by an analyzer.
Complete examples
Example 1: Phone number prefix search with edge n-gram
Use the edge n-gram tokenizer to index phone numbers so users can search by any prefix.
Components used:
Tokenizer:
edge_ngramwithtoken_chars = digit,min_gram = 3,max_gram = 10Analyzer:
edge_ngram_phone_number
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"');Result:
[
{"token":"138"},
{"token":"1389"},
{"token":"13891"},
{"token":"138919"},
{"token":"1389197"},
{"token":"13891972"},
{"token":"138919726"},
{"token":"1389197263"}
]Example 2: Technical log search with standard tokenizer and word_delimiter filter
Use the standard tokenizer with asciifolding, word_delimiter, and lowercase filters for fine-grained tokenization of log messages containing IP addresses, email addresses, and mixed-case identifiers.
Components used:
Token filter:
word_delimiterwithsplit_on_numerics = false,split_on_case_change = falseAnalyzer:
lowercase_delimited(standard tokenizer + asciifolding + word_splitter + lowercase)
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"');Result:
[
{"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"}
]Example 3: Case-insensitive exact matching with keyword tokenizer
Use the keyword tokenizer to keep the entire input as one token, then apply asciifolding and lowercase filters for case-insensitive, accent-insensitive exact matching.
Components used:
Tokenizer:
keywordToken filters:
asciifolding,lowercase
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"');Result:
[
{"token":"hello world"}
]Example 4: Chinese Pinyin search
Use the Pinyin tokenizer to search Chinese names by full Pinyin, first-letter acronyms, and original Chinese characters.
Use the Pinyin tokenizer
-- Create a Pinyin tokenizer that supports multiple output formats
CREATE INVERTED INDEX TOKENIZER IF NOT EXISTS pinyin_tokenizer
PROPERTIES (
"type" = "pinyin",
"keep_first_letter" = "true",
"keep_full_pinyin" = "true",
"keep_joined_full_pinyin" = "true",
"keep_original" = "true",
"keep_none_chinese" = "true",
"lowercase" = "true",
"remove_duplicated_term" = "true"
);
CREATE INVERTED INDEX ANALYZER IF NOT EXISTS pinyin_analyzer
PROPERTIES (
"tokenizer" = "pinyin_tokenizer"
);
CREATE TABLE contacts (
id BIGINT NOT NULL AUTO_INCREMENT(1),
name TEXT NULL,
INDEX idx_name (name) USING INVERTED PROPERTIES("analyzer" = "pinyin_analyzer", "support_phrase" = "true")
) ENGINE=OLAP
DUPLICATE KEY(id)
DISTRIBUTED BY RANDOM BUCKETS 1
PROPERTIES ("replication_allocation" = "tag.location.default: 1");
INSERT INTO contacts VALUES (1, "Liu Dehua"), (2, "Zhang Xueyou"), (3, "Guo Fucheng");
-- Search by original name, full Pinyin, partial Pinyin, or first-letter acronym
SELECT * FROM contacts WHERE name MATCH 'Liu Dehua';
SELECT * FROM contacts WHERE name MATCH 'liudehua';
SELECT * FROM contacts WHERE name MATCH 'liu';
SELECT * FROM contacts WHERE name MATCH 'ldh';Use the Pinyin filter
Apply the Pinyin filter after the keyword tokenizer to keep the full input as a single token before Pinyin conversion.
-- Create a Pinyin filter to apply after the keyword tokenizer
CREATE INVERTED INDEX TOKEN_FILTER IF NOT EXISTS pinyin_filter
PROPERTIES (
"type" = "pinyin",
"keep_first_letter" = "true",
"keep_full_pinyin" = "true",
"keep_original" = "true",
"lowercase" = "true"
);
CREATE INVERTED INDEX ANALYZER IF NOT EXISTS keyword_pinyin
PROPERTIES (
"tokenizer" = "keyword",
"token_filter" = "pinyin_filter"
);
CREATE TABLE stars (
id BIGINT NOT NULL AUTO_INCREMENT(1),
name TEXT NULL,
INDEX idx_name (name) USING INVERTED PROPERTIES("analyzer" = "keyword_pinyin")
) ENGINE=OLAP
DUPLICATE KEY(id)
DISTRIBUTED BY RANDOM BUCKETS 1
PROPERTIES ("replication_allocation" = "tag.location.default: 1");
INSERT INTO stars VALUES (1, "Liu Dehua"), (2, "Zhang Xueyou"), (3, "LiuDehuaABC");
-- Supports multiple search modes
SELECT * FROM stars WHERE name MATCH 'Liu Dehua';
SELECT * FROM stars WHERE name MATCH 'liu';
SELECT * FROM stars WHERE name MATCH 'ldh';
SELECT * FROM stars WHERE name MATCH 'zxy';