belongs は、SQLのIN演算子に変換されます。belongs の詳しい説明は、次のリンクを参照ください。
参考: web2py補足ドキュメント - Expression.belongs(value)
テスト用コード
次のようなコードを考えます。
モデル
db.define_table('person', Field('name'), format='%(name)s') db.define_table('dog', Field('owner_id', db.person), Field('birthday', 'date', requires=IS_DATE(format='%Y-%m-%d')), Field('name'))
コントローラ
def list(): from datetime import date, timedelta today = date.today() date_last_year = today - timedelta(days=365) owner = [row.id for row in db(db.person).select()] dbset = db(db.dog.birthday >= date_last_year)\ (db.dog.birthday <= today)\ (db.dog.owner_id.belongs(owner)) rows = dbset.select() return dict(rows=rows)
このコードは、GAE環境でも特に問題なく動作します。ちなみにコントローラのコードの9行目で belongs を使用しています。
エラーが発生するコード
それではGAE環境で、エラーの発生するコードを見てみましょう。
def list(): from datetime import date, timedelta today = date.today() date_last_year = today - timedelta(days=365) owner = [row.id for row in db(db.person).select()] dbset = db(db.dog.birthday >= date_last_year)\ (db.dog.birthday <= today)\ (db.dog.owner_id.belongs(owner)) rows = dbset.select(limitby=(0,10)) return dict(rows=rows)
10行目の select メッソッドに、limitby を設定しました。limitby は、セレクトするレコード数を指定するオプションです。
参考: web2py補足ドキュメント - Set.select([*fields, **attributes])
GAE以外の環境では問題なく動きます。GAE環境では、次のようなエラーメッセージがログに記録されます。
BadArgumentError: _MultiQuery with cursors requires __key__ order
解決策
このエラーが発生するのは、NDB API を使った場合のようです。NDB は Python の次世代 Datastore 用モジュールで、数年前に登場しました。web2py でもGAE環境の場合は、コードを NDB API 用に変換するようになっています。
参考:
BadArgumentError: _MultiQuery with cursors requires __key__ order in ndb
NDB Datastore API - Query Cursors
NDB ではクエリーとして、IN / OR / != の各演算子とカーソルを同時に使用した場合、上記のエラーが発生する仕様になっています。
「ん?、カーソルは使用していないぞ」 と思われるかもしれませんが、web2py では limitby オプションを利用した場合、カーソルを使用するようになっています。
問題解決のためには、クエリーで使用するフィールドでソートしてあげればよいです。具体的には、次のようにコード変更します。
def list(): from datetime import date, timedelta today = date.today() date_last_year = today - timedelta(days=365) owner = [row.id for row in db(db.person).select()] dbset = db(db.dog.birthday >= date_last_year)\ (db.dog.birthday <= today)\ (db.dog.owner_id.belongs(owner)) rows = dbset.select(limitby=(0,10), orderby=db.dog.birthday|db.dog.owner_id|db.dog.id) return dict(rows=rows)
11行目の select メソッドで、 orderby オプションを指定します。
参考: web2py補足ドキュメント - Set.select([*fields, **attributes])
ここで気をつける事は、ソートフィールドにクエリーで使用するフィールドを含めると共に、主キーのフィールド(idフィールド)を最後に足します。idフィールドがソートに含まれないと、プログラムは動作しません。
ちょっとレアなケースの仕様のようですが、解り難かったので記事にしました。