The Round

合同会社ナイツオの開発ブログ

[PR] 5分から相談できるGCP™ 開発コンサル!→こちら

GAE/Go Datastoreの基本的なクエリ

注:古い記事の為、内容が最新ではない可能性がありますm(_ _)m

どうもこんにちわ!マツウラです。
前回のDatastoreのエンティティ操作に引き続き、今回はQueryについてGoでの基本的な取り扱い方を見ていきます。

参考:Go — Google Developers Datastore Queries

Query

次のコードはGoでのクエリを使用した一般的な例です。
Filter(),Order()といった下記で説明している機能も使用しています。
クエリの結果として取得されるエンティティは、GetAll()に渡したスライスに格納されます。

c := appengine.NewContext(r)
q := datastore.NewQuery("Person").Filter("LastName =", "Voski").Filter("Height <=", 200).Order("-Height")

var people []Person
keys, err := q.GetAll(c, &people)

それでは次にこの例で使用された各種メソッドについて見てゆきます。

抽出条件を定義する

クエリでどのエンティティを抽出するのか、その条件を定義するにはQueryの各種メソッドを使用します。 次は条件付けを行う際に主に使用するメソッド類です。

フィルタを付与したクエリを生成するにはFilter()メソッドを使用します。
このフィルタは構造体のフィールドを対象としたものです。

func (q *Query) Filter(filterStr string, value interface{}) *Query

filterStrはフィールド名である必要があり、スペースに続けて>,<,>=,<=,=のいずれか一つの演算子が続きます。

次にソート順の定義です。
クエリにソート順を定義するにはOrder()メソッドにフィールド名を渡します。

func (q *Query) Order(fieldName string) *Query

Goではソート順はプロパティ名の前にあるハイフン(-)で示します。
このハイフンを省略すると昇順、ハイフンを付けると降順となります。

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

スライスのように複数の値を単一プロパティ持つものをソートする場合、次のように結果がソートされます。

  • 昇順ソートを使用する場合、プロパティの最小値がソート順を決定するために使用される。
  • 降順ソートを使用する場合、プロパティの最大値がソート順を決定するために使用される。
  • 他の値はソート順に影響を与えず、値の数の大小も関係ありません。

このような基準があるためスライスで値をもつプロパティについてソートを行う場合は、注意して下さい。

次はKeyのみを対象にするkeyフィルタです。
keyに対してフィルタを適用するには、__key__プロパティを使用します。

q := datastore.NewQuery("Person").Filter("__key__ =", key)

最後に特定の祖先をもつ子エンティティを取得するため使用する、祖先フィルタです。

func (q *Query) Ancestor(ancestor *Key) *Query

Ancestorメソッドを実際に使用した例が次のコードです。
対象となるエンティティのKeyを渡します。

q := datastore.NewQuery("Person").Ancestor(ancestorKey)

結果の取得

次に、これら条件付けを行い生成したクエリを実行するメソッドです。

func (q *Query) GetAll(c appengine.Context, dst interface{}) ([]*Key, error)

指定されたコンテキストでクエリを実行し、クエリと一致したすべてのkeyを返します。
取得したエンティティについてはdstに値が追加されます。
戻り値として返るのはKeyとエラーですので注意して下さい。

q := datastore.NewQuery("Person")
var people []Person
keys, err := q.GetAll(c, &people)

結果を反復処理する場合は、Runメソッドにてクエリを実行します。
返り値としてイテレータが返されます。

func (q *Query) Run(c appengine.Context) *Iterator

Runメソッドで結果を反復処理する際、バッチで結果を取得します。   デフォルトでは各バッチに20個の結果が含まれています。
すべての結果が返されるか、リクエストがタイムアウトするまで反復処理を継続することが可能です。

そして返されたイテレータのNextメソッドを使うことでステップ処理が可能になります。
次はRunによる反復処理の例です。

q := datastore.NewQuery("Person")
iter := q.Run(c)
for {
    var p Person
    _, err := iter.Next(&p)
    if err == datastore.Done {
        break
    }
    if err != nil {
        c.Errorf("fetching next Person: %v", err)
        break
    }
    fmt.Fprintf(w, "%+v\n", p)
}
func (t *Iterator) Next(dst interface{}) (*Key, error)

取得する結果のkeyを返します。(エンティティはdstに格納されます)
これ以上結果がない場合はDoneがerrorとして返されます。

