All Products
Search
Document Center

ApsaraDB for SelectDB:Custom tokenization

Last Updated:Mar 28, 2026

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.

A diagram of the custom tokenization process

How it works

Every custom analyzer runs text through up to three stages in order:

  1. Character filter (char_filter) — transforms the raw input before tokenization (for example, replacing special characters or stripping HTML tags).

  2. Tokenizer — splits the transformed text into tokens.

  3. 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.

    ParameterDescriptionDefault
    char_filter_patternList of characters to replace
    char_filter_replacementReplacement characterSpace
  • `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 as ngram.

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.

    ParameterDescriptionDefault
    keep_first_letterKeep only the first letter of each character's Pinyin (for example, Liu Dehualdh)true
    keep_separate_first_letterEmit each first letter as a separate token (for example, LiuDeHual, d, h). May increase query ambiguity.false
    limit_first_letter_lengthMaximum length of the first-letter output16
    keep_full_pinyinKeep full Pinyin per character (for example, Liu Dehualiu, de, hua)true
    keep_joined_full_pinyinJoin all full Pinyin into one token (for example, Liu Dehualiudehua)false
    keep_none_chineseKeep non-Chinese letters and digitstrue
    keep_none_chinese_togetherKeep non-Chinese letters as a single token (for example, DJ stays DJ, not D, J). Requires keep_none_chinese.true
    keep_none_chinese_in_first_letterInclude non-Chinese letters in the first-letter result (for example, LiuDeHuaAT2016ldhat2016)true
    keep_none_chinese_in_joined_full_pinyinInclude non-Chinese characters in the joined Pinyin string (for example, LiuDehua2016liudehua2016)false
    none_chinese_pinyin_tokenizeTokenize non-Chinese letters that form valid Pinyin syllables (for example, alibabaa, li, ba, ba). Requires keep_none_chinese and keep_none_chinese_together.true
    keep_originalKeep the original input as a tokenfalse
    lowercaseConvert non-Chinese letters to lowercasetrue
    trim_whitespaceTrim leading and trailing whitespacetrue
    remove_duplicated_termRemove duplicate tokens to save index space (for example, de dede). May affect positional queries.false
    ignore_pinyin_offsetNot in usetrue

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-DuperSuper, Duper

    • Strips leading and trailing delimiters: XL---42+'Autocoder'XL, 42, Autocoder

    • Splits at case transitions: PowerShotPower, Shot

    • Splits at letter-to-digit transitions: XL500XL, 500

    • Removes English possessive 's: Neil'sNeil

    Optional parameters:

    ParameterDefaultDescription
    generate_number_partstrueEmit numeric subwords
    generate_word_partstrueEmit alpha subwords
    protected_wordsWords to exclude from splitting
    split_on_case_changetrueSplit at case transitions
    split_on_numericstrueSplit at digit-letter transitions
    stem_english_possessivetrueRemove trailing 's
    type_tableCustom 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 type value 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_ngram with token_chars = digit, min_gram = 3, max_gram = 10

  • Analyzer: 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_delimiter with split_on_numerics = false, split_on_case_change = false

  • Analyzer: 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: keyword

  • Token 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';