お気に入り | 日本語 | ログイン

クエリとインデックス

すべてのデータストア クエリはインデックスを使用します。インデックスは、希望する順に並べられたクエリ結果を含むテーブルです。App Engine アプリケーションは、datastore-indexes.xml という名前の設定ファイルでインデックスを定義します。 開発用 Web サーバーは、インデックスが設定されていないクエリが発生すると、このファイルに自動的に候補を生成できます。

インデックス ベースのクエリ メカニズムは、ほとんどの一般的な種類のクエリに対応していますが、他のデータベース技術の慣れ親しんだクエリの中には対応していないものもあります。クエリの制限とその説明は次のとおりです。

クエリの概要

クエリは条件のセットに一致するエンティティをデータストアから取得します。クエリはエンティティの種類、エンティティのプロパティ値に基づいたゼロ以上の条件(「フィルタ」と呼ばれることもあります) とゼロ以上の並び替え順序の記述を指定します。クエリは実行されると、指定された種類で、指定されたすべての条件に一致するエンティティをすべてフェッチし、記述された順序に並び替えます。

JDO は、特定の条件を満たすエンティティへのクエリを実行できます。また、JDO Extent を使用し、種類のすべてのエンティティのコレクション(あるクラスの、保存されたすべてのオブジェクト)を表すことができます。

JDOQL を使用したクエリ

JDO には一連の条件を満たすオブジェクトを取得するためのクエリ言語が含まれます。JDOQL と呼ばれるこの言語は、JDO データ クラスとフィールドを直接参照することができます。また、クエリ パラメータと結果の種類のチェック機能も備えています。JDOQL は SQL に似ていますが、App Engine データストアのようなオブジェクト指向のデータベースには SQL よりも適しています(App Engine データストアは、JDO インターフェースでの SQL クエリはサポートしていません)。

クエリ API は複数のコール スタイルをサポートしています。JDOQL 文字列の構文を使用すれば、文字列の中に完全なクエリを記述することができます。また、クエリ オブジェクトのメソッドをコールし、クエリの一部またはすべてを指定できます。

メソッド スタイルのコールを使用したクエリの簡単な例を紹介します。この例では、フィルタで使用される値に対してパラメータの置き換えを行い、フィルタを 1 つ、並び替え順序を 1 つ指定します。クエリ オブジェクトの execute() メソッドは、クエリにおいて代用される値と共に宣言順にコールされます。

import java.util.List;
import javax.jdo.Query;

// ...

    Query query = pm.newQuery(Employee.class);
    query.setFilter("lastName == lastNameParam");
    query.setOrdering("hireDate desc");
    query.declareParameters("String lastNameParam");

    try {
        List<Employee> results = (List<Employee>) query.execute("Smith");
        if (results.iterator().hasNext()) {
            for (Employee e : results) {
                // ...
            }
        } else {
            // ... no results ...
        }
    } finally {
        query.closeAll();
    }

文字列構文を使用した同一のクエリを示します。

    Query query = pm.newQuery("select from Employee " +
                              "where lastName == lastNameParam " +
                              "order by hireDate desc " +
                              "parameters String lastNameParam")

    List<Employee> results = (List<Employee>) query.execute("Smith");

クエリの定義ではこれらのスタイルを混在させることができます。次に例を示します。

    Query query = pm.newQuery(Employee.class,
                              "lastName == lastNameParam order by hireDate desc");
    query.declareParameters("String lastNameParam");

    List<Employee> results = (List<Employee>) query.execute("Smith");

execute() メソッドを複数回コールすることで、単一のクエリ インスタンスのパラメータに対して異なる値を代用して再利用できます。各コールはクエリを実行し、結果をコレクションとして返します。

