Query, SQL, Ruby on Rails - sql

I am working a project and encountered a problem regarding writing the best query for the problem.
I will start presenting the problem and the solution I found.
We have the following ERD structure:
A Player has many Scores, and a Score has many Handicap Results.
We have a many to many relationship between Handicap and League.
In my app at some point I run a calculation formula that takes all players from a Customer or League and for each score of the player we create a handicap result corresponding to he Handicaps that the Club / League has.
HandicapResults: value, score_id, handicap_id
Handicaps: game_type(string), league_ids (association between the handicap and leagues)
Score: league_id, player_id, game_type(string), play_at (date), round_id (integer)
On the customers#show action I want to display for all handicaps and players the result.
The result is the LAST SCORE PLAYED SORTED BY ROUND_ID DESC AND PLAY_AT DESC. From this score we the the handicap_result corresponding to customer handicaps.
The solution I found for one player would be:
The only problem is that this will do 1 select for all players displayed in the view. I would want to write a select that would return the values for a collection of players (player_ids).
At the moment the sql returns the handicap_result corresponding to the last score played (sorted_by round_id desc and play_at desc) where score.league_id included in handicap.league_ids and score.game_type = handicap.game_type.
I would want to created a method that can have as parameters player_ids, handicaps, ... all informations required for the query.
That returns the following:
Ex:
player_ids: [1, 2, 3]
handicaps: [handicap_1, handicap_2]
# and return something like:
{
#player_id 1: { handicap_1.id: value, handicap_2.id: value }
.........
#player_id 5: { handicap_1.id: value, handicap_2.id: value }
}
# the value is the handicap_result where handicap_result.handicap_id == handicap_1.id / handicap_2.id and for the corresponding score
Hopefully I described the problem correctly and people can understand me. I really wish that someone can help me into writing the query that runes 1 time and returns the values for a given collection of players.
Thank you and have a nice day!

Related

Removing repeating values from sql

I have a question for my homework in class that goes as such:
The Professor wants to review information about questions on quizzes that appear to be difficult.
Create a view named HardQuizzes that contains the quiz number, quiz date, average score, question id, number of students who chose A, number of students who chose B, number of students who chose C, and number of students who chose D for each question on a quiz where the average score is less than 15. Verify that the view has been created correctly. Confirm the change.
And I think I came up with a way to get the answer with this:
CREATE VIEW HardQuizzes
AS
SELECT DISTINCT
QQ.QuizNum,
QQ.QuizDate,
AvgScore,
NumChoseA,
NumChoseB,
NumChoseC,
NumChoseD
FROM
QuizQuestions QQ,
Quizzes Q
WHERE
QQ.QuizNum = Q.QuizNum
AND AvgScore < 15
But when I do this it creates the view but so many of the values repeat and I cant figure out a way to stop them repeating. Is there a way do that?
This is a screenshot of the view when I make it
Since it's an assignment, hoping to just nudge you in the right direction -
What other data is available in the original tables that you can investigate?
Are there multiple classes taking each of these quizzes?
If there are multiple classes, would the intent of the question lead you to believe the records should be combined?
If that's the case this could be accomplished with GROUP BY and aggregation functions over the NumChose columns.

Filter by two values with ID column

im analyzing some e-sports soccer championship data.
My original table looks like this:
Every row corresponds to one match with the Date, Players envolved, the Teams they used and their Scores
my df head()
After seaching around tableau community, I pivoted "Player A" and "Player B" columns so i can filter for players individually. Now any match has 2 rows(one for each player on that match) and tey're unified by the 'MatchID' column:
my tableau table
That said, i want to build a view where the viewer could select two players and see statistics about all the matches they played against each other, like these two:
1- Last 10 matches info (Date, teams they played with, scores)
2- Most-frequent results like this graph:
the graph i want to show
Tried bringing some dimensions to colums but i really couldnt find a way to show the entire row data in a view. No idea about h2 filter from two players and take only matches where they encounter using MatchID.
I tried searching around and do some Calculated Fields filters, but i just went Tableau with no background in SQL, Excel or anything, just Python. So im a bit lost with so many options and ways.
If anyone could gimme directions about that i would be very happy. Thx in advice (:
I think you should unpivot your data so you are back with 1 record per match. Then you will be able to use 2 parameters as your filters; one parameter for player 1 and the other for player 2. That would enable the user to select 2 different players.
As there's a chance the same player could be in both the Player 1 and Player 2 columns, to use as a filter is a little more complex. Your filter calculated field for the Player1 parameter would be something like:
[FilterParameterPlayer1]: [ParameterPlayer1] = [Player1] OR ParameterPlayer1] = [Player2]
And for Player2 parameter:
[FilterParameterPlayer2]: [ParameterPlayer2] = [Player1] OR ParameterPlayer2] = [Player2]
Both filter fields should be set to only show True.

creating table with correct data

