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

Tablestore:ソートとページング

最終更新日:May 13, 2026

検索インデックスからデータをクエリする際、順序を事前定義するか、クエリ時に指定することで結果をソートできます。大きな結果セットの場合は、オフセットベースまたはトークンベースのページングを使用して、必要なデータを迅速に見つけることができます。

ユースケース

カテゴリ

方法

機能

ユースケース

ソート

作成時の定義

インデックスの事前ソート (IndexSort)

デフォルトでは、Tablestore は設定されたインデックスの事前ソート (IndexSort) メソッドを使用して、検索インデックス内のデータをソートします。これにより、返される結果のデフォルトのソート順序が決まります。

クエリ時の定義

ScoreSort (関連度スコアソート)

BM25 アルゴリズムを使用して計算される関連度スコアによってクエリ結果をソートします。これは、全文検索など、関連度に基づくシナリオに適しています。

PrimaryKeySort (プライマリキーソート)

プライマリキーによって結果をソートします。これは、一意の識別子で項目をソートする場合に便利です。

FieldSort (フィールドソート)

フィールド値によって結果をソートします。これは、eコマースやソーシャルメディアのシナリオにおいて、販売量やページビューなどの属性でソートする場合に役立ちます。

配列型やネスト型フィールドなどの複数値フィールドの場合、mode パラメータを使用して、ソートに使用する要素を制御できます。

GeoDistanceSort (地理距離ソート)

地理的なポイントからの距離によって結果をソートします。これは、地図や物流などの位置情報サービスに適しており、たとえば、近くのレストランを距離でソートする場合などに使用します。

ページング

クエリ時の定義

オフセットベースのページングの使用

取得する行の総数が 100,000 未満の場合は、オフセットベースのページングを使用します。

トークンベースのページングの使用

トークンベースのページングを使用して、大きな結果セットを順次取得します。デフォルトでは、前方へのページングのみ可能です。ただし、トークンはクエリプロセス全体で有効なままであるため、前のトークンをキャッシュして逆方向にもページングできます。

SDK

次の言語の SDK を使用して、ソートとページングを実装できます。

インデックスの事前ソート

デフォルトでは、Tablestore はインデックスの事前ソート (IndexSort) と呼ばれる事前定義されたソート順序に基づいて、検索インデックス内のデータをソートします。データをクエリする際、IndexSort が、返される結果のデフォルトの順序を決定します。

検索インデックスを作成するときに、IndexSort をカスタマイズできます。この設定を指定しない場合、インデックスはデフォルトでプライマリキーソートになります。

重要
  • インデックスの事前ソートは、PrimaryKeySort (プライマリキーソート) と FieldSort (フィールドソート) のみをサポートします。

  • ネスト型フィールドを含む検索インデックスには、インデックスの事前ソートを使用できません。

  • 検索インデックスを作成した後、動的スキーマ変更機能を使用して IndexSort 設定を変更できます。

クエリ時のソート

enableSortAndAgg プロパティが true に設定されているフィールドでのみソートできます。

クエリごとにソート方法を指定できます。検索インデックスは、次の 4 種類のソーターをサポートしています。複数のソーターを使用して、一連の基準に基づいて結果をソートすることもできます。

ScoreSort

BM25 アルゴリズムを使用して計算される関連度スコアによってクエリ結果をソートします。この方法は、全文検索など、関連度に基づくシナリオに適しています。

重要
  • 関連度スコアでソートする場合は、ScoreSort を明示的に指定する必要があります。指定しない場合、Tablestore はインデックスの IndexSort 設定に従って結果をソートします。

  • ScoreSort を使用する場合、FuzzyKeyword 型のフィールドはソートに含まれず、weight パラメータはこれらのフィールドに影響を与えません。

SearchQuery searchQuery = new SearchQuery();
searchQuery.setSort(new Sort(Arrays.asList(new ScoreSort())));

PrimaryKeySort

プライマリキーによって結果をソートします。

SearchQuery searchQuery = new SearchQuery();
searchQuery.setSort(new Sort(Arrays.asList(new PrimaryKeySort()))); // 昇順。
//searchQuery.setSort(new Sort(Arrays.asList(new PrimaryKeySort(SortOrder.DESC)))); // 降順。

FieldSort

フィールド値によって結果をソートします。

単一フィールドでのソート

単一フィールドの値に基づいて結果をソートします。

SearchQuery searchQuery = new SearchQuery();
searchQuery.setSort(new Sort(Arrays.asList(new FieldSort("col", SortOrder.ASC))));

複数フィールドでのソート

最初に 1 つのフィールドの値で結果をソートし、次に別のフィールドの値でソートします。

SearchQuery searchQuery = new SearchQuery();
searchQuery.setSort(new Sort(Arrays.asList(
    new FieldSort("col1", SortOrder.ASC), new FieldSort("col2", SortOrder.ASC))));

