在 GQL 中计算结果的最佳方法是什么?
-
05-07-2019 - |
题
我认为进行计数的一种方法是这样的:
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 定期计算的统计数据是一个不错的首选资源。
要查询给定 Kind 的统计信息,您可以使用以下 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 = $"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()返回索引顶部的单个记录。它非常快。
然后,当您想要获取超过第1000个对象限制时,您只需执行以下操作:
MyObj.all().filter('num > ' , 2345).fetch(67)
当我读到Aral Balkan严厉的评论时,我已经这样做了: http://aralbalkan.com/1504。这很令人沮丧,但是当你习惯它并且你意识到它比关系数据库上的count()快多少时,你就不会介意......