GQL で結果をカウントする最良の方法は何ですか?
-
05-07-2019 - |
質問
カウントを行う 1 つの方法は次のようなものだと思います。
foo = db.GqlQuery("SELECT * FROM bar WHERE baz = 'baz')
my_count = foo.count()
気に入らないのは、カウントが最大 1000 に制限され、クエリが遅くなる可能性があることです。回避策を持っている人はいますか?考えていることはあるのですが、すっきりしない気がします。GQL に実際の COUNT 関数があれば...
解決
+1エヒアの応答。
GAEでオブジェクトカウンターを取得する公式で祝福された方法は、シャードカウンターを構築することです。重く聞こえる名前にもかかわらず、これは非常に簡単です。
他のヒント
GAEのようなスケーラブルなデータストアを使用して事前に計算を行う場合、考えを反転させる必要があります。この場合、各 baz
のカウンターを保持し、表示時にカウントするのではなく、新しい bar
を追加するたびにカウンターを増やす必要があることを意味します。
class CategoryCounter(db.Model):
category = db.StringProperty()
count = db.IntegerProperty(default=0)
その後、Barオブジェクトを作成するときに、カウンターをインクリメントします
def createNewBar(category_name):
bar = Bar(...,baz=category_name)
counter = CategoryCounter.filter('category =',category_name).get()
if not counter:
counter = CategoryCounter(category=category_name)
else:
counter.count += 1
bar.put()
counter.put()
db.run_in_transaction(createNewBar,'asdf')
特定のカテゴリのカウントを取得する簡単な方法があります
CategoryCounter.filter('category =',category_name).get().count
すべてのデータベースのカウント関数は低速です(たとえば、O(n))-GAEデータストアはそれをより明確にします。 Jehiahが示唆するように、計算されたカウントをエンティティに保存し、スケーラビリティが必要な場合はそれを参照する必要があります。
これはApp Engineに固有のものではありません。他のデータベースは、各リクエストで数万件のレコードをカウントしようとするまで、ページレンダリング時間が指数関数的に増加し始めます。 。
による GqlQuery.count()
ドキュメンテーション, を設定できます。 limit
1000 より大きい数値にする:
from models import Troll
troll_count = Troll.all(keys_only=True).count(limit=31337)
人々が言っているように、シャード カウンターはこのような数値を追跡する正しい方法ですが、(私のように) ゲームの後半でこれを理解した場合は、実際のオブジェクトの数からカウンターを初期化する必要があります。しかし、これはデータストアの小規模オペレーションの無料割り当て (50,000 だと思います) を使い切る素晴らしい方法です。コードを実行するたびに、モデル オブジェクトと同じ数の操作が使用されます。
試したことはありませんが、これは完全なリソースの浪費ですが、おそらく .fetch()
で繰り返し処理し、オフセットを指定するとうまくいきますか?
LIMIT=1000
def count(query):
result = offset = 0
gql_query = db.GqlQuery(query)
while True:
count = gql_query.fetch(LIMIT, offset)
if count < LIMIT:
return result
result += count
offset += LIMIT
oripのソリューションは少し調整して動作します:
LIMIT=1000
def count(query):
result = offset = 0
gql_query = db.GqlQuery(query)
while True:
count = len(gql_query.fetch(LIMIT, offset))
result += count
offset += LIMIT
if count < LIMIT:
return result
現在、エンティティカウントやその他のデータのクエリに使用できるデータストア統計があります。これらの値は24〜48時間ごとに更新されるため、常に最新の変更が反映されるわけではありません。詳細については、ドキュメントをご覧ください(以下のリンクを参照):
@Dimuが指摘したように、Googleが定期的に計算する統計は、正確なカウントが不要で、特定の日中にレコードの%が大幅に変化しない場合、適切なリソースです。
特定の種類の統計を照会するには、次のGQL構造を使用できます。
select * from __Stat_Kind__ where kind_name = 'Person'
これによって返される便利なプロパティがいくつかあります:
-
count
-この種類のエンティティの数 -
bytes
-この種類のすべてのエンティティの合計サイズ -
timestamp
-統計が最後に計算された日時の現在
サンプルコード
回答へのコメントとして投稿されたフォローアップの質問に回答するために、現在使用しているサンプルの C#
コードを提供しています。しかし、私にとってはうまくいくようです:
/// <summary>Returns an *estimated* number of entities of a given kind</summary>
public static long GetEstimatedEntityCount(this DatastoreDb database, string kind)
{
var query = new GqlQuery
{
QueryString = <*>quot;select * from __Stat_Kind__ where kind_name = '{kind}'",
AllowLiterals = true
};
var result = database.RunQuery(query);
return (long) (result?.Entities?[0]?["count"] ?? 0L);
}
最善の回避策は、直感に反するように思えるかもしれませんが、すべてのAppEngineアプリでうまく機能します。整数のKEYおよびcount()メソッドに依存するのではなく、独自の整数フィールドをデータ型に追加します。実際に1000を超えるレコードがあるまで無駄に思えるかもしれませんが、突然、fetch()およびlimit()が1000レコードの境界を超えて機能しないことがわかります。
def MyObj(db.Model):
num = db.IntegerProperty()
新しいオブジェクトを作成するとき、最も高いキーを手動で取得する必要があります:
max = MyObj.all().order('-num').get()
if max : max = max.num+1
else : max = 0
newObj = MyObj(num = max)
newObj.put()
これはクエリの無駄に思えるかもしれませんが、get()はインデックスの先頭から1つのレコードを返します。非常に高速です。
次に、オブジェクトの1000番目の制限を超えて取得する場合は、次のようにします。
MyObj.all().filter('num > ' , 2345).fetch(67)
アラルバルカンの痛烈なレビューを読んだときに、すでにこれを行っていました。 http://aralbalkan.com/1504。イライラしますが、慣れて、リレーショナルデータベースのcount()よりもはるかに高速であることに気づいたら、気にしません...