フォールバックフィールド

Long、Double、または Date 型のフィールドでソートする場合、missingField パラメータを設定できます。このパラメータは、ソート対象フィールドに値がない場合に、フォールバックとしてソートに使用する、同じ型の別のフィールドを指定します。

/**
* Col_Long フィールドの値に基づいて結果を降順でソートします。
* 行に Col_Long フィールド (Long 型) の値がない場合、
* 代わりに Col_Long_sec フィールド (Long 型) の値がソートに使用されます。
*/
SearchQuery searchQuery = new SearchQuery();
FieldSort fieldSort = new FieldSort("Col_Long");
// Col_Long フィールドに値がない場合のソートのフォールバックとして Col_Long_sec フィールドを指定します。
fieldSort.setMissingField("Col_Long_sec");
fieldSort.setOrder(SortOrder.DESC); 

欠損値

missingValue パラメータは、ソートフィールドが欠損しているドキュメントのソート位置を指定します。このパラメータを設定して、これらのドキュメントが結果のどこに表示されるかを制御できます。

ソートの動作は次のとおりです。

  • missingValueFieldSort.FIRST_WHEN_MISSING に設定した場合、欠損値を持つドキュメントは、ソート順序 (昇順または降順) にかかわらず、常に結果の先頭に配置されます。

  • missingValueFieldSort.LAST_WHEN_MISSING または null に設定した場合、欠損値を持つドキュメントは、ソート順序にかかわらず、常に結果の末尾に配置されます。

    /**
    * Col_Long フィールド (Long 型) の値に基づいて結果を降順でソートします。
    * 行に Col_Long フィールドの値がない場合、そのドキュメントを結果の末尾に配置します。
    */
    SearchQuery searchQuery = new SearchQuery();
    FieldSort fieldSort = new FieldSort("Col_Long");
    // 欠損値を持つドキュメントを最後に配置します。
    fieldSort.setMissingValue(FieldSort.LAST_WHEN_MISSING);
    fieldSort.setOrder(SortOrder.DESC);
    searchQuery.setSort(new Sort(Arrays.asList(fieldSort)));

複数値フィールド

配列型やネスト型フィールドなどの複数値フィールドの場合、mode パラメータを使用して、ソートに使用する要素を指定できます。

複数値配列内の指定された値でソートします。

// doc1 と doc2 の 2 つの行があるとします。どちらも配列型の field1 があります。
// doc1 の field1 の値は [2,3] です。doc2 の field1 の値は [1,3,4] です。
// mode パラメータを設定して、ソートに使用する配列内の値を指定できます。
{
    // mode が SortMode.MAX に設定されている場合、ソート順は doc2 (4 でソート)、次に doc1 (3 でソート) となります。
    FieldSort fieldSort = new FieldSort("field1", SortOrder.DESC);
    fieldSort.setMode(SortMode.MAX);
}
{
    // mode が SortMode.MIN に設定されている場合、ソート順は doc1 (2 でソート)、次に doc2 (1 でソート) となります。
    FieldSort fieldSort = new FieldSort("field1", SortOrder.DESC);
    fieldSort.setMode(SortMode.MIN);
}

ネスト型フィールドのサブ行をソートすることもできます。

// doc1 と doc2 の 2 つの行があるとします。どちらもネスト型の field1 があります。
// doc1 の field1 の値は [{"name":"b", "age":1},{"name":"a", "age":7}] です。
// doc2 の field1 の値は [{"name":"a", "age":1},{"name":"c", "age":1},{"name":"d", "age":5}] です。

{
    // すべてのサブ行をソートし、mode パラメータを使用してソートに使用する値を指定します。
    // mode が SortMode.MAX に設定され、age フィールドでソートする場合、結果は doc1 (7 でソート)、次に doc2 (5 でソート) となります。
    FieldSort fieldSort = new FieldSort("field1.age", SortOrder.DESC);
    fieldSort.setMode(SortMode.MAX);
    String path = "field1";
    NestedFilter nestedFilter = new NestedFilter(path, QueryBuilders.matchAll().build());
    fieldSort.setNestedFilter(nestedFilter);
}
{
    // age=1 のサブ行のみをソートし、mode パラメータを使用してどの値を使用するかを指定します。
    {
        // mode が SortMode.MAX に設定され、name フィールドでソートする場合、結果は doc2 ("c" でソート)、次に doc1 ("b" でソート) となります。
        FieldSort fieldSort = new FieldSort("field1.name", SortOrder.DESC);
        fieldSort.setMode(SortMode.MAX);
        String path = "field1";
        NestedFilter nestedFilter = new NestedFilter(path, QueryBuilders.term("field1.age",1).build());
        fieldSort.setNestedFilter(nestedFilter);
    }
    {
        // mode が SortMode.MIN に設定され、name フィールドでソートする場合、結果は doc1 ("b" でソート)、次に doc2 ("a" でソート) となります。
        FieldSort fieldSort = new FieldSort("field1.name", SortOrder.DESC);
        fieldSort.setMode(SortMode.MIN);
        String path = "field1";
        NestedFilter nestedFilter = new NestedFilter(path, QueryBuilders.term("field1.age",1).build());
        fieldSort.setNestedFilter(nestedFilter);
    }
}

