Какой лучший способ подсчета результатов в GQL?

Я полагаю, что один из способов сделать подсчет такой:

foo = db.GqlQuery("SELECT * FROM bar WHERE baz = 'baz')
my_count = foo.count()

Что мне не нравится, так это то, что мой счет будет ограничен до 1000 макс, и мой запрос, вероятно, будет медленным. Кто-нибудь там с обходным путем? Я имею в виду один, но он не чувствует себя чистым. Если бы только GQL имел реальную функцию COUNT...

9 ответов

Решение

+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)

Затронутые счетчики - это правильный способ отслеживать числа, подобные этому, как говорили люди, но если вы поймете это в конце игры (как я), то вам нужно будет инициализировать счетчики из фактического количества объектов. Но это отличный способ прожить вашу бесплатную квоту Datastore Small Operations (я думаю, 50 000). Каждый раз, когда вы запускаете код, он использует столько операций, сколько имеется объектов модели.

Теперь у нас есть статистика хранилища данных, которую можно использовать для запроса количества объектов и других данных. Эти значения не всегда отражают самые последние изменения, поскольку они обновляются каждые 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 = $"select * from __Stat_Kind__ where kind_name = '{kind}'",
        AllowLiterals = true
    };
    var result = database.RunQuery(query);
    return (long) (result?.Entities?[0]?["count"] ?? 0L);
}

Я не пробовал это, и это полный боров ресурсов, но, возможно, итерации с .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

Лучший обходной путь может показаться немного нелогичным, но он прекрасно работает во всех моих приложениях 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)

Я уже сделал это, когда прочитал скандальный обзор Арала Балкана: http://aralbalkan.com/1504. Это разочаровывает, но когда вы привыкнете к этому и поймете, насколько это быстрее, чем count() для реляционных БД, вы не будете возражать...

Другие вопросы по тегам