全部產品
Search
文件中心

Tablestore:基於分詞的萬用字元查詢

更新時間:Sep 20, 2024

對於萬用字元查詢(WildcardQuery)中查詢模式為*word*的情境,您可以使用基於分詞的萬用字元查詢方式(即模糊分詞和短語匹配查詢組合使用)來實現效能更好的模糊查詢。

背景資訊

模糊查詢是資料庫業務中常見的需求,例如查詢檔案名稱、手機號碼等。在Table Store中要實現模糊查詢,通常使用多元索引的萬用字元查詢來實作類別似於MySQL中的like功能,但是萬用字元查詢存在查詢詞長度限制(最長32個字元)以及效能會隨著資料量增長而下降的限制。

為瞭解決萬用字元查詢存在的問題,多元索引支援使用基於分詞的萬用字元查詢方式來實現效能更好的模糊查詢。當使用基於分詞的萬用字元查詢方式時,查詢詞長度無限制,但是原文內容會限制最大1024字元或者漢字,超過後會截斷,只保留前1024個字元或者漢字。

適用情境

請根據查詢情境選擇合適的方式實現模糊查詢。

  • 對於萬用字元查詢中查詢模式為*word*的情境,例如通過"123" 匹配手機號碼中任意位置包含123的號碼,請使用基於分詞的萬用字元查詢來實現模糊查詢。

    在此情境中,大部分情況下使用基於分詞的萬用字元查詢方式會比使用萬用字元查詢有10倍以上的效能提升。

    假設資料表中包含file_name列,該列在多元索引中的欄位類型為Text且分詞類型為模糊分詞(Fuzzy_Analyzer)。如果使用多元索引查詢需要查詢到file_name列值為2021 woRK@杭州的行,則查詢時必須使用短語匹配查詢(MatchPhraseQuery),並設定查詢詞為位置連續的子字串。

    • 如果查詢詞為20212021workWORK@杭州@杭州中的任意一個,則可以匹配到file_name列值為2021 woRK@杭州的行。

    • 如果查詢詞為21work2021杭州2120#杭州中的任意一個,則無法匹配到file_name列值為2021 woRK@杭州的行。

  • 對於其他複雜查詢情境,請使用萬用字元查詢方式來實現模糊查詢。更多資訊,請參見萬用字元查詢

使用方式

使用基於分詞的萬用字元查詢方式實現模糊查詢的具體步驟如下:

  1. 建立多元索引時,指定列類型為Text且分詞類型為模糊分詞(Fuzzy Analyzer),其他參數保持預設配置即可。具體操作,請參見建立多元索引

    說明

    如果已建立多元索引,您可以通過動態修改schema功能為指定列添加虛擬列,同時設定虛擬列為Text類型且分詞類型為模糊分詞來實現。具體操作,請分別參見動態修改schema虛擬列

  2. 使用多元索引查詢資料時,使用MatchPhraseQuery。具體操作,請參見短語匹配查詢

附錄:測試案例

以下樣本通過測試案例的方式展示了使用基於分詞的萬用字元查詢方式實現模糊查詢的效果。

import com.alicloud.openservices.tablestore.SyncClient;
import com.alicloud.openservices.tablestore.model.ColumnValue;
import com.alicloud.openservices.tablestore.model.PrimaryKey;
import com.alicloud.openservices.tablestore.model.PrimaryKeyBuilder;
import com.alicloud.openservices.tablestore.model.PrimaryKeyValue;
import com.alicloud.openservices.tablestore.model.PutRowRequest;
import com.alicloud.openservices.tablestore.model.RowPutChange;
import com.alicloud.openservices.tablestore.model.search.CreateSearchIndexRequest;
import com.alicloud.openservices.tablestore.model.search.CreateSearchIndexResponse;
import com.alicloud.openservices.tablestore.model.search.FieldSchema;
import com.alicloud.openservices.tablestore.model.search.FieldType;
import com.alicloud.openservices.tablestore.model.search.IndexSchema;
import com.alicloud.openservices.tablestore.model.search.SearchQuery;
import com.alicloud.openservices.tablestore.model.search.SearchRequest;
import com.alicloud.openservices.tablestore.model.search.SearchResponse;
import com.alicloud.openservices.tablestore.model.search.query.QueryBuilders;