Im having problems finding the correct data. I have a Table which contains customers(customerID). Each customer is connected to a certain phonenumber(PhoneNr). Every number starts with 2-9.
Every customer have a callcenter(CallCenterID) they can call iff needed.
I want to know how many customers call each callcenter, divided from 2-9(PhoneNumber).
So I want to know how many calls a callcenter gets from every customer with 5, as there starting number in phonenumber.
So far so good. My Code in sql:
Select CallCenter, Count(Customers) AS Number
from ******
Where PhoneNumber Like '45%' --Just need the numbers from Danish customers.
Group By Callcenter;
Im new to much of this, but i've tried the whole day to come up with the right result.
Right now Im getting every callcenter, and the number for every call to them.
Can anyone help me?
:)
If I'm understanding correctly, you want the counts for all CallCenter's broken down by the first digit in the PhoneNumber:
SELECT CallCenter, SUBSTR(PhoneNumber, 1, 1) as startsWith, COUNT(*) as number
FROM myTable
GROUP BY CallCenter, SUBSTR(PhoneNumber, 1, 1)
ORDER BY 2, 3
If that's not what you wanted, please explain your question a bit better.

Redis zrevrangebyscore, sorting other than lexicographical order

I have implemented a leader board using sorted sets in redis. I want users with same scores to be ordered in chronological order, i.e., user who came first should be ranked higher. Currently redis supports lexicographical order. Is there a way to override that. Mobile numbers are being used as members in sorted set.
One solution that I thought of is appending timestamp in front of mobile numbers and maintaining a hash to map mobile number and timestamp.
$redis.hset('mobile_time', '1234567890', "#{Time.now.strftime('%y%m%d%H%M%S')}")
pref = $redis.hget('mobile_time, '1234567890'')
$redis.zadd('myleaderboard', "1234567890:#{pref}")
That way I can get rank for a given user at any instance by adding a prefix from hash.
Now this is not exactly what I want. This will return opposite of what I want. User who comes early will be placed below user who comes later(both with same score).
Key for user1 = 201210121953**23**01234567890 score: 400
key for user2 = 201210121253**26**09313123523 score: 400 (3 seconds later)
if I use zrevrangebyscore, user2 will be placed higher than user1.
However, there's a way to get the desired rank:
users_with_higher_score_count = $redis.zcount("mysset", "(400", "+inf")
users_with_same_score = $redis.zrangebyscore("mysset", "400", "400")
Now I have the list users_with_same_score with correct ordering. Looking at index I can calculate rank of the user.
To get leader board. I can get members in intervals of 50 and order them through ruby code. But it doesn't seems to be a good way.
I want to know if there's a better approach to do it. Or any improvements that can be made in solution I purposed.
Thanks in advance for your help.
P.S. Scores are in multiples of 50
The score in a sorted set supports double precision floating point numbers, so possibly a better solution would be to store the redis score as highscore.timestamp
e.g. (pseudocode)
highscore = 100
timestamp = now()
redis.zadd('myleaderboard', highscore + '.' + timestamp, playerId)
This would mean that multiple players who achieved the same high score will also be sorted based on the time they achieved that high score as per the following
For player 1...
redis.zadd('myleaderboard', '100.1362345366', "Charles")
For player 2...
redis.zadd('myleaderboard', '100.1362345399', "Babbage")
See this question for more detail: Unique scoring for redis leaderboard
The external weights feature of the sort command is your saviour here
SORT mylist BY weight_*
http://redis.io/commands/sort
If you are displaying leaderboard in descending order of score then I don't think the above solution will work. Instead of just appending timestamp in the score you should append Long.MAX_VALUE - System.nanoTime() So your final score code should be like -
highscore = 100
timestamp = Long.MAX_VALUE - System.nanoTime();
redis.zadd('myleaderboard', highscore + '.' + timestamp, playerId);
Now you will get the correct order when you call redis.zrevrange('myleaderboard', startIndex, endIndex)

Dynamic user ranks

I have a basic karma/rep system that awards users based on their activities (questions, answers, etc..). I want to have user ranks (title) based on their points. Different ranks have different limitations and grant powers.
ranks table
id rankname points questions_per_day
1 beginner 150 10
2 advanced 300 30
I'm not sure if I need to have a lower and upper limit, but for the sake of simplicity I have only left a max points limit, that is, a user below 150 is a 'beginner' and below or higher than 300, he's an 'advanced'.
For example, Bob with 157 points would have an 'advanced' tag displayed by his username.
How can I determine and display the rank/title of an user? Do I loop through each row and compare values?
What problems might arise if I scale this to thousands of users having their rank calculated this way? Surely it will tax the system to query and loop each time a user's rank is requested, no?
You could better cache the rank and the score. If a user's score only changes when they do certain activities, you can put a trigger on that activity. When the score changes, you can recalculate the rank and save it in the users record. That way, retreiving the rank is trivial, you only need to calculate it when the score changes.
You can get the matching rank id like this; query the rank that is closest (but below or equal to) the user schore. Store this rank id in the user's record.
I added the pseudovariable {USERSCORE} because I don't know if you use parameters or any other way to enter values in a query.
select r.id
from ranks r
where r.points <= {USERSCORE}
order by r.points desc
limit 1
A little difficult without knowing your schema. Try:
SELECT user.id, MIN(ranks.id) AS rankid FROM user JOIN ranks ON (user.score <= ranks.points) GROUP BY user.id;
Now you know the ranks id.
This is non-trivial though (GROUP BY and MAX are pipeline breakers and so quite heavyweight operations), so GolezTrol advice is good; you should cache this information and update it only when a users score changes. A trigger sounds fine for this.