射影クエリ

ほとんどの Datastore クエリでは、エンティティ全体が結果として返されますが、多くの場合、アプリケーションが実際に必要としているのはエンティティの一部のプロパティのみです。射影クエリを使用すると、エンティティのプロパティの中で本当に必要なものだけをクエリできるため、エンティティ全体を取得する場合よりもレイテンシとコストを低く抑えられます。

射影クエリは、次のような形式の SQL クエリと似ています。

SELECT name, email, phone FROM CUSTOMER

標準的なエンティティ クエリで使用できるフィルタリングや並べ替えの機能はすべて使用できます。制限事項については、以降で説明するとおりです。このクエリでは要約された結果が返されます。指定したプロパティ(この例では nameemailphone)だけに値が格納され、その他すべてのプロパティにはデータは格納されません。

Python 2 での射影クエリの使用

次のモデルを考えてみましょう。

class Article(ndb.Model):
    title = ndb.StringProperty()
    author = ndb.StringProperty()
    tags = ndb.StringProperty(repeated=True)

projection は次のように指定します。

def print_author_tags():
    query = Article.query()
    articles = query.fetch(20, projection=[Article.author, Article.tags])
    for article in articles:
        print(article.author)
        print(article.tags)
        # article.title will raise a ndb.UnprojectedPropertyError

これらのクエリの結果は、標準的なエンティティのクエリの場合と同じように扱います(結果を反復処理する、など)。

for article in articles:
        print(article.author)
        print(article.tags)
        # article.title will raise a ndb.UnprojectedPropertyError

構造化プロパティでは、インデックス化されたサブプロパティを射影できます。連絡先の addresscity プロパティのみを取得する場合、次のような射影を利用できます。

class Address(ndb.Model):
    type = ndb.StringProperty()  # E.g., 'home', 'work'
    street = ndb.StringProperty()
    city = ndb.StringProperty()
...
class Contact(ndb.Model):
    name = ndb.StringProperty()
    addresses = ndb.StructuredProperty(Address, repeated=True)
...
Contact.query().fetch(projection=["name", "addresses.city"])
Contact.query().fetch(projection=[Contact.name, Contact.addresses.city])

グループ化(試験運用版)

射影クエリで distinct メソッドを使用すると、完全に一意の結果のみが結果セットで返されます。つまり、射影されているプロパティについて同じ値を持つエンティティが複数存在する場合は、最初の結果だけが返されます。

Article.query(projection=[Article.author], group_by=[Article.author])
Article.query(projection=[Article.author], distinct=True)

どちらのクエリも同等で、各作成者の名前が 1 回だけ生成されます。

射影の制限事項

射影クエリには次のような制限事項があります。

  • 射影できるのはインデックス付けされたプロパティのみ。

    インデックスに明示的にも暗黙的にも登録されていないプロパティでは、射影はサポートされません。長いテキスト文字列(Text)と長いバイト文字列(Blob)はインデックスに登録されません。

  • 同じプロパティは複数回射影できない。

  • 等式(=)またはメンバー(IN)フィルタで参照されているプロパティは射影できない。

    次に例を示します。

    SELECT A FROM kind WHERE B = 1
    

    は有効です(射影されたプロパティが等式フィルタで使用されていないため)。同様に、

    SELECT A FROM kind WHERE A > 1
    

    も有効です(等式フィルタではないため)。ただし、

    SELECT A FROM kind WHERE A = 1
    

    は無効です(射影されたプロパティが等式フィルタで使用されているため)。

  • 射影クエリから返された結果を Datastore に再保存することはできない。

    このようなクエリでは部分的に格納された結果しか返されないため、Datastore に書き戻すことはできません。

射影と複数の値を持つプロパティ

複数の値を持つプロパティを射影した場合は、そのプロパティのすべての値が格納されるのではなく、クエリに一致する、射影された値の一意の組み合わせごとに個別のエンティティが返されます。たとえば、複数の値を持つ 2 つのプロパティ AB が設定された Foo という種類のエンティティがあるとします。

entity = Foo(A=[1, 1, 2, 3], B=['x', 'y', 'x'])

この状況で、次の射影クエリを実行します。

SELECT A, B FROM Foo WHERE A < 3

この場合、次の値の組み合わせを持つ 4 つのエンティティが返されます。

A = 1B = 'x'
A = 1B = 'y'
A = 2B = 'x'
A = 2B = 'y'

値が指定されていない複数値プロパティがエンティティに含まれている場合、そのプロパティのエントリはどれもインデックスに取り込まれません。したがって、こうしたプロパティを含む射影クエリからは、エンティティに対する結果が返されません。

射影のインデックス

射影クエリでは、射影で指定されているすべてのプロパティが Datastore のインデックスに含まれるようにする必要があります。App Engine 開発用サーバーを使用すると、必要なインデックスがインデックス構成ファイル(index.yaml)内に自動生成されます。このファイルはアプリケーションと一緒にアップロードされます。

必要なインデックスの数を最小限に抑える方法の 1 つとして、必ずしもすべてが必要でない場合であっても、同じ複数のプロパティを一貫して射影する方法があります。たとえば、次のクエリはそれぞれ個別のインデックスを必要とします。

SELECT A, B FROM Kind
SELECT A, B, C FROM Kind

ただし、C が必要でないときでもプロパティ ABC を常に射影すれば、必要となるインデックスは 1 つのみとなります。

射影のプロパティがクエリの別の部分に含まれていない場合、既存のクエリを射影クエリに変換する際に新しいインデックスを構築しなければならない場合があります。たとえば、次のような既存のクエリがあるとします。

SELECT * FROM Kind WHERE A > 1 ORDER BY A, B

必要なインデックスは次のとおりです。

Index(Kind, A, B)

このクエリを次のいずれかの射影クエリに変換します。

SELECT C FROM Kind WHERE A > 1 ORDER BY A, B
SELECT A, B, C FROM Kind WHERE A > 1 ORDER BY A, B

新しいプロパティ(C)が取り入れられるため、新しいインデックス Index(Kind, A, B, C) を作成する必要があります。次の射影クエリ

SELECT A, B FROM Kind WHERE A > 1 ORDER BY A, B

では、必要なインデックスに変化はありません。これは射影される A プロパティと B プロパティが既存のクエリにすでに含まれているためです。