import java.util.Arrays;
import java.util.Collections;

import static org.junit.Assert.assertEquals;


public class TestFuzzy {
    private static final String tableName = "analysis_test";
    private static final String indexName = "analysis_test_index";


    public void testFuzzyMatchPhrase(SyncClient client) throws Exception {
        // 定義表schema。
        IndexSchema indexSchema = new IndexSchema();
        indexSchema.setFieldSchemas(Collections.singletonList(
                // 注意:當原來查詢的name欄位為Keyword類型時,如果修改該欄位為Text類型並為該欄位設定分詞後,查詢可能會出現異常。
                // 如果需要同時保留Keyword和Text類型,請參見“虛擬列”功能的樣本。假如使用name欄位只需要完成匹配*abc*的查詢功能,則只用Text類型的欄位即可,無需Keyword類型。
                new FieldSchema("name", FieldType.TEXT).setAnalyzer(FieldSchema.Analyzer.Fuzzy)
        ));
        // 建立多元索引。
        {
            CreateSearchIndexRequest request = new CreateSearchIndexRequest();
            request.setTableName(tableName);
            request.setIndexName(indexName);
            request.setIndexSchema(indexSchema);
            CreateSearchIndexResponse response = client.createSearchIndex(request);
        }

        // 寫入一行資料。
        PrimaryKey primaryKey = PrimaryKeyBuilder.createPrimaryKeyBuilder()
                .addPrimaryKeyColumn("pk1", PrimaryKeyValue.fromString("1"))
                .addPrimaryKeyColumn("pk2", PrimaryKeyValue.fromLong(1))
                .addPrimaryKeyColumn("pk3", PrimaryKeyValue.fromBinary(new byte[]{1, 2, 3}))
                .build();
        RowPutChange rowPutChange = new RowPutChange(tableName, primaryKey);
        // 寫入屬性列。
        rowPutChange.addColumn("name", ColumnValue.fromString("調音師1024x768P.mp4"));
        PutRowRequest request = new PutRowRequest(rowPutChange);
        client.putRow(request);

        // 等待多元索引中同步完成一條資料。
        Thread.sleep(1000 * 180);

        // 匹配*abc*的查詢功能情境展示。
        assertMatchPhraseQuery(client, tableName, indexName, "name", "調", 1);
        assertMatchPhraseQuery(client, tableName, indexName, "name", "調音", 1);
        assertMatchPhraseQuery(client, tableName, indexName, "name", "調 音", 0);
        assertMatchPhraseQuery(client, tableName, indexName, "name", "調音師102", 1);
        assertMatchPhraseQuery(client, tableName, indexName, "name", "調音師1024", 1);
        assertMatchPhraseQuery(client, tableName, indexName, "name", "調音師1024x", 1);
        assertMatchPhraseQuery(client, tableName, indexName, "name", "調音師1024x7", 1);
        assertMatchPhraseQuery(client, tableName, indexName, "name", "調音師1024x768P.mp4", 1);
        assertMatchPhraseQuery(client, tableName, indexName, "name", "24x768P.mp4", 1);
        assertMatchPhraseQuery(client, tableName, indexName, "name", "24x76 8P.mp4", 0);
        assertMatchPhraseQuery(client, tableName, indexName, "name", "24x7 P.mp4", 0);
    }

