クエリの制限

このページでは、Google App Engine から Datastore に行うクエリに関する制限について説明します。次のリストは、Datastore 向けの開発時に課される一般的な制限です。

クエリで指定されたプロパティがないエンティティは無視される

同じ種類のエンティティが同じプロパティを持つとは限りません。エンティティがクエリ結果の対象になるためには、クエリのフィルタと並べ替え順序で指定されているすべてのプロパティについて値(場合によっては null)を持っていなければなりません。そうでない場合、そのエンティティはクエリの実行に使われるインデックスから除外され、クエリの結果に含まれなくなります。

インデックス付けされていないプロパティのフィルタリングには結果が返されない

クエリはインデックス付けされていないプロパティ値を見つけられず、そのようなプロパティを並べ替えることもできません。インデックス付けされていないプロパティの詳しい説明については、データストア インデックス ページをご覧ください。

不等式フィルタの対象は最大で 1 つのプロパティに制限される

インデックス全体をスキャンしなくても済むように、クエリ メカニズムでは、潜在的なクエリの結果がすべてインデックス内で互いに隣接していることが求められます。この制約を満たすために、単一のクエリの中では、すべてのフィルタにわたり複数のプロパティに対して不等比較(LESS_THANLESS_THAN_OR_EQUALGREATER_THANGREATER_THAN_OR_EQUALNOT_EQUAL)を使うことはできません。たとえば次のクエリは、どちらの不等式フィルタも同じプロパティに適用されているため有効です。

Filter birthYearMinFilter =
    new FilterPredicate("birthYear", FilterOperator.GREATER_THAN_OR_EQUAL, minBirthYear);

Filter birthYearMaxFilter =
    new FilterPredicate("birthYear", FilterOperator.LESS_THAN_OR_EQUAL, maxBirthYear);

Filter birthYearRangeFilter =
    CompositeFilterOperator.and(birthYearMinFilter, birthYearMaxFilter);

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

ただし次のクエリは 2 種類のプロパティに対して不等式フィルタを使用しているため無効です。

Filter birthYearMinFilter =
    new FilterPredicate("birthYear", FilterOperator.GREATER_THAN_OR_EQUAL, minBirthYear);

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

Filter invalidFilter = CompositeFilterOperator.and(birthYearMinFilter, heightMaxFilter);

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

クエリでは、複数のプロパティに対する等式(EQUAL)フィルタと、単一のプロパティに対する 1 つ以上の不等式フィルタを組み合わせることができます。そのため、次のクエリは有効です。

Filter lastNameFilter = new FilterPredicate("lastName", FilterOperator.EQUAL, targetLastName);

Filter cityFilter = new FilterPredicate("city", FilterOperator.EQUAL, targetCity);

Filter birthYearMinFilter =
    new FilterPredicate("birthYear", FilterOperator.GREATER_THAN_OR_EQUAL, minBirthYear);

Filter birthYearMaxFilter =
    new FilterPredicate("birthYear", FilterOperator.LESS_THAN_OR_EQUAL, maxBirthYear);

Filter validFilter =
    CompositeFilterOperator.and(
        lastNameFilter, cityFilter, birthYearMinFilter, birthYearMaxFilter);

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

並べ替え順序が指定されていない場合、クエリ結果の順序付けは定義されない

クエリで並べ替え順序を指定していない場合、結果は取得された順序で返されます。この順序は、Datastore 実装の進展(またはアプリケーションのインデックスの変更)に伴って変化する可能性があります。そのため、クエリの結果が特定の順序に並べ替えられている必要があるアプリケーションでは、並べ替え順序をクエリで明示的に指定してください。

等式フィルタが使用されたプロパティでは並べ替え順序が無視される

特定のプロパティに対して等式フィルタが使われているクエリでは、そのプロパティに対するいかなる並べ替え順序も無視されます。そのプロパティについては、すべての結果で同じ値になるため、それ以上の並べ替えは不要です。これは、単一値のプロパティに対する不要な処理を節減するシンプルな最適化といえます。ただし複数の値を持つプロパティは、等式フィルタに一致したもの以外にも値を持つ可能性があります。このユースケースはまれであり、並べ替え順序を適用するとコストがかかり、余分なインデックスが必要となるため、Datastore のクエリ プランナーでは複数の値を持つ場合であっても単純に並べ替え順序を無視しています。これは、プロパティの値から連想される順序とは異なる順序でクエリの結果が返される原因となる可能性があります。

不等式フィルタで使われているプロパティは最初に並べ替える必要がある

不等式フィルタに一致するすべての結果を取得するため、クエリは、まずフィルタに一致する最初の行のインデックスをスキャンし、一致しない行に遭遇するまでスキャンを進めます。連続した行に結果セット全体が含まれるようにするため、他のプロパティよりも前に、不等式フィルタで使われているプロパティを基準として行を並べ替える必要があります。そのため、クエリで 1 つ以上の不等式フィルタとともに 1 つ以上の並べ替え順序を指定している場合は、最初の並べ替え順序で参照するプロパティが不等式フィルタで指定されているプロパティと同じものでなければなりません。次に有効なクエリを示します。

