How to get the last nth record for each group in a rails query - ruby-on-rails

I have a table named Game which is filled with individual players statlines from all their games in an nhl season. The fields That I am concerned with here are games_played which = n for the nth game of a player in the season.
and name which is the players name.
Essentially I am trying to figure out how to grab the nth last game for each player.
For instance I know the following query can get me the last game for each player
Game.group(:name).having('games_played = MAX(games_played)')
However when I try the following (lets say n is 10)
Game.group(:name).having('games_played=MAX(games_played)-10')
I get no results
and in fact if I explicitly do something like
Game.group(:name).having('games_played=16')
I only get the last game of players who played exactly 16 games, instead of the 16th game for all players. (Which I guess explains the no results of the previous query)
How do I go about getting the nth last game for each player?
Is this even the right way to query this? I also have a player table where a player has_many games, and a game belongs_to a player. Should I be taking advantage of that?

To find the nth last game for a specific player, it would probably be easiest to start by finding the player.
player = Player.find_by(name: "user3692508")
then you can find the players games with:
player.games
To get the nth last game, you can order it by games_played in descending order, then limit it to one result and offset it with the offset you want:
player.games.order(games_played: :desc).limit(1).offset(0)
If you do an offset of 0, you will get the last game. If you do an offset of 1 you will get the 2nd last game and so on.
This is assuming your player has_many :games and the game belongs_to :player
By using a sub query you can get the nth last game for each player.(it looks like a mess.....)
offset = 1
sub_query = "SELECT 'sub_game'.id FROM 'games' as sub_game WHERE 'sub_game'.'player_id' = games.player_id ORDER BY 'sub_game'.'games_played' DESC LIMIT 1 OFFSET #{offset}"
Game.joins(:player).where("games.id IN (#{sub_query})").order(id: :desc).group(:player_id)
With this solution, you would sort the games for each player in the sub query and do the offset there first.

Related

Order by nodes that received more relationships in a certain period of time

Is it possible to order by nodes that received more relationships in a certain period of time?
For example, I have User and Movie, and a User can LIKE a Movie. The LIKE relationship has a property called date, which is the moment the user liked the product.
What I want is: the Products that received more LIKE in the last 2 days.
How can I do it? :)
I'm assuming that you are storing dates as strings like "2017-09-02 00:00:00". So in this case I believe you can try something like:
MATCH (:User)-[like:LIKE]->(movie:Movie)
WHERE like.date > "date from 2 days ago"
RETURN movie, count(like) as numberOfLikes
ORDER BY numberOfLikes DESC

When using GKLocalPlayerListener's receivedTurnEventFor match, is there a way to determine if the turn event is the result of a matchmaking find?

In iOS 9 Apple deprecated the public func turnBasedMatchmakerViewController(_ viewController: GKTurnBasedMatchmakerViewController, didFind match: GKTurnBasedMatch)
method of the GKTurnBasedMatchmakerViewControllerDelegate.
Apple's direction is to use the func player(_ player: GKPlayer, receivedTurnEventFor match: GKTurnBasedMatch, didBecomeActive: Bool) method of GKLocalPlayerListener.
Using only receivedTurnEventFor match leads to the following scenario:
User taps + which displays a GKTurnBasedMatchmakerViewController.
User taps Play Now and Game Center will search for a match.
Game Center will return a match with empty matchData (a new match), or place the user into a match in progress — and the game is informed of this through receivedTurnEventFor match.
While it is simple enough to determine if a new match has been created (using matchData), there doesn't appear to be a way to determine if a match has been found vs a match being updated as all events flow through receivedTurnEventFor match.
My question is this:
When using GKLocalPlayerListener's receivedTurnEventFor match method, is there a way to determine if the turn event is the result of a matchmaking find?
There are four cases:
1. You join a new match and it's your turn (you are creator of the match)
2. You join a new match and it's other player turn (you found the match)
3. You join an exising match and it's your turn
4. You join an existing match and it's other player turn.
Cases 3 and 4 could be when you switch between matches or rejoin a match.
You can check match.currentPatriticant and lastTurnDate properties to determine which case takes place.
If a player just create a new match (receivedTurnEventFor is called as a result of matchmaking) the match.participants[0].lastTurnDate is nil and match.currentParticipant?.player?.playerID is equal GKLocalPlayer.localPlayer().playerID (case 1).
If you join an existing match:
match.participants[0].lastTurnDate is nil and match.currentParticipant?.player?.playerID is NOT equal GKLocalPlayer.localPlayer().playerID (you joined a new match, but other player is in a turn - case 2)
Do determine unambiguously cases 3 and 4 you can check lastTurnDate of all participant and compare local player with current player.
I don't think game center will help you on this one. It looks like yet another ambiguous call to the overloaded receivedTurnEventForMatch function. But, I believe you can manage this yourself since you know the originating player always sees the match first, before any of the other participants.
Looks like there are four cases here:
The player creates a new match with at least 1 automatch participant: you can detect this because the participants will be in "matching" status when you receive the new match. In this case, as originator, you can set flags in the match data that indicates which players have invites and which are automatches.
The player creates a new match with invites: In this case, all of the participants should be set in the newly received match. Again, you can set flags in the match data that subsequent players can read.
The player joins a match that was created as an automatch: If you support only 2 players, at the time player2 receives this match both playerIDs will be set. If you support more, there's an (unreliable) chance that some slots will still be set to automatch, but that's not very helpful. If the originating player set flags in the match data indicating which players were invited vs automatch slots, though, this player can determine their own status.
The player joins a match had one or more invitations: Same as the prior situation. The joining player can't determine anything useful from game center, and will rely on data added to the match by the originating player.

