すべてのプロダクト
Search
ドキュメントセンター

Tablestore:トークン化ベースのワイルドカードクエリ

最終更新日:Dec 28, 2024

*word*文字列を検索するためにワイルドカードクエリを実行する場合、トークン化ベースのワイルドカードクエリ(あいまいトークン化と一致フレーズクエリの組み合わせ)を使用して、クエリのパフォーマンスを向上させることができます。

背景情報

あいまいクエリは、データベースにおける一般的な要件です。たとえば、あいまいクエリを実行して、ファイル名や携帯電話番号をクエリできます。Tablestoreであいまいクエリを実行するには、検索インデックスのワイルドカードクエリ機能を使用できます。ワイルドカードクエリ機能は、MySQLのLIKE演算子に似ています。ただし、ワイルドカードクエリ機能は、ワイルドカードクエリに使用される文字列で最大32文字までしかサポートしておらず、データサイズが大きくなるにつれてクエリのパフォーマンスが低下します。

これらの問題を解決するために、検索インデックスはトークン化ベースのワイルドカードクエリをサポートして、あいまいクエリでの高パフォーマンスを保証します。トークン化ベースのワイルドカードクエリを使用する場合、Tablestoreはクエリに使用される文字列の長さを制限しません。ただし、列の値の長さが1,024文字を超える場合、システムは列の値を切り捨て、最初の1,024文字に対してのみトークン化を実行します。

シナリオ

シナリオに合った方法を選択して、あいまいクエリを実行できます。

  • ワイルドカードクエリに*word*を使用する場合、トークン化ベースのワイルドカードクエリを使用してあいまいクエリを実行できます。たとえば、"123"を使用して、任意の位置に123を含む携帯電話番号をクエリする場合、トークン化ベースのワイルドカードクエリを使用してあいまいクエリを実行できます。

    この場合、トークン化ベースのワイルドカードクエリは、ワイルドカードクエリよりも10倍以上クエリのパフォーマンスを向上させます。

    たとえば、データテーブルにfile_nameという名前の列が含まれており、列のタイプがTextで、検索インデックスの列のトークン化方法があいまいトークン化(FuzzyAnalyzer)であるとします。検索インデックスを使用して、file_name列の値が2021 woRK@Hangzhouである行をクエリする場合、一致フレーズクエリ(MatchPhraseQuery)を実行し、クエリに連続する部分文字列にトークンを設定する必要があります。

    • クエリのトークンが20212021workWORK@HangzhouHangzhou、または@Hangzhouの場合、file_name列の値が2021 woRK@Hangzhouである行はトークンと一致します。

    • クエリのトークンが21work2021Hangzhou2120、または#Hangzhouの場合、file_name列の値が2021 woRK@Hangzhouである行はトークンと一致しません。

  • その他の複雑なクエリについては、ワイルドカードクエリを使用してあいまいクエリを実行できます。詳細については、「ワイルドカードクエリ」を参照してください。

あいまいクエリにあいまいトークン化を使用する

あいまいクエリにトークン化ベースのワイルドカードクエリを使用するには、次の手順を実行します。

  1. 検索インデックスを作成します。検索インデックスを作成するときは、指定した列のフィールドタイプをTextに設定し、トークン化方法をあいまいトークン化(FuzzyAnalyzer)に設定し、他のパラメーターはデフォルト設定を保持します。詳細については、「検索インデックスを作成する」を参照してください。

    説明

    検索インデックスが存在する場合は、検索インデックスのスキーマを動的に変更することで、指定した列に仮想列を追加できます。次に、仮想列のフィールドタイプをTextに設定し、トークン化方法をあいまいトークン化に設定します。詳細については、「検索インデックスのスキーマを動的に変更する」および「仮想列」を参照してください。

  2. 検索インデックスを使用してデータをクエリします。検索インデックスを使用してデータをクエリするときは、一致フレーズクエリを実行します。詳細については、「一致フレーズクエリ」を参照してください。

付録:テストケース

次のテストケースは、トークン化ベースのワイルドカードクエリを使用してあいまいクエリを実行する方法を示しています:

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 {
        // 検索インデックスのスキーマを指定します。
        IndexSchema indexSchema = new IndexSchema();
        indexSchema.setFieldSchemas(Collections.singletonList(
                // 注:データテーブルのKeywordタイプのname列にマッピングされるnameフィールドのタイプにTextを指定し、フィールドのトークン化方法を設定すると、クエリで例外が発生する可能性があります。
                // KeywordタイプとTextタイプの両方を保持する場合は、「仮想列」トピックに記載されている例を参照してください。*abc*を使用してnameフィールドと一致させる場合は、Textタイプのnameフィールドのみが必要です。Keywordタイプのnameフィールドは必要ありません。
                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("TheLionKing1024x768P.mp4"));
        PutRowRequest request = new PutRowRequest(rowPutChange);
        client.putRow(request);

        // データの行が検索インデックスに同期されるまで待機します。
        Thread.sleep(1000 * 180);

        // クエリに*abc*を使用します。
        assertMatchPhraseQuery(client, tableName, indexName, "name", "The", 1);
        assertMatchPhraseQuery(client, tableName, indexName, "name", "TheLion", 1);
        assertMatchPhraseQuery(client, tableName, indexName, "name", "The Lion", 0);
        assertMatchPhraseQuery(client, tableName, indexName, "name", "TheLionKing102", 1);
        assertMatchPhraseQuery(client, tableName, indexName, "name", "TheLionKing1024", 1);
        assertMatchPhraseQuery(client, tableName, indexName, "name", "TheLionKing1024x", 1);
        assertMatchPhraseQuery(client, tableName, indexName, "name", "TheLionKing1024x7", 1);
        assertMatchPhraseQuery(client, tableName, indexName, "name", "TheLionKing1024x768P.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 {
        // 検索インデックスのスキーマを指定します。
        IndexSchema indexSchema = new IndexSchema();
        indexSchema.setFieldSchemas(Arrays.asList(
                // nameフィールドのタイプを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("TheLionKing1024x768P.mp4"));
        PutRowRequest request = new PutRowRequest(rowPutChange);
        client.putRow(request);

        // データの行が検索インデックスに同期されるまで待機します。
        Thread.sleep(1000 * 180);

        // クエリに*abc*を使用します。
        // 注:クエリのフィールドはnameではなくname_virtual_textです。
        assertMatchPhraseQuery(client, tableName, indexName, "name_virtual_text", "The", 1);
        assertMatchPhraseQuery(client, tableName, indexName, "name_virtual_text", "TheLion", 1);
        assertMatchPhraseQuery(client, tableName, indexName, "name_virtual_text", "The Lion", 0);
        assertMatchPhraseQuery(client, tableName, indexName, "name_virtual_text", "TheLionKing102", 1);
        assertMatchPhraseQuery(client, tableName, indexName, "name_virtual_text", "TheLionKing1024", 1);
        assertMatchPhraseQuery(client, tableName, indexName, "name_virtual_text", "TheLionKing1024x", 1);
        assertMatchPhraseQuery(client, tableName, indexName, "name_virtual_text", "TheLionKing1024x7", 1);
        assertMatchPhraseQuery(client, tableName, indexName, "name_virtual_text", "TheLionKing1024x768P.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);
    }

    // 一致フレーズクエリを実行します。
    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();
        // 一致フレーズクエリを実行して、トークンと一致するデータをクエリします。
        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());
    }
}

FAQ

参考資料