Filter birthYearMinFilter =
    new FilterPredicate("birthYear", FilterOperator.GREATER_THAN_OR_EQUAL, minBirthYear);

Query q =
    new Query("Person")
        .setFilter(birthYearMinFilter)
        .addSort("birthYear", SortDirection.ASCENDING)
        .addSort("lastName", SortDirection.ASCENDING);

次のクエリは、不等式フィルタで使われているプロパティを基準として並べ替えを行っていないため無効です。

Filter birthYearMinFilter =
    new FilterPredicate("birthYear", FilterOperator.GREATER_THAN_OR_EQUAL, minBirthYear);

// Not valid. Missing sort on birthYear.
Query q =
    new Query("Person")
        .setFilter(birthYearMinFilter)
        .addSort("lastName", SortDirection.ASCENDING);

同様に、次のクエリは不等式フィルタで使われているプロパティが最初に並べ替えられていないため無効です。

Filter birthYearMinFilter =
    new FilterPredicate("birthYear", FilterOperator.GREATER_THAN_OR_EQUAL, minBirthYear);

// Not valid. Sort on birthYear needs to be first.
Query q =
    new Query("Person")
        .setFilter(birthYearMinFilter)
        .addSort("lastName", SortDirection.ASCENDING)
        .addSort("birthYear", SortDirection.ASCENDING);

複数の値を持つプロパティは予期せぬ動作をする場合がある

同じプロパティに対して複数の値があるエンティティでは、クエリのフィルタや並べ替え順序に対する動作が予期しない意外なものになる場合があります。これはインデックス付けの仕組みによります。

特定のプロパティに対して複数の不等式フィルタが使われているクエリの場合、そのプロパティの個別の値の少なくとも 1 つがフィルタの「すべて」を満たす場合のみ、エンティティがクエリと一致するものとみなされます。たとえば、種類 Widget のエンティティのプロパティ x の値が 12 である場合、クエリと一致しません

Query q =
    new Query("Widget")
        .setFilter(
            CompositeFilterOperator.and(
                new FilterPredicate("x", FilterOperator.GREATER_THAN, 1),
                new FilterPredicate("x", FilterOperator.LESS_THAN, 2)));

エンティティの x 値は、それぞれいずれかのフィルタを満たしていますが、どちらも単体では両方のフィルタを満たしていません。これは等式フィルタには適用されない点に注意してください。たとえば、同じエンティティであれば、クエリ条件を満たします。

Query q =
    new Query("Widget")
        .setFilter(
            CompositeFilterOperator.and(
                new FilterPredicate("x", FilterOperator.EQUAL, 1),
                new FilterPredicate("x", FilterOperator.EQUAL, 2)));

エンティティの x 値は、個別では両方のフィルタ条件を満たすものがないことに注目してください。

NOT_EQUAL 演算子は、「値はそれ以外である」テストで機能します。たとえば、次のクエリをご覧ください。

Query q = new Query("Widget").setFilter(new FilterPredicate("x", FilterOperator.NOT_EQUAL, 1));

このクエリは、x の値が 1 以外のすべての Widget エンティティに一致します。

Java では次のようなクエリを使用することもできます。

Query q =
    new Query("Widget")
        .setFilter(
            CompositeFilterOperator.and(
                new FilterPredicate("x", FilterOperator.NOT_EQUAL, 1),
                new FilterPredicate("x", FilterOperator.NOT_EQUAL, 2)));

これは次のようになります。

x < 1 OR (x > 1 AND x < 2) OR x > 2

そのため、x の値が 123 である Widget エンティティは一致しますが、値が 12 のエンティティは一致しません。

同様に、複数の値を持つプロパティの並べ替え順序も通常とは異なります。このようなプロパティは一意の値ごとに 1 回しかインデックスに表れないため、インデックスに表れる最初の値によってエンティティの並べ替え順序が決定されます。

  • クエリ結果を昇順で並べ替えると、順序付けにはプロパティの最小値が使用されます。
  • クエリ結果を降順で並べ替えると、順序付けには最大値が使用されます。
  • その他の値は、並べ替え順序にも値の数にも影響しません。

これにより、プロパティ値 19 を持つエンティティが、昇順と降順の両方で、プロパティ値 4567 を持つエンティティよりも前に並べられるという、通常とは異なる並び順になります。

トランザクション内のクエリは祖先フィルタを含んでいなければならない

Datastore のトランザクションは、同じエンティティ グループ(共通の祖先を持つ子孫)に属するエンティティに対してのみ機能します。この制限を保持するには、トランザクション内で実行されすべてのクエリは、そのトランザクション内の他のオペレーションと同じエンティティ グループの祖先を指定する祖先フィルタを含んでいる必要があります。