    // 使用虛擬列。
    public void testFuzzyMatchPhraseWithVirtualField(SyncClient client) throws Exception {
        // 定義表schema。
        IndexSchema indexSchema = new IndexSchema();
        indexSchema.setFieldSchemas(Arrays.asList(
                // 原始欄位為Keyword類型,方便進行等值查詢。
                new FieldSchema("name", FieldType.KEYWORD).setIndex(true).setStore(true),
                // 建立一個虛擬列“name_virtual_text”,同時設定虛擬列為Text類型且分詞類型為Fuzzy。該虛擬列的來源為“name”欄位。
                new FieldSchema("name_virtual_text", FieldType.TEXT).setIndex(true).setAnalyzer(FieldSchema.Analyzer.Fuzzy).setVirtualField(true).setSourceFieldName("name")
        ));
        // 建立多元索引。
        {
            CreateSearchIndexRequest request = new CreateSearchIndexRequest();
            request.setTableName(tableName);
            request.setIndexName(indexName);
            request.setIndexSchema(indexSchema);
            CreateSearchIndexResponse response = client.createSearchIndex(request);
        }

        // 寫入一行資料。
        PrimaryKey primaryKey = PrimaryKeyBuilder.createPrimaryKeyBuilder()
                .addPrimaryKeyColumn("pk1", PrimaryKeyValue.fromString("1"))
                .addPrimaryKeyColumn("pk2", PrimaryKeyValue.fromLong(1))
                .addPrimaryKeyColumn("pk3", PrimaryKeyValue.fromBinary(new byte[]{1, 2, 3}))
                .build();
        RowPutChange rowPutChange = new RowPutChange(tableName, primaryKey);
        // 寫入屬性列。
        rowPutChange.addColumn("name", ColumnValue.fromString("調音師1024x768P.mp4"));
        PutRowRequest request = new PutRowRequest(rowPutChange);
        client.putRow(request);

        // 等待多元索引中同步完成一條資料。
        Thread.sleep(1000 * 180);

        // 配置*abc*的查詢情境展示。
        // 請注意查詢欄位為虛擬列“name_virtual_text”,而不是“name”。
        assertMatchPhraseQuery(client, tableName, indexName, "name_virtual_text", "調", 1);
        assertMatchPhraseQuery(client, tableName, indexName, "name_virtual_text", "調音", 1);
        assertMatchPhraseQuery(client, tableName, indexName, "name_virtual_text", "調 音", 0);
        assertMatchPhraseQuery(client, tableName, indexName, "name_virtual_text", "調音師102", 1);
        assertMatchPhraseQuery(client, tableName, indexName, "name_virtual_text", "調音師1024", 1);
        assertMatchPhraseQuery(client, tableName, indexName, "name_virtual_text", "調音師1024x", 1);
        assertMatchPhraseQuery(client, tableName, indexName, "name_virtual_text", "調音師1024x7", 1);
        assertMatchPhraseQuery(client, tableName, indexName, "name_virtual_text", "調音師1024x768P.mp4", 1);
        assertMatchPhraseQuery(client, tableName, indexName, "name_virtual_text", "24x768P.mp4", 1);
        assertMatchPhraseQuery(client, tableName, indexName, "name_virtual_text", "24x76 8P.mp4", 0);
        assertMatchPhraseQuery(client, tableName, indexName, "name_virtual_text", "24x7 P.mp4", 0);
    }

    // 展示MatchPhraseQuery如何?。
    public static void assertMatchPhraseQuery(SyncClient client, String tableName, String indexName, String fieldName, String searchContent, long exceptCount) {
        SearchRequest searchRequest = new SearchRequest();
        searchRequest.setTableName(tableName);
        searchRequest.setIndexName(indexName);
        SearchQuery searchQuery = new SearchQuery();
        // 使用MatchPhraseQuery查詢分詞欄位。
        searchQuery.setQuery(QueryBuilders.matchPhrase(fieldName, searchContent).build());
        searchQuery.setLimit(0);
        // 為了展示功能需要,此處設定返回匹配總行數。如果不需要關心匹配總行數,請設定為false,來實現更高效能。
        searchQuery.setGetTotalCount(true);
        searchRequest.setSearchQuery(searchQuery);
        SearchResponse response = client.search(searchRequest);
        assertEquals(String.format("field:[%s], searchContent:[%s]", fieldName, searchContent), exceptCount, response.getTotalCount());
    }
}

常見問題

相關文檔