Payout Algorithm Ruby

Im building a rails app that has users and scores. I want the top half of the users to get paid out. I have a separate tiebreaker input stored for each user if they get happen to tie for last place (last paid out place). For example, I need help, if their are 8 users and 4th and 5th tie in points. Then it calls my tiebreaker.
This is what I have tried:
First I am counting the users and determening the top half of the players:
theUsersCount = ParticipatingUser.where(game_id: game_id).size
numofWinners = theUsersCount / 2
Then I am taking the users and their scores and pushing it to an array then only showing the top half of the users that won.
userscores.push("#{user.username}" => playerScore})
userscores[0..numofWinners].sort_by { |y| y[:score] }
But I am unsure of how to take execute the tiebreaker if their is a tie for last place.
To get the users count you should use count rather than size - size fetches all the rows, then counts them, while count counts the rows in the DB, and returns the number:
user_count = ParticipatingUser.where(game_id: game_id).count
(actually - the above is wrong - here is an explanation - you should use size which smartly chooses between length and count - thanks #nzifnab)
Now, find the score of the user in the user_count/2 place
minimal_score = ParticipatingUser.order(:score, :desc).pluck(:score).take(user_count/2).last
And take all the users with this score or more:
winning_users = ParticipatingUser.where('score >= ?', minimal_score).order(:score, :desc)
now check if there are more users than expected:
if winning_users.size > user_count/2
then break your ties:
tie_breaker(winning_users[user_count/2-1..-1])
All together:
user_count = ParticipatingUser.where(game_id: game_id).size
minimal_score = ParticipatingUser.order(:score, :desc).pluck(:score).take(user_count/2).last
winning_users = ParticipatingUser.where('score >= ?', minimal_score).order(:score, :desc)
if winning_users.size > user_count/2
losers = tie_breaker(winning_users[user_count/2-1..-1])
winning_users -= losers
end
winning_users

Rails 4 scope for SQL statement

I have 3 models, Player, Detail and Hero. A Player has many Details, a Detailbelongs to a Hero. Now I want to retrieve all Heroes a Player has played. I came up with this so far.
Hero.where("id IN (SELECT hero_id FROM details WHERE player_id = 1)").group("id")
How would I write a scope for it so I can also pass the Player to the scope? This is what I got so far, but it only groups the Details. I would also like to count every Heroso at the end I have x times Hero1, x times Hero2 and so on.
scope :heroes, ->(player) { where('player_id = ?', player.id).group("id") }
This scope is in the Detail model. I don't know if it is the best place, since I want it to return Heroes and not Details.
Ok finally after quite some time I figured out how to get most played Heroes with a scope.
scope :mostplayed, ->(player) { select('details.*, count(heros.id) AS hero_count').joins(:hero).where('player_id = ?', player.id).group('hero_id').order('hero_count DESC').limit(3) }
Maybe it is not the best solution, but it does exactly what I want it to do.
I think you just need to use has_many :through or has_and_belongs_to_many, so if you use:
player.heroes
That will bring all the heroes related with that player. If you don't need duplicated heroes, you could use
player.heroes.uniq
Now, related about sum every hero, maybe you want to sum how many details has each hero? if that's not what you want, please explain it better.

Can I add where clauses after putting limit on a scoped query?

I have a model called Game in which I build up a scoped query.
Something like:
games = Game.scoped
games = games.team(team_name) if team_name
games = game.opponent(opponent_name) if opponent_name
total_games = games
I then calculate several subsets like:
wins = games.where("team_score > opponent_score").count
losses = games.where("opponent_score > team_score").count
Everything is great. Then I decided that I want to limit the original scope to show the last X number of games.
total_games = games.limit(10)
If there are 100 games that match what I want for total_games, and then I add .limit(10) - it gets the last 10. Great. But now calling
total_games.where("team_score > opponent_score").count
will reach back beyond the last 10, and into results that aren't part of total_games. Since adding .limit(10), I'll always get 10 total games, but also 10 wins, and 10 losses.
After typing this all out, I've realized that the cases where I want to use limit are for showing a smaller set of results - so I'll probably end up just looping through the results to calculate things like wins and losses (instead of doing separate queries as in my subsets above).
I tried this out when total_games had hundreds or thousands of results, and it's significantly slower to loop through than it is to just do separate queries for the subsets.
So, now I must know - what is the best way to limit a scoped query, and then do future queries of those results that restrict themselves results returned by the original .limit(x)?
I don't think you can do what you want to do without separating your query into two steps, first getting 10 games from total_games and making the DB query with all:
last_10_games = total_games.limit(10).all
then selecting from the resulting array and getting the size of the result:
wins = last_10_games.select { |g| g.team_score > g.opponent_score }.count
losses = last_10_games.select { |g| g.opponent_score > g.team_score }.count
I know this is not exactly what you asked for, but I think it's probably the most straightforward solution to the problem.

Resources