GeoDistanceSort

geo フィールドは GeoPoint 型です。このフィールドの値とポイント "0,0" との距離で結果をソートします。

SearchQuery searchQuery = new SearchQuery();
// 'geo' フィールドは GeoPoint 型です。このフィールドの値とポイント "0,0" との
// 距離で結果をソートします。
Sort.Sorter sorter = new GeoDistanceSort("geo", Arrays.asList("0, 0"));
searchQuery.setSort(new Sort(Arrays.asList(sorter)));

ページング方法

クエリ結果のページングには、limitoffset パラメータまたはトークンを使用できます。

オフセットベースのページング

取得する行の総数が 100,000 未満の場合に、オフセットベースのページングを使用できます。limitoffset の合計は 100,000 以下である必要があり、limit の最大値は 100 です。

説明

limit のしきい値を引き上げるには、「Search API の limit を 1,000 に引き上げる方法」をご参照ください。

limitoffset パラメータを設定しない場合、limit のデフォルトは 10、offset のデフォルトは 0 です。

SearchQuery searchQuery = new SearchQuery();
searchQuery.setQuery(new MatchAllQuery());
searchQuery.setLimit(100);
searchQuery.setOffset(100);

トークンベースのページング

深度の制限がないため、ディープページングにはトークンベースのページングを推奨します。

レスポンスにすべての一致するデータが含まれていない場合、サーバーは nextToken を返します。

デフォルトでは、トークンベースのページングでは前方へのページングのみ可能です。ただし、トークンはクエリプロセス全体で有効なままであるため、前のトークンをキャッシュして逆方向にもページングできます。

重要

nextToken を永続化するか、フロントエンドアプリケーションに送信する必要がある場合は、Base64 を使用して文字列にエンコードしてください。 トークンは文字列ではなくバイト配列であるため、new String(nextToken) を使用して直接文字列に変換すると、データ損失が発生します。

トークンを使用してページングする場合、ソート順序は、デフォルトの IndexSort を使用するかカスタムソートを使用するかにかかわらず、前のリクエストと同じになります。したがって、トークンが使用されている場合、Sort パラメータを設定することはできません。また、データを順次読み取ることしかできないため、offset を設定することもできません。

重要

ネスト型フィールドを含む検索インデックスは、インデックスの事前ソートをサポートしていません。このようなインデックスの結果をページングする必要がある場合は、クエリでソート順序を指定する必要があります。ソート順序を指定しない場合、利用可能なデータがさらにあっても、サーバーは nextToken を返しません。

private static void readMoreRowsWithToken(SyncClient client) {
    SearchQuery searchQuery = new SearchQuery();
    searchQuery.setQuery(new MatchAllQuery());
    searchQuery.setGetTotalCount(true); // 一致する行の総数を返すように設定します。
    // データテーブル名 (例:sampleTable) と検索インデックス名 (例:sampleSearchIndex) を指定します。
    // 検索インデックス名は、Tablestore コンソールのテーブルの [インデックス管理] タブで確認するか、SDK で検索インデックスを一覧表示することで確認できます。
    SearchRequest searchRequest = new SearchRequest("sampleTable", "sampleSearchIndex", searchQuery);

    SearchResponse resp = client.search(searchRequest);
    if (!resp.isAllSuccess()) {
        throw new RuntimeException("not all success");
    }
    List<Row> rows = resp.getRows();
    while (resp.getNextToken() != null) { // nextToken が null の場合は、すべてのデータが読み取られたことを意味します。
        // nextToken を取得します。
        byte[] nextToken = resp.getNextToken();

        {
            // nextToken を永続化したり、フロントエンドアプリケーションに送信したりする必要がある場合、
            // Base64 を使用して nextToken を文字列にエンコードして、保存および転送します。
            // トークンはバイト配列です。 new String(nextToken) を直接使用すると、データが失われます。
            String tokenAsString = Base64.toBase64String(nextToken);
            // 文字列をバイト配列にデコードします。
            byte[] tokenAsByte = Base64.fromBase64String(tokenAsString);
        }

        // 次のリクエストのトークンを設定します。
        searchRequest.getSearchQuery().setToken(nextToken);
        resp = client.search(searchRequest);
        if (!resp.isAllSuccess()) {
            throw new RuntimeException("not all success");
        }
        rows.addAll(resp.getRows());
    }
    System.out.println("RowSize: " + rows.size());
    System.out.println("TotalCount: " + resp.getTotalCount()); // 返された行数ではなく、一致した行の総数を出力します。
}