データ ストアから 1000 を超えるレコードをフェッチし、すべてを 1 つのリストに入れて django に渡すにはどうすればよいですか?
16 に答える
バージョン1.3.6(2010年8月17日リリース)以降、次のことができます。
すべてのデータストアクエリのデータストアcount()クエリとオフセットの結果は、1000に制限されなくなりました。
記録のために - 1000 エントリのフェッチ制限がなくなりました:
http://googleappengine.blogspot.com/2010/02/app-engine-sdk-131-include-major.html
引用:
1000 件の結果制限はもうありません - そうです。カーソルの追加と、過去数か月にわたる多くの小規模なデータストアの安定性とパフォーマンスの向上の集大成により、結果の最大制限を完全に削除するのに十分な自信が持てるようになりました。フェッチ、反復、または Cursor を使用しているかどうかに関係なく、結果の数に制限はありません。
App Engine は、キーで順序付けを行い、最後のキーを次のオフセットとして使用することで、結果を 1000 単位で「ページング」する優れた方法を提供します。彼らはここにいくつかのサンプルコードを提供しています:
http://code.google.com/appengine/docs/python/datastore/queriesandindexes.html#Queries_on_Keys
この例ではクエリを多数のリクエストに分散させていますが、ページ サイズを 20 から 1000 に変更し、クエリセットを組み合わせてループでクエリを実行できます。さらに、 itertools を使用して、必要になる前に評価せずにクエリをリンクすることもできます。
たとえば、1000 を超える行数をカウントするには、次のようにします。
class MyModel(db.Expando):
@classmethod
def count_all(cls):
"""
Count *all* of the rows (without maxing out at 1000)
"""
count = 0
query = cls.all().order('__key__')
while count % 1000 == 0:
current_count = query.count()
if current_count == 0:
break
count += current_count
if current_count == 1000:
last_key = query.fetch(1, 999)[0].key()
query = query.filter('__key__ > ', last_key)
return count
これが制限として出てくるたびに、私はいつも「なぜ1,000 件以上の結果が必要なのですか?」と疑問に思います。Google 自体が 1,000 を超える結果を提供していないことをご存知ですか? この検索を試してください : http://www.google.ca/search?hl=en&client=firefox-a&rls=org.mozilla:en-US:official&hs=qhu&q=1000+results&start=1000&sa=N最近、クエリの検索結果の 100 ページ目までクリックするのに時間をかけたことがなかったからです。
実際に 1,000 を超える結果をユーザーに返す場合は、データ ストアがそれを許可しないという事実よりも大きな問題があると思います。
多くの結果が必要になる (正当な) 理由の 1 つは、データに対して大規模な操作を実行し、要約 (たとえば、このすべてのデータの平均はいくらか) を提示する場合です。この問題 (Google I/O の講演で話題になっている) の解決策は、要約データをその場で計算し、保存することです。
できません。
FAQ の一部には、クエリの行 1000 を超えてアクセスする方法はないと記載されています。「OFFSET」を増やすと、結果セットが短くなります。
すなわち: OFFSET 999 --> 1 つの結果が返されます。
ウィキペディアから:
App Engine は、エンティティ get から返される最大行数を Datastore 呼び出しごとに 1,000 行に制限しています。ほとんどの Web データベース アプリケーションはページングとキャッシュを使用するため、一度にこれほど多くのデータを必要としないため、ほとんどのシナリオでは問題になりません。[要出典] アプリケーションが操作ごとに 1,000 を超えるレコードを必要とする場合、クライアント側のソフトウェアまたは Ajax ページを所有して、無制限の数の行に対して操作を実行します。
http://code.google.com/appengine/docs/whatisgoogleappengine.htmlから
サービス制限のもう 1 つの例は、クエリによって返される結果の数です。クエリは最大 1,000 件の結果を返すことができます。より多くの結果を返すクエリは、最大値のみを返します。この場合、そのようなクエリを実行するリクエストは、タイムアウト前にリクエストを返す可能性は低くなりますが、データストアのリソースを節約するために制限が設けられています。
http://code.google.com/appengine/docs/datastore/gqlreference.htmlから
注: LIMIT 句の最大値は 1000 です。最大値より大きい制限が指定されている場合は、最大値が使用されます。これと同じ最大値が GqlQuery クラスの fetch() メソッドに適用されます。
注: fetch() メソッドのオフセット パラメータと同様に、GQL クエリ文字列の OFFSET は、データストアからフェッチされるエンティティの数を減らしません。fetch() メソッドによって返される結果にのみ影響します。オフセットのあるクエリには、オフセット サイズに線形に対応するパフォーマンス特性があります。
http://code.google.com/appengine/docs/datastore/queryclass.htmlから
limit および offset 引数は、データストアからフェッチされる結果の数と、fetch() メソッドによって返される結果の数を制御します。
データストアは、オフセット + 制限の結果をアプリケーションにフェッチします。最初のオフセット結果は、データストア自体によってスキップされません。
fetch() メソッドは最初のオフセット結果をスキップし、残りを返します (制限結果)。
クエリには、オフセット量に制限を加えた値に直線的に対応するパフォーマンス特性があります。
これが意味することは
単一のクエリがある場合、0 ~ 1000 の範囲外のものを要求する方法はありません。
オフセットを増やすと 0 が増えるだけなので、
LIMIT 1000 OFFSET 0
1000行を返しますが、
と
LIMIT 1000 OFFSET 1000
0 行が返されるため、1 つのクエリ構文で手動または API を使用して 2000 件の結果を取得することはできません。
唯一のもっともらしい例外
テーブルに数値インデックスを作成します。つまり、次のようになります。
SELECT * FROM Foo WHERE ID > 0 AND ID < 1000
SELECT * FROM Foo WHERE ID >= 1000 AND ID < 2000
データまたはクエリがこの「ID」ハードコードされた識別子を持つことができない場合は、運が悪い
この1K制限の問題は解決されました。
query = MyModel.all()
for doc in query:
print doc.title
Queryオブジェクトを反復可能として扱うことにより:イテレーターはデータストアから小さなバッチで結果を取得し、アプリが結果の反復を停止して、必要以上のフェッチを回避できるようにします。クエリに一致するすべての結果が取得されると、反復は停止します。fetch()と同様に、イテレータインターフェイスは結果をキャッシュしないため、Queryオブジェクトから新しいイテレータを作成するとクエリが再実行されます。
最大バッチサイズは1Kです。また、自動データストアクォータもまだあります。
しかし、プラン1.3.1 SDKでは、シリアル化して保存できるカーソルが導入されたため、将来の呼び出しで、最後に中断したところからクエリを開始できます。
1000レコードの制限は、GoogleAppEngineの厳しい制限です。
このプレゼンテーションhttp://sites.google.com/site/io/building-scalable-web-applications-with-google-app-engineでは、AppEngineを使用してデータを効率的にページングする方法について説明しています。
(基本的に、キーとして数値IDを使用し、IDにWHERE句を指定します。)
1000 を超えるレコードがある場合、リモート API を介したフェッチにはまだ問題があります。テーブルをチャンクで反復処理するために、この小さな関数を作成しました。
def _iterate_table(table, chunk_size = 200):
offset = 0
while True:
results = table.all().order('__key__').fetch(chunk_size+1, offset = offset)
if not results:
break
for result in results[:chunk_size]:
yield result
if len(results) < chunk_size+1:
break
offset += chunk_size
ModelBase
クラスで次のようなものを使用しています。
@classmethod
def get_all(cls):
q = cls.all()
holder = q.fetch(1000)
result = holder
while len(holder) == 1000:
holder = q.with_cursor(q.cursor()).fetch(1000)
result += holder
return result
これにより、考える必要なく、すべてのモデルで 1000 のクエリ制限を取得できます。キーバージョンは実装が同じくらい簡単だと思います。
class Count(object):
def getCount(self,cls):
class Count(object):
def getCount(self,cls):
"""
Count *all* of the rows (without maxing out at 1000)
"""
count = 0
query = cls.all().order('__key__')
while 1:
current_count = query.count()
count += current_count
if current_count == 0:
break
last_key = query.fetch(1, current_count-1)[0].key()
query = query.filter('__key__ > ', last_key)
return count
entities = []
for entity in Entity.all():
entities.append(entity)
そのような単純な。エンティティごとに作成される RPC があることに注意してください。これは、チャンクでフェッチするよりもはるかに低速です。したがって、パフォーマンスが気になる場合は、次のようにします。
アイテムが 100 万未満の場合:
entities = Entity.all().fetch(999999)
それ以外の場合は、カーソルを使用してください。
また、次の点にも注意してください。
Entity.all().fetch(Entity.all().count())
最大 1000 を返すため、使用しないでください。
JJG:上記のソリューションは素晴らしいですが、レコードが0の場合に無限ループが発生する点が異なります。(私のレポートのいくつかをローカルでテストしているときにこれを見つけました)。
whileループの開始を次のように変更しました。
while count % 1000 == 0:
current_count = query.count()
if current_count == 0:
break
2つのクエリの内容を一緒に追加するには:
list1 = first query
list2 = second query
list1 += list2
リスト1には、2000件すべての結果が含まれています。
提案された解決策は、エントリがキーでソートされている場合にのみ機能します...最初に別の列でソートしている場合でも、 limit(offset, count) 句を使用する必要があり、1000 エントリの制限が引き続き適用されます。最初のリクエストは 1000 個を超えるキーを返すことができないため、2 つのリクエストを使用する場合も同じです。( 1000 件の結果の制限を削除するためにキーで並べ替える必要がある場合、キーに関するGoogleクエリのセクションには明確に記載されていません)
これは Gabriel が提供するソリューションに近いですが、結果を取得せずにカウントするだけです。
count = 0
q = YourEntityClass.all().filter('myval = ', 2)
countBatch = q.count()
while countBatch > 0:
count += countBatch
countBatch = q.with_cursor(q.cursor()).count()
logging.info('Count=%d' % count)
私のクエリでは完璧に動作し、高速でもあります (67,000 エンティティをカウントするのに 1.1 秒)
クエリが不等式フィルターまたはセットであってはならないことに注意してください。そうしないと、カーソルが機能せず、次の例外が発生します。
AssertionError: MultiQuery で使用できるカーソルがありません ("IN" または "!=" 演算子を使用したクエリ)