Datastore クエリ数

Datastore のクエリとは、指定した一連の条件を満たすエンティティを Cloud Datastore から取得する処理です。

一般的なクエリは次のような要素で構成されます。

クエリを実行すると、指定した種類のすべてのエンティティの中で指定したすべてのフィルタを満たすものが取得され、指定した順序で並べ替えられます。クエリは読み取り専用として実行されます。

このページでは、Cloud Datastore からデータを取得するために App Engine で使用するクエリの構造と種類について説明します。

フィルタ

クエリのフィルタでは、取得するエンティティのプロパティキー祖先に対する制約を設定します。

プロパティ フィルタ

プロパティ フィルタでは、次の要素を指定します。

  • プロパティ名
  • 比較演算子
  • プロパティ値
例:

Filter propertyFilter =
    new FilterPredicate("height", FilterOperator.GREATER_THAN_OR_EQUAL, minHeight);
Query q = new Query("Person").setFilter(propertyFilter);

プロパティ値はアプリケーションで提供する必要があります。他のプロパティを参照することも、他のプロパティを基準として計算することもできません。エンティティが指定された名前のプロパティを持ち、その値がフィルタで指定された値との比較演算子で示された条件に合致する場合、そのエンティティはフィルタを満たすものと見なされます。

比較演算子としては以下のいずれかを使用できます(ネストクラス Query.FilterOperator で列挙型定数として定義)。

演算子 意味
EQUAL 等しい
LESS_THAN 次の値より小さい
LESS_THAN_OR_EQUAL 次の値以下
GREATER_THAN 次の値より大きい
GREATER_THAN_OR_EQUAL 次の値以上
NOT_EQUAL 等しくない
IN 含まれる(指定されたリストのいずれかの値と等しい)

NOT_EQUAL 演算子は、実際には 2 つのクエリを実行します。1 つは、その他のフィルタを変更することなく NOT_EQUAL フィルタを LESS_THAN に置換したクエリです。もう 1 つは、このフィルタを GREATER_THAN に置換したクエリです。その後、それぞれの結果が適切な順序でマージされます。1 つのクエリに複数の NOT_EQUAL フィルタを含めることはできません。また、このフィルタを含むクエリに別の不等式フィルタを含めることもできません。

同様に、IN 演算子も複数のクエリを実行します。その他のフィルタは変更することなく、IN フィルタを EQUAL フィルタに置換して、指定されたリストのアイテムごとにクエリを実行します。その後、それぞれの結果がリストのアイテムの順にマージされます。クエリに複数の IN フィルタが含まれる場合は、IN リスト内の値の可能な組み合わせごとにクエリが繰り返されます。

NOT_EQUAL 演算子または IN 演算子を含むクエリでは、サブクエリの数が 30 個以下に制限されます。

NOT_EQUAL および IN を含むクエリが JDO / JPA フレームワークで複数のクエリに変換される仕組みについては、!= および IN フィルタを使用したクエリをご覧ください。

主なフィルタ

エンティティのキーの値をフィルタリングするには、特殊なプロパティ Entity.KEY_RESERVED_PROPERTY を使用します。

Filter keyFilter =
    new FilterPredicate(Entity.KEY_RESERVED_PROPERTY, FilterOperator.GREATER_THAN, lastSeenKey);
Query q = new Query("Person").setFilter(keyFilter);

Entity.KEY_RESERVED_PROPERTY に対する昇順の並べ替えもサポートされています。

異なる要素を比較する場合、キーは次の条件に従って次の順に順序付けられます。

  1. 祖先パス
  2. エンティティの種類
  3. 識別子(キー名または数値 ID)

祖先パスの各要素も同様に比較され、最初に種類(文字列)、次にキー名または数値 ID を基準として比較されます。種類とキー名は文字列であり、バイト値を基準として順序付けられます。また数値 ID は整数値であり、その数値の順に順序付けられます。同じ親と種類を持つエンティティが文字列のキー名と数値 ID を混用している場合、数値 ID を持つエンティティのほうがキー名を持つエンティティよりも優先されます。

キーを基準としたクエリではプロパティを基準としたクエリと同じようにインデックスを使用し、カスタム インデックスを必要とする場面も基本的に同じですが、いくつかの例外があります。キーに対して不等式フィルタを使用する場合、または昇順の並べ替え順を使用する場合はカスタム インデックスを必要としませんが、降順の並べ替え順を使用する場合は必要になります。すべてのクエリと同様に、開発用ウェブサーバーは、カスタム インデックスが必要なクエリをテストするときに、適切なエントリをインデックス構成ファイルに作成します。

祖先フィルタ

Datastore クエリは、指定した祖先でフィルタリングできます。これにより、返される結果には、その祖先の子孫のみが含まれるようになります。

