まず一例
数値と生成時刻だけを持つモデルを例に取る。
def Item(db.Model): score = db.IntegerProperty(default=0) created = db.DateTimeProperty(auto_now_add=True)
ぐきゅる
#itemを取得する qs = Item.all()
オフセット、リミットを指定して
#10番目から5個取得する qs = Item.all().fetch(5, 10)
できないことあれこれ
1000番目以降のデータをぐきゅることはできない
#2000番目から5個取得する qs = Item.all().fetch(5, 2000) #動かない!
同様に、
1000個以上要素があるなら総数は調べられない
print Item.all().count() #上限が1000
複数のプロパティを、それぞれイコール以外の条件を使って絞り込みできない
#scoreが100未満で、2000年以前に作成されたものを取得する qs = Item.gql('WHERE score < :1 AND created <= :2', 100, '2000-01-01 00:00:00' ) #動かない!
そのままだと余りに自由度がないので、これらを多少改善していく。
とりあえず、auto-incrementなidは付加しておくと便利
def Item(db.Model): id = db.IntegerProperty(required=True) score = db.IntegerProperty(default=0) created = db.DateTimeProperty(auto_now_add=True)
とモデルを定義して、
def get_max_id(db_class): """idの最大数を返す""" qs = db_class.gql('ORDER BY id DESC LIMIT 1') for q in qs: return q.id return 0 def add_item(id, score): """itemを追加する""" key = 'i%s' % id obj = db.get( db.Key.from_path('Item', key) ) if not obj: obj = Item(id=id, score=score ) obj.put() else: raise Exception try: id = get_max_id(Item) id += 1 db.run_in_transaction(add_item, id, score) ...
のようにすると、1から順にitemそれぞれにidが付加される。
こうすると、
1000個以上要素があっても、正しい総数が取得できる
print Item.all().count() #上限が1000
↓
print get_max_id(Item) #1000以上でも大丈夫
また、fetch()の制限は、
「WHEREで絞った後の結果について、1000番目以降についてはアクセスできませんよ」ということなので、max_idを使うとWHEREで任意の番号のものを絞り込むことができる。つまり、
1000番目以降のデータをぐきゅることができる
#2000番目から5個取得する qs = Item.all().fetch(5, 2000) #動かない!
↓
#2000番目から5個取得する qs = Item.gql('WHERE :1 <= id', 2000 ).fetch(5)
これがfetch(5, 2000)の代替としてなり立つのは、他に何もWHEREの条件がないから。
その点は注意を。
複数の、イコール以外の条件で絞り込みたいなら
よく考えないとぐだぐだになる感がひしひしとするが、
条件が固定な場合、その条件の真偽をプロパティにしておく
def Item(db.Model): id = db.IntegerProperty(required=True) score = db.IntegerProperty(default=0) created = db.DateTimeProperty(auto_now_add=True) score_lt_100 = BooleanProperty()
モデルに真偽値のプロパティを追加し、
scoreにアクセスするときにこの条件を判定し、常に反映させておけば、
def lt_100(score): if score < 100: return True return False new_score = 200 obj = db.get( db.Key.from_path('Item', 'i%s' % id) ) if obj: obj.score = new_score obj.score_lt_100 = lt_100(new_score) obj.put() else: raise Exception
#scoreが100未満で、2000年以前に作成されたものを取得する qs = Item.gql('WHERE score < :1 AND created <= :2', 100, '2000-01-01 00:00:00' ) #動かない!
↓
#scoreが100未満で、2000年以前に生成されたものを取得する qs = Item.gql('WHERE score_lt_100 = :1 AND created <= :2', True, '2000-01-01 00:00:00' )
とできる。
例えばある月、ある週などで絞り込みたいときでも同様に、
def Item(db.Model): id = db.IntegerProperty(required=True) score = db.IntegerProperty(default=0) created = db.DateTimeProperty(auto_now_add=True) score_lt_100 = BooleanProperty() cym = db.StringProperty() cyW = db.StringProperty()
とモデルを定義して、
try: now = datetime.datetime.now() cym = now.strftime('%y-%m') cyW = now.strftime('%y-%W') id = get_max_id(Item) id += 1 db.run_in_transaction(add_item, id, score, cym, cyW)
として追加すれば、
#今月作成された、scoreが100未満のものを取得する qs = Item.gql('WHERE score_lt_100 = :1 AND cym = :2', True, cym )
とできる。
もしくは、
#今週作成された、scoreが100未満で、idが2000以降のものを、5個取得する qs = Item.gql('WHERE score_lt_100 = :1 AND cyW = :2 AND :3 < id', True, cyW, 2000 ).fetch(5)
のようにも。
この場合、前述のように、このクエリはfetch(5, 2000)と等価でないことに注意。
常に反映させることは不可能な場合や、条件が変動する場合などは、cronで判定ジョブを走らせることになると思う。
その際はcreatedや参照の構造などを参考に、ジョブがチェックする場所を最適化しないと、データが多くなるにつれてしんどくなる。
ORDER BY について
http://code.google.com/intl/ja/appengine/docs/python/datastore/queriesandindexes.html
にちょこっと書いてあるが、
SELECT * FROM Person WHERE birth_year >= :min_year ORDER BY last_name, birth_year #エラー
SELECT * FROM Person WHERE birth_year >= :min_year ORDER BY birth_year, last_name #正しい
この場合、ORDER BYの先頭にbirth_yearがこないといけない。
後者の場合、index.yamlは
indexes: - kind: Person properties: - name: birth_year - name: last_name
になる。
SELECT * FROM Person WHERE birth_year = :min_year ORDER BY last_name #正しい
はエラーにならない。
index.yamlは
indexes: - kind: Person properties: - name: birth_year - name: last_name
で、前の例と同じ。
ここ半日ぐらいで調べたところなので、断言する自信はないが、
要するに、WHERE句で一つしか使えない、イコール以外で絞り込んだプロパティは、オーダーに影響するのでORDER BYに書かなきゃいけませんよ、という話(だと思う)。
WHERE foo = :1 AND bar < :2 ORDER BY bar, baz #正しい
であれば、インデックスは
indexes: - kind: Hoge properties: - name: foo - name: bar - name: baz
のようになる。
ということで
…SQLが恋しい、と思いつつなんとかかんとかGAEでこのぐらいはできそう。
みんながぐきゅってくれることを期待してメモを置いておく。
れっつ、ぐきゅる!