Mysql matchmaking / pairing - join

I'm currently working on a 1v1 online game and I ran into a problem when trying to match up players.
A player who wants to play gets put into a matchmaking table
id, user, amount
Now I want to query the table matchmaking for the best possible pairs of users (So, users who want to play for the same amount)
I also want users who are waiting for a longer time (smaller id), to be paired up first.
So far I have this query:
SELECT *
FROM matchmaking a, wpr_matchmaking b
WHERE a.user != b.user
AND a.amount = b.amount
ORDER BY a.id ASC , b.id ASC
LIMIT 0 , 30
This returns all possible pairings, so in a table with this content:
id, user, amount
1, 1, 10
2, 2, 10
3, 3, 10
I get the pairs:
1,2
1,3
2,1
2,3
3,1
3,2
Whereas I only want 1,2 returned in that case.
How do I make it only show me each user at most once?
Edit: adding the condition 'and a.id < b.id' to the query reduces the pairings by a factor of 2, but there's still too many.

Do you just want the highest pair to match those and then rerun the query? You could use SELECT TOP 1

Related

Rails Postgres, select users with relation and group them based on users starting time

I have Users and Checkpoint tables, each User can make multiple Checkpoints per day
I want to aggregate how many Checkpoints had been taken each day in the past 6 months based on each user's starting point, to create a graph showing avarage user Checkpoints witin thier x months.
for example:
if user1 started on January 1st, user2 started on March 15th, and user3 started on July 6th those would each be considered day 1, I would want the data from each of their day 1 even though they occur at different periods of time.
The current query I came up with, but unfotunatily it returns data based on fixed time for all of the users.
SELECT dates.date AS date,
checkpoints_count,
checkpoints_users
FROM (SELECT DATE_TRUNC('DAY', ('2000-01-01'::DATE - offs))::DATE AS date
-- 180 = 6 month in days
FROM GENERATE_SERIES(0, 180) AS offs) AS dates
LEFT OUTER JOIN (
SELECT
checkpoints_date::DATE AS date,
COUNT(id) AS checkpoints_count,
COUNT(user_id) AS checkpoints_users
FROM checkpoints
WHERE user_id in (1, 2, 3)
AND checkpoints_date::DATE
BETWEEN '2000-01-01'::DATE AND '2000-06-01'::DATE
GROUP BY checkpoints_date::DATE
) AS ck
ON dates.date = ck.date
ORDER BY dates.date;
EDIT
Here is a working SQL example that works (If I understand what you are looking for. Your SQL seems really complicated for what you are asking but I'm not a SQL expert)...
SELECT t1.*
FROM checkpoints AS t1
WHERE t1.user_id IN (1, 2, 3)
AND t1.id = (SELECT t2.id
FROM checkpoints AS t2
WHERE t2.user_id = t1.user_id
ORDER BY t2.check_date ASC
LIMIT 1)
SQL FIDDLE here
Since this is tagged Ruby on Rails I'll give you a rails answer.
If you know your user IDs (or use some other query to get them you have:
user_ids = [1, 2, 3]
first_checkpoints = []
user_ids.each do |id|
first_checkpoints << Checkpoint.where(user_id: id).order(:date).first
end
#returns an array of the first checkpoint of each user in list
This assumes a column in the checkpoints table called date. You didn't give a table structure for the two tables so this answer is a bit general. There might be a pure ActiveRecord answer. But this will get you what you want.

Active Record query Distinct Group by

I have a query, where I am trying to find min score of a user in a grade, in a grade, there are users with the same min score
Example: User A has a score of 2 and user B has a score of 2, so my expectation is to get both the users grouped by grade.
However, I am only getting one user. The query is :
users = Users.all
#user_score = users
.where.not(score: [ nil, 0 ])
.select('DISTINCT ON ("users"."grade") grade, "users".*')
.order('"users"."grade" ASC, "users"."score" ASC')
.group_by(&:grade)
Please if some can guide me what am i doing wrong here.
DISTINCT will cut off all non uniq values in the result, so there is no way to get multiple users with same min score in your query.
I think you can achieve the desired result with window function:
SELECT * FROM
(SELECT *, rank() OVER (PARTITION BY grade ORDER BY score) AS places
FROM users
WHERE score IS NOT NULL AND score != 0
) AS ranked_by_score
WHERE places = 1;

Limit PER user in rails query

So I have a standard users table structure, with a primary id key and what so not and the following persona table:
user_id | persona_id | time_inserted
2 1 x
2 2 x+1
2 3 x+2
1 1 x+3
5 8 x+6
5 9 x+1
What I'd like to do is retrieve the LAST inserted row and limit to ONE per user id. So, in that query, the result I want would be:
[2, 3] because the last inserted for 2 was persona_id 3 (x+2), [1, 1], and [5,8] because the last inserted for 5 was persona_id 8 (x+6)
This is my query:
to_return = Persona.select(to_get).where(to_condition)
This works, but retrieves them all. How can I restrict the query as asked? Thank you very much.
This should work:
to_return = Persona.select(to_get).where(to_condition).group('user_id').having('time_inserted = MAX(time_inserted)')
Update
You can't select a column if you don't put that in the group clause.
As you want to group by only user_id, one possible solution is, select the user_id s first with the maximum time_inserted like this:
users_ids_relation = Persona.select('user_id').group('user_id').having('time_inserted = MAX(time_inserted)')
Then, join it with the personas table based on the condition and then select the required columns:
users_ids_relation.joins('personas').where(to_condition).select(to_get)
It will give you the expected result.

Neo4j cypher time interval histogram query of time tree

I would like to build an histogram on time series stored as time tree in neo4j.
The data structures are event done by a user each has timestamp, say user purchases category.
What I need to have is the number of browsing on each category by each user between start and end time, with interval of (1 second to days)
My model feats graph db very nicely, as I read neo4j documentation I could not find any way to do it in one query, and I'm afraid that calling for each user would be very slow.
I am aware to cypher capabilities, but I have no idea how to create such query.
I am looking for something like this (not working)
MATCH startPath=(root)-[:`2010`]->()-[:`12`]->()-[:`31`]->(startLeaf),
endPath=(root)-[:`2011`]->()-[:`01`]->()-[:`03`]->(endLeaf),
valuePath=(startLeaf)-[:NEXT*0..]->(middle)-[:NEXT*0..]->(endLeaf),
vals=(middle)-[:VALUE]->(event)
WHERE root.name = 'Root'
RETURN event.name, count(*)
ORDER BY event.name ASC
GROUP BY event.timestamp % 1000*60*10 // 10 minutes histogram bar
Then I'd like to have a report, for how many users browse to each site category:
0-9 news 5, commerce 3 ; 10-19 news 6, commerce 19; 1 20-29 news 2, commerce 8;
Any idea if it is optional with neo4j time tree model?
if so how? :-)
Does this work?
MATCH
startPath=(root)-[:`2010`]->()-[:`12`]->()-[:`31`]->(startLeaf),
endPath=(root)-[:`2011`]->()-[:`01`]->()-[:`03`]->(endLeaf),
valuePath=(startLeaf)-[:NEXT*0..]->(middle)-[:NEXT*0..]->(endLeaf),
vals=(middle)-[:VALUE]->(event)
WHERE root.name = 'Root'
RETURN event.name, event.timestamp % 1000*60*10 AS slice, count(*)
ORDER BY slice ASC
Basically I just added the event.timestamp % 1000*60*10 into the return so that Neo4j will use that as a grouping criteria

Ranking position

I have a Rails application with the following models:
User
Bet
User has many_bets and Bets belongs_to User. Every Bet has a Profitloss value, which states how much the User has won/lost on that Bet.
So to calculate how much a specific User has won overall I cycle through his bets in the following way:
User.bets.sum(:profitloss)
I would like to show the User his ranking compared to all the other Users, which could look something like this:
"Your overall ranking: 37th place"
To do so I need to sum up the overall winnings per User, and find out in which position the current user is.
How do I do that and how to do it, so it don't overload the server :)
Thanks!
You can try something similar to
User.join(:bets).
select("users.id, sum(bets.profitloss) as accumulated").
group("users.id").
order("accumulated DESC")
and then search in the resulting list of "users" (not real users, they have only two meaningful attributes, their ID and a accumulated attribute with the sum), for the one corresponding to the current one.
In any case to get a single user's position, you have to calculate all users' accumulated, but at least this is only one query. Even better, you can store in the user model the accumulated value, and query just it for ranking.
If you have a large number of Users and Bets, you won't be able to compute and sort the global profitloss of each user "on demand", so I suggest that you use a rake task that you schedule regularly (once a day, every hour, etc...)
Add a column position in the User model, get the list of all Users, compute their global profitloss, sort the list of Users with their profitloss, and finally update the position attribute of each User with their position in the list.
Best way to do it is to keep a pre calculated total in your database either on user model itself or on a separate model that has 1:1 relation to user. If you don't do this, you will have to calculate sum for all users at all times in order to get their rating, which means full table operation on bets table. This said, this query will give you desired results, if more than 1 person has the same total, it will count both as rating X:
select id, (select count(h.id) from users u inner join
(select user_id, sum(profitloss) as `total` from bets group by user_id) b2
on b2.user_id = u.id, (select id from users) h inner join
(select user_id, sum(profitloss) as `total` from bets group by user_id) b
on b.user_id = h.id where u.id = 1 and (b.total > b2.total))
as `rating` from users where id = 1;
You will need to plug user.id into query in where id = X
if you add a column to user table to keep track of their total, query is a little simpler, in this example column name is total_profit_loss:
select id, total_profit_loss, (select count(h.username)+1 from users u,
(select username, score from users) h
where id = 1 and (h.total_profit_loss > u.total_profit_loss))
as `rating` from users where id = 1;

Resources