Google App Engine の仕様に苦しめられたので、今日自分がやったことを簡単にまとめる。
1. クエリの制限Google App Engine のデータストアでは、
実行できるクエリに制限がある。
主な制限は以下のとおり
・不等式が使用できるのは1つのプロパティ(DBで言うところの列名)に限られる
# これはOK
SELECT * FROM Test WHERE date = DATE('2010-12-01') AND cost >= 100
# これは BadFilterError 例外が発生する
SELECT * FROM Test WHERE date >= DATE('2010-12-01') AND cost >= 100
・不等式を使用したクエリで ORDER BY を使用する場合、ORDER BY の先頭は不等式を使用したプロパティにする必要がある
# これはOK
SELECT * FROM Test WHERE date >= DATE('2010-12-01') ORDER BY date
# これは BadArgumentError 例外が発生する
SELECT * FROM Test WHERE date >= DATE('2010-12-01') ORDER BY item
自分が作っているプログラムで、「月単位で item の cost をまとめる」みたいなことをする必要があった。
普通のDBでは、以下のようなプログラムにすればよい。
from google.appengine.ext import db
# test データモデル
class Test(db.Model):
item = db.StringProperty(required=True)
cost = db.IntegerProperty(default=0)
date = db.DateProperty(required=True)
(中略)
# 月単位で item の cost をまとめる処理
query = db.GqlQuery("SELECT * FROM Test WHERE date >= DATE('2010-12-01') AND date < DATE('2011-01-01') ORDER BY item")
for entity in query:
# entity.item が前の値と同じなら entity.cost を加算
しかし Google App Engine では、上記の ORDER BY 制限のため BadArgumentError 例外が発生する。
2. ORDER BY 制限を回避するORDER BY の制限だけなら、
query = db.GqlQuery("SELECT * FROM Test WHERE item != '' AND date >= DATE('2010-12-01') AND date < DATE('2011-01-01') ORDER BY item")
で回避できそうだが、これは上記の「不等式が使用できるのは1つのプロパティ」に反するので BadFilterError 例外が発生する。
仕方なくリストを使ってソートした。
# クエリから ORDER BY を削除
query = db.GqlQuery("SELECT * FROM Test WHERE date >= DATE('2010-12-01') AND date < DATE('2011-01-01')")
entityL = [] # ソートするための暫定リスト
for entity in query:
# 値として有り得ない文字列を使って、値を結合
entityL.append(entity.item + '$$$$' + str(entity.cost))
entityL.sort()
for test in entityL:
# 文字列を分割して item と cost を取得
testSep = test.split('$$$$',1)
item = testSep[0]
cost = int(testSep[1])
# item が前の値と同じなら cost を加算
エンティティ数が少ないからこれで何とかなったが、数が多い場合は処理時間や負荷が心配になる。
クエリで不等式を使わないよう、プロパティを追加する方法も考えられる。
# month プロパティを追加
class Test(db.Model):
item = db.StringProperty(required=True)
cost = db.IntegerProperty(default=0)
date = db.DateProperty(required=True)
month = db.StringProperty()
(中略)
# データ登録時に年月を設定
test.month = '%4d%2d' % (test.date.year, test.date.month)
test.put()
この場合、クエリで ORDER BY が使用できるので以下のように簡潔になる。
query = db.GqlQuery("SELECT * FROM Test WHERE month = '201012' ORDER BY item")
for entity in query:
# entity.item が前の値と同じなら entity.cost を加算
ここらへんを考慮してデータ設計しなきゃいけないってことだな。
Google App Engine のデータストアには他にもいろいろ考慮すべき点がありそうだから、ちゃんと調べるか。