Получение DISTINCT пользователей в Google App Engine - PullRequest
4 голосов
/ 29 января 2010

Как это сделать в Google App Engine (Python):

SELECT COUNT(DISTINCT user) FROM event WHERE event_type = "PAGEVIEW" 
AND t >= start_time AND t <= end_time

Длинная версия:

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

Мои объекты событий выглядят так:

class Event(db.Model):
    t = db.DateTimeProperty(auto_now_add=True)
    user = db.StringProperty(required=True)
    event_type = db.StringProperty(required=True)

С базой данных SQL я бы сделал что-то вроде

SELECT COUNT(DISTINCT user) FROM event WHERE event_type = "PAGEVIEW" 
AND t >= start_time AND t <= end_time

Первая мысль, которая возникает, - это получить все события PAGEVIEW и отфильтровать дублированных пользователей. Что-то вроде:

query = Event.all()
query.filter("t >=", start_time)
query.filter("t <=", end_time)
usernames = []
for event in query:
    usernames.append(event.user)
answer = len(set(usernames))

Но это не сработает, потому что оно будет поддерживать только до 1000 событий. Следующее, что приходит мне в голову, это получить 1000 событий, затем, когда они закончатся, получить следующую тысячу и так далее. Но это тоже не сработает, потому что прохождение тысячи запросов и получение миллиона сущностей заняло бы более 30 секунд, что является пределом времени запроса.

Тогда я подумал, что мне нужно ЗАКАЗАТЬ ПОЛЬЗОВАТЕЛЯ, чтобы быстрее пропустить дубликаты. Но это недопустимо, потому что я уже использую неравенство "t> = start_time AND t <= end_time". </p>

Кажется очевидным, что это не может быть выполнено менее чем за 30 секунд, поэтому его необходимо фрагментировать. Но поиск отдельных предметов, похоже, плохо разбивается на подзадачи. Лучшее, что я могу придумать, - это при каждом вызове cron найти 1000 событий просмотра страниц, а затем получить из них разные имена пользователей и поместить их в сущность, подобную Chard. Это может выглядеть примерно так:

class Chard(db.Model):
    usernames = db.StringListProperty(required=True)

Таким образом, в каждом чарде будет до 1000 имен пользователей, меньше, если будут удалены дубликаты. Примерно через 16 часов (это нормально) у меня были бы все карты и я мог сделать что-то вроде:

chards = Chard.all()
all_usernames = set()
for chard in chards:
    all_usernames = all_usernames.union(chard.usernames)
answer = len(all_usernames)

Кажется, это может сработать, но вряд ли красивое решение. И с достаточным количеством уникальных пользователей этот цикл может занять слишком много времени. Я не проверял это в надежде, что кто-то предложит лучшее предложение, так что нет, если этот цикл окажется достаточно быстрым.

Есть ли какое-нибудь более красивое решение моей проблемы?

Конечно, все это уникальное подсчет пользователей может быть легко выполнено с помощью Google Analytics, но я создаю информационную панель для метрик, специфичных для приложений, и намереваюсь сделать это первой из многих статистических данных.

Ответы [ 4 ]

4 голосов
/ 14 декабря 2012

Начиная с SDK v1.7.4, теперь есть экспериментальная поддержка функции DISTINCT.

См .: https://developers.google.com/appengine/docs/python/datastore/gqlreference

1 голос
/ 24 января 2013

NDB по-прежнему не поддерживает DISTINCT. Я написал небольшой вспомогательный метод, чтобы иметь возможность использовать различные с GAE.

Смотрите здесь. http://verysimplescripts.blogspot.jp/2013/01/getting-distinct-properties-with-ndb.html

1 голос
/ 29 января 2010

Вот, возможно, работоспособное решение. В некоторой степени он основан на использовании memcache, поэтому всегда есть вероятность, что ваши данные будут непредсказуемым образом выселены. Предостережение emptor.

У вас будет переменная memcache с именем unique_visits_today или что-то подобное. Каждый раз, когда у пользователя был первый просмотр страницы за день, вы использовали функцию .incr () для увеличения этого счетчика.

Определение того, что это первый визит пользователя, выполняется путем просмотра поля last_activity_day , прикрепленного к пользователю. Когда пользователь заходит, вы смотрите на это поле, и если оно вчера, вы обновляете его до сегодняшнего дня и увеличиваете свой счетчик memcache.

В полночь каждый день задание cron принимает текущее значение в счетчике memcache и записывает его в хранилище данных, устанавливая счетчик на ноль. У вас будет такая модель:

class UniqueVisitsRecord(db.Model):
    # be careful setting date correctly if processing at midnight
    activity_date = db.DateProperty()
    event_count = IntegerProperty()

Затем вы можете просто, легко, быстро получить все UnqiueVisitsRecords, которые соответствуют любому диапазону дат, и сложить числа в их event_count полях.

1 голос
/ 29 января 2010

Google App Engine и, в частности, GQL не поддерживает функцию DISTINCT.

Но вы можете использовать функцию Python set, как описано в этом блоге и в этом ТА вопросе.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...