インデックス

どのクエリでも1つ以上のインデックスを使い結果を計算するのでインデックスがなければクエリは動きません。
通常、データストアは各プロパティのインデックスを自動的に事前定義します。
これらの事前定義されたインデックスは多くのシンプルなクエリを実行するには十分です。(=、<, >)
その他のクエリでは、index.yamlでインデックス定義が必要です。
仮に単純な=<>以外のクエリを有効なインデックスで実行しても、クエリは失敗するでしょう。(事前定義、またはindex.yamlで指定されたインデックスを用いても。)

データストは次の形式のクエリで自動的にインデックスを構築します。

  • 種類無しのクエリで祖先とkeyフィルタのみを使用する。
  • 祖先と等価フィルタのみを使用する。
  • 不等式フィルタのみを使用する。(単一プロパティの制限)
  • 祖先フィルタ、プロパティでの等価フィルタ、キーでの不等式フィルタのみを使用する。
  • フィルタ無しでプロパティに昇順または降順のソートを1つ使用する。

その他に次のようなクエリ形式ではindex.yamlでインデックスを指定する必要があります。

  • 祖先と不等式フィルタを使用する。
  • プロパティでの1つ以上の不等式フィルタを使用し、その他の1つ以上のプロパティでの等価フィルタを使用する。
  • keyで降順ソートを使用する。
  • 複数のソート順を使用する。

Note: 開発サーバーはindex.yamlの管理をより簡単に行うことができます。
クエリの実行に必要なインデックスが無いと、クエリが成功するようにインデックス設定を生成してくれます。
もしアプリケーションのローカルテストにおいて、フィルタやソートの全ての組み合わせを使用したすべての有効なクエリを行った場合、生成されたindex.yamlは完全なインデックスのセットになります。

結果数を制限する

指定した数だけ結果を取得するようにするにはLimit()メソッドに整数を渡します。

func (q *Query) Limit(limit int) *Query

結果をスキップする

指定した数だけ結果をスキップするにはOffset()メソッドに整数を渡します。
次の例では背の高い人を5人とばし、6~10番目に背の高い人を返します。

q := datastore.NewQuery("Person").Order("-Height").Limit(5).Offset(5)

Note: offsetを使用すると、スキップしたエンティティはアプリケーションに返されませんが内部的には取得されています。
スキップされたエンティティはクエリの待ち時間に影響し、取得に必要な読み取り操作に対しての請求も行われます。
これらのコストを避けるには、代わりにクエリカーソルを使用します。

Keyのみを取得する

KeysOnly()メソッドを使用するクエリは、エンティティの代わりにそのkeyを取得します。
これはエンティティ全体を取得するよりも低遅延かつ低コストです。

q := datastore.NewQuery("Person").KeysOnly()

特殊なクエリ

通常のクエリの他に、特殊な動作をするクエリがあります。

種類無しのクエリ

種類と祖先フィルタ無しのクエリは全てのエンティティを取得します。

これは統計エンティティ、BlobstoreメタエンティティのようなApp Engineの機能により作成、管理されるエンティティも含まれます。

このようなクエリにはプロパティ値にフィルタやソート順を含めることは出来ません。
しかし、__key__を指定することでkeyをフィルタリングすることは出来ます。

q := datastore.NewQuery("")
// q := datastore.NewQuery("").Filter("__key__ >", key)

ちなみに不等号で比較する際、keyは次の基準の順番によって順序付けされます。

  • 祖先パス
  • エンティティの種類
  • 識別子(key名、数値ID)

種類無しの祖先クエリ

祖先フィルタを含む種類無しクエリは、指定した祖先とその子孫のすべてを種類に関係なく取得します。

このタイプのクエリではカスタムインデックスを必要としません。   すべての種類なしクエリ同様に、フィルタを含むこと、プロパティ値でソートすることは出来ません。 これもエンティティのkeyでフィルタリングは可能です。

q := datastore.NewQuery("").Ancestor(ancestorKey)
// q := datastore.NewQuery("").Ancestor(ancestorKey).Filter("__key__ >", key)

今回は前回のエンティティ操作に引き続き、基本的なクエリについて取り上げました。

次回は今回取り上げたoffsetによるオーバーヘッドを発生させることなく、結果をスキップして結果を取得することが出来るクエリカーソルについて、Goでの使い方を見てゆきます。