Query q = new Query("Person").setAncestor(ancestorKey);

特別なタイプのクエリ

特別なタイプのクエリについては、次の点に注意してください。

種類を指定しないクエリ

種類も祖先フィルタも指定していないクエリは、アプリケーションのすべてのエンティティを Datastore から取得します。これには、統計情報エンティティBlobstore メタデータ エンティティなど、他の App Engine 機能によって作成、管理されるエンティティ(存在する場合)も含まれます。このような「種類を指定しないクエリ」では、プロパティ値に対するフィルタや並べ替え順序を含めることはできません。ただし、プロパティ名として Entity.KEY_RESERVED_PROPERTY を指定することで、エンティティ キーによるフィルタリングが可能になります。

Filter keyFilter =
    new FilterPredicate(Entity.KEY_RESERVED_PROPERTY, FilterOperator.GREATER_THAN, lastSeenKey);
Query q = new Query().setFilter(keyFilter);

祖先クエリ

祖先フィルタを含むクエリの結果は、指定されたエンティティとその子孫に限定されます。

DatastoreService datastore = DatastoreServiceFactory.getDatastoreService();

Entity tom = new Entity("Person", "Tom");
Key tomKey = tom.getKey();
datastore.put(tom);

Entity weddingPhoto = new Entity("Photo", tomKey);
weddingPhoto.setProperty("imageURL", "http://domain.com/some/path/to/wedding_photo.jpg");

Entity babyPhoto = new Entity("Photo", tomKey);
babyPhoto.setProperty("imageURL", "http://domain.com/some/path/to/baby_photo.jpg");

Entity dancePhoto = new Entity("Photo", tomKey);
dancePhoto.setProperty("imageURL", "http://domain.com/some/path/to/dance_photo.jpg");

Entity campingPhoto = new Entity("Photo");
campingPhoto.setProperty("imageURL", "http://domain.com/some/path/to/camping_photo.jpg");

List<Entity> photoList = Arrays.asList(weddingPhoto, babyPhoto, dancePhoto, campingPhoto);
datastore.put(photoList);

Query photoQuery = new Query("Photo").setAncestor(tomKey);

// This returns weddingPhoto, babyPhoto, and dancePhoto,
// but not campingPhoto, because tom is not an ancestor
List<Entity> results =
    datastore.prepare(photoQuery).asList(FetchOptions.Builder.withDefaults());

種類を指定しない祖先クエリ

祖先フィルタを含む「種類を指定しない」クエリは、種類にかかわりなく、指定した祖先と、その子孫のすべてを取得します。この種類のクエリにカスタム インデックスは必要ありません。すべての種類なしクエリと同様に、プロパティ値に対するフィルタや並べ替え順を含めることはできませんが、エンティティのキーに対するフィルタリングは可能です。

Filter keyFilter =
    new FilterPredicate(Entity.KEY_RESERVED_PROPERTY, FilterOperator.GREATER_THAN, lastSeenKey);
Query q = new Query().setAncestor(ancestorKey).setFilter(keyFilter);

次の例は、特定の祖先を持つ子孫のエンティティをすべて取得する方法を示しています。

DatastoreService datastore = DatastoreServiceFactory.getDatastoreService();

Entity tom = new Entity("Person", "Tom");
Key tomKey = tom.getKey();
datastore.put(tom);

Entity weddingPhoto = new Entity("Photo", tomKey);
weddingPhoto.setProperty("imageURL", "http://domain.com/some/path/to/wedding_photo.jpg");

Entity weddingVideo = new Entity("Video", tomKey);
weddingVideo.setProperty("videoURL", "http://domain.com/some/path/to/wedding_video.avi");

List<Entity> mediaList = Arrays.asList(weddingPhoto, weddingVideo);
datastore.put(mediaList);

// By default, ancestor queries include the specified ancestor itself.
// The following filter excludes the ancestor from the query results.
Filter keyFilter =
    new FilterPredicate(Entity.KEY_RESERVED_PROPERTY, FilterOperator.GREATER_THAN, tomKey);

Query mediaQuery = new Query().setAncestor(tomKey).setFilter(keyFilter);

// Returns both weddingPhoto and weddingVideo,
// even though they are of different entity kinds
List<Entity> results =
    datastore.prepare(mediaQuery).asList(FetchOptions.Builder.withDefaults());

キーのみのクエリ

キーのみのクエリは、結果のエンティティ自体ではなく、エンティティのキーのみを返すため、エンティティ全体を取得するよりも、レイテンシとコストを抑えられます。

Query q = new Query("Person").setKeysOnly();

一般的なクエリを実行すると、実際に必要なものよりも多くのエンティティがフェッチされる場合があります。それに対して、まずキーのみのクエリを実行し、その結果からエンティティのサブセットをフェッチしたほうが、多くの場合経済的です。

