I have an activity table which says which users follows who. (fromUser
and toUser
)
I am constructing a leaderboard to see who has the most rating posted, amongst the followers.
So I created this query:
ParseQuery<ParseObject> queryActivityFollowing = new ParseQuery<>("Activity");
queryActivityFollowing.whereEqualTo("type", "follow");
queryActivityFollowing.whereEqualTo("fromUser", ParseUser.getCurrentUser());
queryActivityFollowing.setLimit(500);
// innerQuery, only get Users posted by the users I follow
ParseQuery<ParseUser> queryUserFollowing = ParseUser.getQuery();
queryUserFollowing.whereMatchesQuery("toUser", queryActivityFollowing);
// querySelf
ParseQuery<ParseUser> querySelf = ParseUser.getQuery();
querySelf.whereEqualTo("objectId", ParseUser.getCurrentUser().getObjectId());
List<ParseQuery<ParseUser>> queries = new ArrayList<>();
queries.add(queryUserFollowing);
queries.add(querySelf);
query = ParseQuery.or(queries);
query.orderByDescending("rating_count");
query.setLimit(20);
But somehow, it times out and never displays a result. Is there something inefficient with my query?
Thanks!
Edit:
Data description:
Activity
is a class with 3 columns, fromUser
, toUser
, type
. fromUser
and toUser
are pointers to the _User
class, type
is a string
in _User
, I have the classic attributes, and an integer named rating_count
, to which is the orderBy criteria (updated code above).
Actually, I think the query doesn't time out, but just returns 0 results. I follow some of my users so it's definitely not the expected output.
It's a tough one, because parse's query supports this sort of thing only minimally. The best idea I can offer is this one:
whereEqualTo("type", "follow")
and whereEqualTo("fromUser", ParseUser.getCurrentUser())
Parse.Query.or()
. setLimit(1000)
will explain why belowinclude("toUser")
result.get("toUser").getInt("rating_count")
since the results will be instances of Activity, and you'll have eagerly fetched their related toUsers.This scheme is simpler than what you coded, and will get the job done. However, possibly a major problem is that it will miss data for users with > 1000 followers. Let me know if that's a problem, and I can suggest a more complex answer. A minor shortcoming is that you'll be forced to do the search (maybe a sort) yourself in memory to find the maximum rating_count.
EDIT - For > 1k followers, you're stuck with calling the query multiple times, setting the skip
to the count of records received in the previous query, collecting the results in a big array.
Your point about transmitting so much data is well taken, and you can minimize the network use by putting all this into a cloud function, doing the in-memory work in the cloud and only returning the records the client needs. (This approach has the added benefit of being coded in javascript, which I speak more fluently than java, so I could be more prescriptive about the code).
EDIT 2 - Doing this in cloud code has the benefit of reducing the network traffic to just those users (say, 20) that have maximum ratings. It doesn't solve the other problems I indicated earlier. Here's how I'd do it in the cloud...
var _ = require('underscore');
Parse.Cloud.define("topFollowers", function(request, response) {
var user = new Parse.User({id:request.params.userId});
topFollowers(user, 20).then(function(result) {
response.success(result);
}, function(error) {
response.error(error);
});
});
// return the top n users who are the top-rated followers of the passed user
function topFollowers(user, n) {
var query = new Parse.Query("Activity");
query.equalTo("type", "follow");
query.equalTo("fromUser", user);
query.include("toUser");
return runQuery(query).then(function(results) {
var allFollowers = _.map(results, function(result) { return result.get("toUser"); });
var sortedFollowers = _.sortBy(allFollowers, function(user) { return user.get("rating_count"); });
return _.first(sortedFollowers, n);
});
}
// run and rerun a query using skip until all results are gathered in results array
function runQuery(query, results) {
results = results || [];
query.skip(results.length);
return query.find().then(function(nextResults) {
results = results.concat(nextResults);
return (nextResults.length)? runQuery(query, results) : results;
});
}
Note - I haven't tested this, but have similar stuff working in production.