google app engine - ndb - ¿Cuál es la mejor manera de contar los resultados en GQL?
gql graphql (9)
+1 a la respuesta de Jehiah.
El método oficial y bendecido para obtener contadores de objetos en GAE es construir un contador sin formato . A pesar de que suena muy fuerte, esto es bastante sencillo.
Me imagino que una manera de hacer un conteo es como esta:
foo = db.GqlQuery("SELECT * FROM bar WHERE baz = ''baz'')
my_count = foo.count()
Lo que no me gusta es que mi recuento estará limitado a 1000 máx. Y mi consulta probablemente sea lenta. ¿Alguien por ahí con una solución alternativa? Tengo uno en mente, pero no me siento limpio. Si solo GQL tuviera una función COUNT real ...
Ahora contamos con estadísticas de almacén de datos que se pueden usar para consultar recuentos de entidades y otros datos. Estos valores no siempre reflejan los cambios más recientes, ya que se actualizan una vez cada 24-48 horas. Consulte la documentación (ver enlace a continuación) para más detalles:
Como señala @Dimu, las estadísticas calculadas por Google de forma periódica son un recurso decente cuando los recuentos precisos no son necesarios y el% de los registros NO están cambiando drásticamente durante un día determinado.
Para consultar las estadísticas de un tipo dado, puede usar la siguiente estructura GQL:
select * from __Stat_Kind__ where kind_name = ''Person''
Hay una serie de propiedades devueltas por este que son útiles:
-
count
- el número de entidades de este tipo -
bytes
- tamaño total de todas las entidades almacenadas de este tipo -
timestamp
: una fecha y hora para cuando se calcularon las estadísticas por última vez
Código de ejemplo
Para responder a una pregunta de seguimiento publicada como comentario de mi respuesta, ahora proporciono un ejemplo de código C#
que estoy usando, que sin duda puede no ser tan sólido como debería ser, pero parece funcionar bien para mí:
/// <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);
}
De acuerdo con la documentación de GqlQuery.count()
, puede establecer que el limit
sea un número superior a 1000:
from models import Troll
troll_count = Troll.all(keys_only=True).count(limit=31337)
Los contadores afilados son la forma correcta de realizar un seguimiento de números como este, como han dicho las personas, pero si descubres esto tarde en el juego (como yo), entonces necesitarás inicializar los contadores a partir de un conteo real de objetos. Pero esta es una excelente manera de aprovechar su cuota gratuita de Pequeñas operaciones de almacén de datos (creo que 50.000). Cada vez que ejecutas el código, utilizará tantas operaciones como objetos de modelo.
Debe cambiar sus ideas al trabajar con un almacén de datos escalable como GAE para hacer sus cálculos por adelantado. En este caso, eso significa que necesita mantener contadores para cada baz
e incrementarlos cada vez que agrega una nueva bar
, en lugar de contar en el momento de la visualización.
class CategoryCounter(db.Model):
category = db.StringProperty()
count = db.IntegerProperty(default=0)
luego al crear un objeto Bar, incrementa el contador
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'')
ahora tienes una manera fácil de obtener el conteo de cualquier categoría específica
CategoryCounter.filter(''category ='',category_name).get().count
La mejor solución puede parecer un poco contra-intuitiva, pero funciona muy bien en todas mis aplicaciones appengine. En lugar de confiar en los métodos enteros KEY y count (), agrega un campo entero propio al tipo de datos. Puede parecer un desperdicio hasta que realmente tenga más de 1000 registros, y de repente descubre que fetch () y limit () NO FUNCIONAN ANTERIORMENTE EL LÍMITE DE REGISTRO 1000.
def MyObj(db.Model):
num = db.IntegerProperty()
Cuando crea un nuevo objeto, debe recuperar manualmente la clave más alta:
max = MyObj.all().order(''-num'').get()
if max : max = max.num+1
else : max = 0
newObj = MyObj(num = max)
newObj.put()
Esto puede parecer una pérdida de una consulta, pero get () devuelve un solo registro de la parte superior del índice. Es muy rápido.
Luego, cuando quiere ir más allá del límite de objeto número 1000, simplemente lo hace:
MyObj.all().filter(''num > '' , 2345).fetch(67)
Ya había hecho esto cuando leí la mordaz crítica de Aral Balkan: http://aralbalkan.com/1504 . Es frustrante, pero cuando te acostumbras y te das cuenta de cuánto más rápido es esto que count () en un DB relacional, no te importará ...
La solución de Orip funciona con un pequeño ajuste:
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
Las funciones de recuento en todas las bases de datos son lentas (por ejemplo, O (n)); el almacén de datos de GAE simplemente lo hace más obvio. Como sugiere Jehiah, debe almacenar el recuento calculado en una entidad y referirse a eso si desea escalabilidad.
Esto no es exclusivo de App Engine; otras bases de datos simplemente lo ocultan mejor, hasta el punto en que intentas contar decenas de miles de registros con cada solicitud, y el tiempo de renderizado de la página comienza a aumentar exponencialmente ...
No lo he probado, y este es un completo recurso de cerdo, pero tal vez iterar con .fetch()
y especificar el desplazamiento funcionaría?
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