射影クエリ

クエリの結果で実際に必要なのは、ごくわずかな特定のプロパティの値だけという場合もあります。このような場合、エンティティ全体を取得するのではなく、射影クエリを使用して実際に必要なプロパティだけを取得すると、レイテンシとコストを抑えることができます。詳細については、射影クエリをご覧ください。

並べ替え順序

クエリの並べ替え順序では、次の要素を指定します。

  • プロパティ名
  • 並べ替えの方向(昇順または降順)

例:

// Order alphabetically by last name:
Query q1 = new Query("Person").addSort("lastName", SortDirection.ASCENDING);

// Order by height, tallest to shortest:
Query q2 = new Query("Person").addSort("height", SortDirection.DESCENDING);

クエリに複数の並べ替え順序が含まれている場合は、指定されている順序で適用されます。次の例では、最初に名字の昇順で並べ替えてから、身長の降順で並べ替えます。

Query q =
    new Query("Person")
        .addSort("lastName", SortDirection.ASCENDING)
        .addSort("height", SortDirection.DESCENDING);

並べ替え順序が指定されていない場合、結果は Datastore から取得された順序で返されます。

注: あるプロパティに対して不等式フィルタを指定し、別のプロパティに対して並べ替え順序を指定する場合は、不等式フィルタで使用されているプロパティを他のプロパティよりも前に並べる必要があります。これは Datastore がクエリを実行する仕組みに原因があります。

インデックス

Datastore クエリでは、常に 1 つ以上のインデックスを使用して結果が計算されます。インデックスには、インデックスのプロパティとエンティティの祖先(省略可)によって指定されている順序でエンティティ キーが格納されています。インデックスは、アプリケーションがエンティティに加えた変更を反映して増分的に更新されます。そのため追加の計算を行うことなく、すべてのクエリの正しい結果が得られます。

App Engine は、エンティティの各プロパティに単純なインデックスを事前定義します。App Engine アプリケーションでは、アプリケーションの /war/WEB-INF/appengine-generated ディレクトリに生成される datastore-indexes.xml というインデックス構成ファイルで、さらにカスタム インデックスを定義できます。開発用サーバーは、既存のインデックスでは実行できないクエリが出現したときに、このファイルに自動的に候補を追加します。アプリケーションをアップロードする前にこのファイルを編集して、インデックスを手動で調整できます。

クエリ インターフェースの例

低レベルの Java Datastore API には、クエリを作成するためのクラス Query と、Datastore からエンティティを取得するための PreparedQuery インターフェースがあります。

DatastoreService datastore = DatastoreServiceFactory.getDatastoreService();

Filter heightMinFilter =
    new FilterPredicate("height", FilterOperator.GREATER_THAN_OR_EQUAL, minHeight);

Filter heightMaxFilter =
    new FilterPredicate("height", FilterOperator.LESS_THAN_OR_EQUAL, maxHeight);

// Use CompositeFilter to combine multiple filters
CompositeFilter heightRangeFilter =
    CompositeFilterOperator.and(heightMinFilter, heightMaxFilter);

// Use class Query to assemble a query
Query q = new Query("Person").setFilter(heightRangeFilter);

// Use PreparedQuery interface to retrieve results
PreparedQuery pq = datastore.prepare(q);

for (Entity result : pq.asIterable()) {
  String firstName = (String) result.getProperty("firstName");
  String lastName = (String) result.getProperty("lastName");
  Long height = (Long) result.getProperty("height");

  out.println(firstName + " " + lastName + ", " + height + " inches tall");
}

FilterPredicateCompositeFilter を使用してフィルタを作成していていることに注意してください。クエリに 1 つのフィルタのみを設定する場合は、FilterPredicate を単独で使用できます。

Filter heightMinFilter =
    new FilterPredicate("height", FilterOperator.GREATER_THAN_OR_EQUAL, minHeight);

Query q = new Query("Person").setFilter(heightMinFilter);

ただし、クエリに複数のフィルタを設定する場合は、CompositeFilter を使用する必要があります(これを使用するには 2 つ以上のフィルタが必要です)。上記の例では、CompositeFilterOperator.and ショートカット ヘルパーを使用しています。次の例は、composite OR フィルタを作成する 1 つの方法を示しています。

Filter tooShortFilter = new FilterPredicate("height", FilterOperator.LESS_THAN, minHeight);

Filter tooTallFilter = new FilterPredicate("height", FilterOperator.GREATER_THAN, maxHeight);

Filter heightOutOfRangeFilter = CompositeFilterOperator.or(tooShortFilter, tooTallFilter);

Query q = new Query("Person").setFilter(heightOutOfRangeFilter);

次のステップ