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

Я полагаю, что один из способов подсчета выглядит следующим образом:

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

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


person barneytron    schedule 07.01.2009    source источник
comment
Зачем нужно знать счет? Когда я увидел ваш пост, я проголосовал за него, потому что мне тоже нужна эта функциональность. Потом я понял, что мне это действительно не нужно, если я представляю вещи по-другому.   -  person asterite    schedule 07.01.2009
comment
Я пытаюсь собрать пользовательский ввод, скажем, рейтинги «большой палец вверх/вниз». Но я хочу отслеживать их с такими подробностями, как метка времени и другие. Я мог бы использовать сущность с двумя столбцами: вверх и вниз и соответственно увеличивать, но тогда мне понадобилось бы 2 сущности. И gql поддерживает обновление только 1 объекта в трансе.   -  person barneytron    schedule 07.01.2009
comment
Я надеюсь, что кто-то может указать мне на какую-то документацию, которую я пропустил, о том, как это сделать. Или что кто-то знает, есть ли эта функциональность в дорожной карте gql.   -  person barneytron    schedule 07.01.2009


Ответы (9)


+1 к ответу Иехии.

Официальный и благословенный метод получения счетчиков объектов в GAE — создание сегментированного счетчика. Несмотря на громкое название, это довольно просто.

person zgoda    schedule 25.02.2009
comment
Однако потеря возможности отслеживать счетчики для каждого пользователя значительно усложняет отсеивание спамеров. Как можно решить эту проблему при использовании сегментированных счетчиков? - person Luke; 08.05.2009

При работе с масштабируемым хранилищем данных, таким как 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
person Jehiah    schedule 07.01.2009
comment
Я вижу, что в вашем трансе есть 2 вызова .put() для 2 разных объектов. Насколько я помню, я получил сообщение об ошибке, говорящее мне, что я не могу обновить два корневых объекта в одном и том же трансе, когда я пробовал что-то подобное раньше. Может быть, поместить оба (бар и стойку) в одну группу? Я попробую еще раз, когда у меня будет больше времени. - person barneytron; 08.01.2009
comment
Я подтвердил ошибку, которую я получил при выполнении двух вызовов .put() в транзакции: невозможно работать с разными группами сущностей в транзакции. Мне все же нравится идея использования двух сущностей. - person barneytron; 11.01.2009
comment
Разве значение счетчика по умолчанию не должно быть 1? - person dave paola; 10.08.2010

Функции подсчета во всех базах данных работают медленно (например, O(n)) — хранилище данных GAE просто делает это более очевидным. Как предлагает Иехайя, вам нужно сохранить вычисленное количество в сущности и ссылаться на нее, если вам нужна масштабируемость.

Это не уникально для App Engine — другие базы данных просто скрывают это лучше, вплоть до момента, когда вы пытаетесь подсчитать десятки тысяч записей с каждым запросом, и время рендеринга вашей страницы начинает увеличиваться в геометрической прогрессии...

person Nick Johnson    schedule 08.01.2009
comment
Это не всегда медленно - есть некоторые, которые поддерживают сегментированный счетчик для вас, ожидая таких запросов, как SELECT COUNT(*) FROM MyTable. (IIRC, MyISAM в MySQL делает это.) И время рендеринга вашей страницы будет увеличиваться только линейно, а не экспоненциально. Если бы он был действительно экспоненциальным, вы бы увидели его после сотен записей, и вам не пришлось бы ждать десятков тысяч. - person me22; 21.12.2010

Согласно GqlQuery.count() документации, вы можете установить limit как некоторое число больше 1000:

from models import Troll
troll_count = Troll.all(keys_only=True).count(limit=31337)

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

person rescdsk    schedule 31.01.2013

Я не пробовал, и это полный расход ресурсов, но, возможно, повторение с .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
person orip    schedule 07.01.2009
comment
Я думал об этом. Пожиратель ресурсов прав, но, вероятно, это сработает. - person barneytron; 08.01.2009

решение 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
person dfichter    schedule 07.02.2011

Теперь у нас есть статистика хранилища данных, которую можно использовать для запроса количества сущностей и других данных. Эти значения не всегда отражают самые последние изменения, поскольку они обновляются каждые 24–48 часов. Ознакомьтесь с документацией (см. ссылку ниже) для получения более подробной информации:

Статистика хранилища данных

person TheAddonDepot    schedule 07.02.2017

Как отметил @Dimu, статистика, периодически рассчитываемая Google, является достойным ресурсом для использования, когда точные подсчеты не нужны, а процент записей НЕ меняется кардинально в течение любого дня.

Чтобы запросить статистику для данного типа, вы можете использовать следующую структуру GQL:

select * from __Stat_Kind__ where kind_name = 'Person'

Есть ряд полезных свойств, возвращаемых this:

  • 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);
}
person Jonathan B.    schedule 11.03.2018
comment
Как получить доступ к этим свойствам? Мы попробовали query_result.count, но он был пуст. - person Praxiteles; 16.03.2018
comment
@Praxiteles, из какой библиотеки или языка вы запускаете свой код? Я использую библиотеку .NET/С#. Я обновлю свой ответ кодом, который я использую. - person Jonathan B.; 18.03.2018

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

person Alyxandor    schedule 19.01.2010
comment
Смещение не работает после ограничения в 1000, потому что это означает, что вы используете en.wikipedia.org /wiki/Schlemiel_the_painter%27s_Algorithm . И хотя использование .filter('foo ', bar) является хорошим способом просмотреть все результаты, нет необходимости добавлять в вашу запись бессмысленное поле, поскольку они уже упорядочены по ключу. Просто возьмите последний ключ, на который вы смотрели, и начните оттуда (после). - person me22; 21.12.2010