JDOQL 字列構文は、文字列の中のリテラル値を文字列値または数値としてサポートしています。文字列をシングル クォート(')またはダブルクォート(")のいずれかで囲みます。すべての値型はパラメータ代用を使用しなければなりません。文字列のリテラル値を使用した例を次に示します。

    Query query = pm.newQuery(Employee.class,
                              "lastName == 'Smith' order by hireDate desc");

クエリ フィルタ

フィルタはフィールド名、演算子および値を指定します。値はアプリケーションによって提供される必要があります。他のプロパティを参照したり、他のプロパティに基づいて演算したりすることはできません。演算子には次のいずれかを指定できます。< <= == >= >

注: Java データストア インターフェースは、Python データストア インターフェースに実装されている != および IN フィルタ演算子はサポートしていません。(Python インターフェースでは、これらの演算子は複数のデータストアのクエリとしてクライアント側のライブラリに実装されています。データストア自体に実装された機能ではありません。)

フィルタの対象には、主キーやエンティティ グループの親も含め、どのようなオブジェクト フィールドも使用できます(トランザクションをご覧ください)。

すべてのフィルタを満たしたエンティティのみが結果として返されます。JDOQL 文字列構文では、&&(論理式の「and」)で区切られた複数のフィルタが指定されています。フィルタのその他の論理式の組み合わせ(論理式の「or」および「not」)はサポートしていません。

App Engine データストアによるクエリの実行方法のため、単一のクエリでは 1 つのプロパティでしか不等式フィルタ(< <= >= >)を使用できません。同一のプロパティで複数の不等式フィルタを使用(範囲を指定したクエリなど)することは可能です。クエリに対する制限をご覧ください。

    query.setFilter("lastName == 'Smith' && hireDate > hireDateMinimum");
    query.declareParameters("Date hireDateMinimum");

クエリの並び替え順序

並び替え順序では、プロパティおよび昇順または降順を指定します。返される結果は、指定した順序で並び替えられています。クエリの並び替え順序が指定されていない場合、結果はエンティティ キー順となります。

クエリのプロパティに不等式フィルタを適用し、他のプロパティに並び替え順序を指定した場合、不等式で使用されたプロパティは他のプロパティより優先して並び替えられる必要があります。これは、App Engine データストアがクエリを実行する順序が原因です。クエリに対する制限をご覧ください。

    query.setOrdering("hireDate desc, firstName asc");

クエリの範囲

クエリでは、アプリケーションに返す結果の範囲を指定できます。範囲では、完全な結果セットの内最初に返す結果から最後に返す結果までを、数値インデックスで指定します。最初の結果のインデックスは 0 となります。たとえば、5, 10 の範囲は 6、7、8、9、10 番目の結果を返します。

開始オフセットはパフォーマンスに影響します。データストアは、開始オフセットより前のすべての結果を取得し、破棄する必要があるからです。たとえば、範囲が 5, 10 のクエリでは、データストアから 10 件の結果をフェッチし、最初の 5 つを破棄し残りの 5 つをアプリケーションに返します。

    query.setRange(5, 10);

Extent

JDO Extent は、データストア内にある特定のクラスのすべてのオブジェクトを表します。

PersistenceManager の getExtent() メソッドから Extent を開始し、データ クラスへと渡します。Extent クラスは、結果へのアクセスに Iterable インターフェースを実装しています。結果アクセスの完了後は、closeAll() メソッドをコールします。

次に、データストア内のすべての Employee オブジェクトを繰り返し処理する例を示します。

import java.util.Iterator;
import javax.jdo.Extent;

// ...

    Extent extent = pm.getExtent(Employee.class, false);
    for (Employee e : extent) {
        // ...
    }
    extent.closeAll();

エクステントは結果をバッチで取得するため、クエリ結果に適用される 1,000 行という制限を超えて結果を取得できます。

インデックスの概要

App Engine データストアは、アプリケーションが作成しようとするすべてのクエリについてインデックスを保持します。アプリケーションがデータストア エンティティに変更を加えると、データストアは正しい結果でインデックスを更新します。アプリケーションがクエリを実行すると、データストアは対応するインデックスから直接結果をフェッチします。

アプリケーションは、クエリに使用される種類、フィルタ プロパティおよび演算子、並び替え順序の各組み合わせのインデックスを持ちます。JDOQL で記述されたクエリ例をご覧ください。

select from Person where lastName == "Smith"
                      && height < 72
                order by height desc

このクエリのインデックスは Person の種類のエンティティ用のキーのテーブルで、height 値と lastName プロパティの入る列を持っています。このインデックスは、height を基準にして降順に並び替えられます。

同じ形式でフィルタ値の異なる 2 つのクエリは同じインデックスを使用します。たとえば、次のクエリは上記のクエリと同じインデックスを使用します:

select from Person where lastName == "Jones"
                      && height < 64
                order by height desc

データストアは次の手順に従ってクエリを実行します:

  1. データストアはクエリの種類、フィルタ プロパティ、フィルタ演算子、並び替え順序に対応するインデックスを特定します。
  2. データストアはクエリのフィルタ値を使用して、すべてのフィルタ条件に一致する最初のエンティティで、インデックスのスキャンを開始します。
  3. データストアは各エンティティを返しながら、次のエンティティがフィルタ条件を満たさなくなるまで、または、インデックスの最後に到達するまで、またはクエリが要求した最大結果件数に到達するまで、インデックスのスキャンを続行します。

インデックス テーブルには、フィルタまたは並び替え順序に使用されるすべてのプロパティのための列が含まれます。行は次のアスペクトを基準にして順番に並び替えられます。

  • 祖先
  • 等式フィルタで使用されるプロパティ値
  • 不等式フィルタで使用されるプロパティ値
  • 並び替え順序で使用されるプロパティ値

これにより、このインデックスを使用する、すべての可能なクエリのすべての結果がテーブル内の連続した行に置かれます。

このメカニズムは広範囲のクエリをサポートしており、ほとんどのアプリケーションに適しています。しかし、他のデータベース技術の慣れ親しんだクエリの中には対応していないものもあります。

フィルタ プロパティを持たないエンティティはクエリでは返されない

インデックスに含まれるのは、すべてのプロパティがインデックスによって参照されるエンティティに限られます。エンティティがインデックスによって参照されるプロパティを持たない場合、エンティティはインデックスに出現せず、インデックスを使用するクエリの結果となることはありません。

App Engine データストアは、プロパティを持たないエンティティと null 値のプロパティを持つエンティティ(null)を区別するので注意してください。種類のすべてのエンティティがクエリの結果に含むことができるようにするために、 JDO データ クラスまたは JPA データ クラスを使用すれば、クラス内のフィールドに対応するすべてのプロパティに常に値を割り当てることができます。

Text 値と Blob 値はインデックスされない

長いテキスト型(Text)または長いバイナリ型(Blob)の値を持つプロパティはインデックスには含まれないため、クエリで検索できません。

これらのプロパティ値をインデックスしない結果として、プロパティにフィルタまたは並び替え順序を持つクエリが、プロパティに Text 値または Blob 値を持つエンティティに一致することはありません。このような値を持つプロパティは、プロパティにクエリ フィルタまたは並び替え順序が設定されていないような動作をします。

混在した型のプロパティ値を型で並び替える

2 つのエンティティが同じ名前で異なる値型のプロパティを持つ場合、プロパティのインデックスは、エンティティをまず値型で並び替え、続いて型の適切な順序で並び替えます。たとえば、2 つのエンティティが「age」というプロパティをそれぞれ持ち、1 つが整数値、もう 1 つが文字列値である場合、「age」プロパティで並び替えると、値自体に関係なく、整数値のエンティティが常に文字列値のエンティティより前に並べられます。

データストアで別の型として認識される整数値と浮動小数点値の場合には特に注意が必要です。すべての整数値は浮動小数点値より前に並べられるため、整数値 38 を持つプロパティは、浮動小数点値 37.5 のプロパティよりも前の順序となります

(JDO または JPA を使用している場合、データストアの既存のエンティティを更新することなくフィールド型を変更しない限り、または低レベルのデータストア API または非 Java API を使用していない限り、この状況は発生しません)。

インデックスの定義と設定

App Engine はデフォルトでいくつかのシンプルなクエリのインデックスを構築します。その他のクエリに対しては、アプリケーションは必要なインデックスを datastore-indexes.xml という名前の設定ファイルに指定する必要があります。App Engine で実行しているアプリケーションが、(デフォルトによって提供されるか datastore-indexes.xml で記述されるかした)対応するインデックスがないクエリを実行しようとすると、クエリは失敗となります。

App Engine は次の形式のクエリに自動的にインデックスを提供します:

  • 等式、および祖先フィルタのみを使用するクエリ
  • 不等式フィルタのみを使用するクエリ(単一のプロパティを持つものに限られます)
  • プロパティに昇順または降順のいずれかの並び替え順序が 1 つだけ設定されているクエリ

次のような他の形式のクエリはインデックスが datastore-indexes.xml で指定されている必要があります。

  • 複数の並び替え順序を持つクエリ
  • キーに降順の並び替え順序の指定されているクエリ
  • 1 つのプロパティに対して 1 つ以上の不等式フィルタを持ち、その他のクエリに対して 1 つ以上の等式フィルタを持つクエリ
  • 不等式フィルタと祖先フィルタを持つクエリ

開発 Web サーバーを使用している場合、インデックス設定は容易に実行できます。必要なインデックスが存在しないクエリを実行すると、操作は失敗しませんが、逆に開発 Web サーバーがクエリを成功させるために必要なインデックス設定を生成します。アプリケーションのローカル テスト時に、アプリケーションが作成する可能性のあるクエリ(種類、祖先、フィルタ、並び替え順序のすべての組み合わせ)をすべて呼び出す場合、生成されたエンティティはインデックスの完全なセットを表します。テストですべての可能性のあるクエリ形式を実行しない場合、アプリケーションをアップロードする前にインデックス設定を見直し、調整することもできます。

アプリケーションの WAR に含まれる WEB-INF/ ディレクトリの datastore-indexes.xml ファイルを使用し、インデックスを手動で定義できます。自動インデックス設定(下記参照)が有効になっている場合、開発サーバーは WEB-INF/appengine-generated/ ディレクトリの datastore-indexes-auto.xml という名前のファイルにインデックス設定を作成し、インデックスの完全なセットの定義において両方のファイルを使用します。

次のクエリの例をもう一度考えてみましょう。

select from Person where lastName = 'Smith'
                      && height < 72
                   order by height desc

このクエリで必要なインデックスの設定は、datastore-indexes.xml に次のように表示されます。

<?xml version="1.0" encoding="utf-8"?>
<datastore-indexes
  xmlns="http://appengine.google.com/ns/datastore-indexes/1.0"
  autoGenerate="true">
    <datastore-index kind="Person" ancestor="false">
        <property name="lastName" direction="asc" />
        <property name="height" direction="desc" />
    </datastore-index>
</datastore-indexes>

datastore-indexes.xml<datastore-indexes> 要素に属性 autoGenerate="true"(上記を参照)が含まれている場合、またはアプリケーションに datastore-indexes.xml ファイルが存在しない場合、自動インデックス設定が有効となります。自動インデックス設定が有効である場合、アプリケーションが開発サーバーでクエリを実行する際にインデックス設定が存在しないと、サーバーがこのインデックス設定を datastore-indexes-auto.xml ファイルに追加します。

datastore-indexes.xmldatastore-indexes-auto.xml の詳細については、Java データストア インデックス設定をご覧ください。

キーに対するクエリ

エンティティ キーは、クエリ フィルタまたは並び替え順序として使用できます。JDO のクエリからエンティティ キーを参照するには、オブジェクトの主キー フィールドを使用します。データストアは、エンティティの親パス、種類、アプリケーションが割り当てたキー名の文字列またはシステムが割り当てた数値 ID など、クエリにおける完全なキー値を考慮します。

エンティティ キーはシステム全体のエンティティにおいて一意であるため、キーに対するクエリは、データストアのコンテンツのバッチ ダンプなど、指定された種類のバッチのエンティティも容易に取得できます。JDOQL の範囲とは異なり、キーに対するクエリは任意の数のエンティティに対して効率的に動作します。

キーはまず親パスで並び替えられ、続いて種類、そしてキー名または ID の順序に従います。種類とキー名は文字列で、バイト値によって並び替えられます。ID は整数値で数値順に並び替えられます。同じ親と種類を持つエンティティが、キー名の文字列と数値 ID を組み合わせて使用している場合、数値 ID を使用しているエンティティの方が、キー名文字列を使用しているエンティティより前であると判断されます。親パスの要素も同様に、種類(文字列)、キー名(文字列)、ID(数値)で比較されます。

キーを含むクエリは、プロパティを含むクエリと同様にインデックスを使用しますが、1 つだけ違う点があります。プロパティとは異なり、キーに対する等式フィルタと同時にそれ以外のフィルタを含むクエリは、アプリケーションのインデックス定義ファイルに含まれるカスタム インデックスを使用する必要があります。すべてのクエリに対する場合と同様に、カスタム インデックスを必要とするクエリのテストが行われた場合、開発 Web サーバーはこのファイルに適切な設定エントリを作成します。

クエリに対する制限

インデックス クエリのメカニズムの性質により、クエリは多少の制限を受けます。

プロパティをフィルタまたは並び替えするには、プロパティが存在することが必要

クエリ フィルタ条件やプロパティの並び替え順序は、エンティティがプロパティ値を持っているという条件が前提になっています。

データストア エンティティには、同じ種類のエンティティが持つプロパティ値は必要ではありません。プロパティのフィルタはプロパティ値を持つエンティティにのみ一致できます。プロパティ値を持たないエンティティで、フィルタまたは並び替え順序に使用されているエンティティはクエリ用に構築されるインデックスから省略されます。

プロパティを持たないエンティティに一致するフィルタはない

指定されたプロパティがないエンティティにクエリは実行できません。1 つの代替方法は、null のデフォルト値を持つ固定(モデル化された)プロパティを作成し、null をプロパティ値として持つエンティティに対するフィルタを作成することです。

不等式フィルタが使用できるのは 1 つのプロパティに限られる

クエリが不等式フィルタ(<<=>=>)を使用できるのは、すべてのフィルタにわたって 1 つのプロパティに限られます。

たとえば、このクエリは記述できます。

select from Person where birthYear >= minBirthYearParam
                      && birthYear <= maxBirthYearParam

しかし、このクエリは、同じクエリ内で 2 つの異なったプロパティに不等式フィルタを使用しているため、使用できません。

select from Person where birthYear >= minBirthYearParam
                      && height >= minHeightParam   // ERROR

フィルタは、同じクエリ内の異なったプロパティに対して、1 つ以上の不等式条件を持つクエリを含め、等号(==)比較を組み合わせても構いません。これは使用可能です。

select from Person where lastName == lastNameParam
                      && city == cityParam
                      && birthYear >= minBirthYearParam

クエリ メカニズムは、結果を得るためにテーブル全体をスキャンする必要のないように、クエリのすべての結果がインデックス テーブル内で隣接し合うことで成り立っています。単一のインデックス テーブルは、すべての結果をテーブル内で連続させながら、複数のプロパティに対する複数の不等式フィルタを表すことはできません。

他の並び替え順序より先に、不等式フィルタのプロパティを並び替える必要がある

クエリに不等式比較のフィルタと 1 つ以上の並び替え順序がある場合、クエリは不等式に使用されるプロパティの並び替え順序を含んでいる必要があり、この並び替え順序は他のプロパティに対する並び替え順序より先に出現する必要があります。

このクエリは不等式フィルタを使用しており、また、フィルタされたプロパティ順序で並び替えられないので、有効ではありません。

select from Person where birthYear >= minBirthYearParam
                order by lastName                    // ERROR

同様に、その他の順序で並び替えられるより先に、フィルタされたプロパティ順序に並び替えられていないので、この クエリも有効ではありません。

select from Person where birthYear > minBirthYearParam
                order by lastName, birthYear         // ERROR

このクエリは有効です。

select from Person where birthYear >= minBirthYearParam
                order by birthYear, lastName

不等式フィルタに一致するすべての結果を得るために、クエリはインデックス テーブルの最初に一致する行をスキャンし、次に、一致しない行が見つかるまで、連続するすべての結果を返します。行が完全な結果のセットを表すためには、行は他の並び替え順序より先に不等式フィルタで並び替えられている必要があります。

複数の値での並び替え順序とプロパティ

複数の値のプロパティのインデックス方法は通常とは異なるため、これらのプロパティの並び替え順序は通常とは異なります。

  • エンティティが複数の値のプロパティを基準にして昇順に並び替えられる場合、並び替えに使用される値は最小値になります。
  • エンティティが複数の値のプロパティを基準にして降順に並び替えられる場合、並び替えに使用される値は最も大きな値になります。
  • その他の値は並び替え順序や値の数に影響しません。
  • 要素の大きさが同じ場合、エンティティのキーはタイブレーカとして使用されます。

この並び替え順序は通常とは異なった結果となり、昇順および降順ともに [1, 9][4, 5, 6, 7] より先になります。

複数の値のプロパティに等式フィルタと並び替え順序の両方を指定したクエリには、十分に注意する必要があります。これらのクエリでは、並び替え順序は無視されます。単一の値のプロパティの場合、これはシンプルな最適化になります。すべての結果はプロパティに対して同じ値を持つことになり、そのため、結果はそれ以上並び替える必要がありません。

しかし、複数の値のプロパティでは追加的な値を持つ場合があります。並び替え順序が無視されるため、クエリ結果は並び替え順序が適用された場合とは異なった順序で返される可能性があります(無視された並び替え順序の保存は負荷が高く、余分なインデックスを必要としますが、めったにないケースなので、クエリの計画者はこれを省略します)。

大きなエンティティとエクスプローディング インデックス

上記で説明したように、すべてのエンティティのすべてのプロパティ(Text または Blob 値を持つものは例外)はデフォルトで提供されるシンプルなインデックスも含めて、少なくとも 1 つのインデックス テーブルに追加されます。プロパティを参照するアプリケーションの ファイルに記述されたインデックスdatastore-indexes.xml があればそれにも追加されます。各プロパティに 1 つの値を持つエンティティについては、App Engine はシンプルなインデックスにプロパティ値をいったん格納し、プロパティがカスタム インデックスで参照されるたびに 1 回ずつ格納します。これらのインデックス エンティティはプロパティ値が変更されるたびに更新される必要があるので、プロパティを参照するインデックスが多ければ多いほど、プロパティの更新に要する時間が長くなります。

エンティティの更新にあまり長い時間がかからないように、データストアは単一のエンティティが持つことのできるインデックス数を制限しています。制限数は大きいものなので、ほとんどのアプリケーションには影響ありません。しかし、制限に達する場合もあります。たとえば、非常に多くの単一値を持つプロパティを所有するエンティティはインデックスのエントリ制限を超えることがあります。

複数の値のプロパティは、各値をインデックスの別のエントリとして格納します。非常に多くの値を持つ単一プロパティを所有するエンティティはインデックスのエントリ制限を超えることがあります。

複数の値を持つ複数のプロパティを参照するカスタム インデックスは、値の数が少なくても非常に大きなものとなることがあります。こうしたプロパティを完全に記録するには、インデックス テーブルはインデックスのすべてのプロパティの、値のすべての置換のための行を含む必要があります。

たとえば、次のインデックス(datastore-indexes.xml シンタックスで記述されたもの)はエンティティの MyModel の種類の x プロパティおよび y プロパティを含んでいます。

<?xml version="1.0" encoding="utf-8"?>
<datastore-indexes>
    <datastore-index kind="MyModel">
        <property name="x" direction="asc" />
        <property name="y" direction="asc" />
    </datastore-index>
</datastore-indexes>

次のコードはプロパティ x に対して 2 値のプロパティを持つエンティティとプロパティ y に対して 2 値のプロパティを持つエンティティを作成します。

        MyModel m = new MyModel();

        m.setX(Arrays.asList("one", "two"));
        m.setY(Arrays.asList("three", "four"));

        pm.makePersistent(m);

この値を正確に表すには、インデックスは 12 個のプロパティ値を格納する必要があります。xy に組み込みのインデックスに 2 つずつ、カスタム インデックスの xy の 4 つの置換それぞれに 2 つずつです。複数の値のプロパティに多くのリスト値がある場合、インデックスは単一のエンティティに対して非常に多くのインデックス エンティティを格納しなければならないことになります。複数の値を持つ複数のプロパティを参照するインデックスは「エクスプローディング インデックス」と呼ばれることがあります。値の数が少なくても非常に大きなものとなることがあるからです。

put() の結果がインデックス数のエントリ制限を超えると、呼び出しは例外となり失敗します。構築された時点でエンティティのインデックス数がエントリ制限を超える新しいインデックスを作成すると、インデックスに対するクエリは失敗し、インデックスは管理コンソールで「Error」状態として表示されます。

リスト プロパティを使用する、カスタム インデックスを必要とするクエリを使用しないことで、エクスプローディング インデックスを回避できます。上記で説明したように、これには複数の並び替え順序、等式と不等式が混合したフィルタ、祖先フィルタを持つクエリが含まれます。