Merging multiple rows as separate columns using keys from second table - mysql

I have two tables.
Users:
int player_id
varchar player_name
Games:
int game_id
int player_id1
int player_id2
I want to make a query that takes in a player id as a parameter, and returns info on each game, along with the player's name. So far, what I have is the following:
SELECT
game_id,
player_id1,
player_id2,
from GAMES, GAME_STATES
where player_id1=#playerid or player_id2=#playerid
The part I'm stuck at is a simple way to have it return the names of players along with the player ids. The returning query would have 5 columns, one of the game id, two for each player id, and two for each of their names.
One solution I thought of is:
SELECT
game_id,
player_id1,
(select player_name from USERS where player_id=player_id1) as player_name1, player_id2,
(select player_name from USERS where player_id=player_id2) as player_name2,
from GAMES, GAME_STATES
where player_id1=#playerid or player_id2=#playerid
However, this seems like a lot of extra work on the database since there would be 2 more queries per row returned. If I have to do that, I'm wondering if making requests for names as a second query on the client side is a better option? Then the client could create a list of unique ids, and do one query for all of them. I'm not too worried about latency since the client and server are in the same data center.
Thank you for your help.

SELECT game_id, u1.player_name, u2.player_name FROM games AS game INNER JOIN users AS u1 ON y1.playerid = game.player_id1 INNER JOIN users AS u2 ON u2.playedid = game.player_id2 WHERE player_id1 = #playerid OR player_id2 = #playerid
Should do the trick

Related

MySQL query for multi-column distinct plus an ancillary column condition

Imagine a flat table that tracks game matches in which each game has three participants: an attacker, a defender and a bettor who is wagering on the outcome of the battle between players 1 and 2. The table includes the names of the players and the bettor of each game, as well as the date of the game, the scores of each player, the game venue and the name of the referee. I have included the CREATE sql for some sample data below.
DROP TABLE IF EXISTS `game`;
CREATE TABLE `game` (
`game_date` text,
`player_1` text,
`player_2` text,
`bettor` text,
`p1_score` double DEFAULT NULL,
`p2_score` double DEFAULT NULL,
`result` double DEFAULT NULL,
`venue` text,
`referee` text
)
INSERT INTO `game` VALUES ('2020-04-05','Bob','Kelly','Kevin',100,78,0.2,'TS1','Richard'),('2020-03-06','Jim','Bob','Dave',100,97,1.2,'TS2','Mike'),('2020-02-05','Jim','Bob','Kevin',100,86,0.9,'TS2','Mike'),('2020-01-06','Kelly','Bob','Jim',100,92,1.3,'TS2','Richard'),('2019-12-07','Kelly','Bob','Jim',100,98,1.7,'TS1','Mike'),('2019-11-07','Kelly','Bob','Kevin',78,100,2.1,'TS2','Mike'),('2019-10-08','Kelly','Bob','Kevin',97,100,1.5,'TS1','Mike'),('2019-09-08','Kelly','Jim','Dave',86,100,2.4,'TS1','Richard'),('2019-08-09','Kelly','Jim','Dave',92,100,2.8,'TS2','Mike'),('2019-07-10','Kelly','Jim','Dave',98,100,2.2,'TS2','Mike'),('2019-06-10','Kelly','Jim','Dave',100,78,1.9,'TS2','Richard'),('2019-05-11','Sarah','Jim','Kevin',100,97,2.1,'TS1','Mike'),('2019-04-11','Sarah','Jim','Kevin',100,86,2.1,'TS2','Mike'),('2019-03-12','Sarah','Jim','Kevin',100,92,2.8,'TS1','Mike'),('2019-02-10','Sarah','Jim','Kevin',100,98,1.8,'TS1','Richard');
I need a query that returns match info for each unique assembly of match participants... but only for the first match that the three participants ever played in all together, i.e., for the earliest game_date among the matches that all three participated in.
For example, a game where Bob was player 1, Kelly was player two and Kevin was the bettor would constitute a unique threesome. In the data, there is only one such pairing for this threesome so the query would return a row for that one match.
In the case of Sarah as player 1, Jim as player 2 and Kevin as bettor, there are four matches with that threesome and so the query would return only info for the earliest match, i.e., the one 2/10/2019.
Note that in the sample data there are two matches with the threesome 'Kelly','Bob','Jim'. There are also two other matchs with the threesome 'Kelly','Jim','Bob'. These are not the same because Bob and Jim swap places has player 2 and bettor. So the query would return one row for each of them, i.e., the matches dated '12/072019' and '08/09/2019', respectively.
Using DISTINCT, I can return a list of all of the unique player groupings.
SELECT DISTINCT player_1, player_2, bettor FROM games;
Using GROUP BY, I can return all of the game info for all of the matches the group played in.
SELECT * FROM games GROUP BY player_1, player_2, bettor;
But I can't figure out how to return all of the game info but only for the earliest game where all three participants played together and in distinct roles in the games.
I have tried sub-queries using MIN() for game_date but that's a loser. I suspect there is perhaps an INNER JOIN solution but I haven't found it yet.
I am grateful for any guidance you can provide.
One canonical approach uses a join to a subquery which identifies the earliest games for each trio:
SELECT g1.*
FROM games g1
INNER JOIN
(
SELECT player_1_name, player_2_name, player_3_name,
MIN(game_date) AS min_game_date
FROM games
GROUP BY player_1_name, player_2_name, player_3_name
) g2
ON g2.player_1_name = g1.player_1_name AND
g2.player_2_name = g1.player_2_name AND
g2.player_3_name = g1.player_3_name AND
g2.min_game_date = g1.game_date;
If you are running MySQL 8+, then the ROW_NUMBER analytic function provides another option:
WITH cte AS (
SELECT *, ROW_NUMBER() OVER (PARTITION BY player_1_name, player_2_name,
player_3_name
ORDER BY game_date) rn
FROM games
)
SELECT *
FROM cte
WHERE rn = 1;

Randomize a winner pair from the subscriptions

I have a project, where customer needs a winnner pair for events. The users of this site can "like" other user's (just like on FB), they subscribe to a particular post, and the script will generate a winner pair from the subscribers.
So I need a SQL query to randomize a winner pair from the list of pairs, where the users liked each other, and subscribed to a particular post.
How do i do that ?
I cant write a query that, because i got unexpected results.
I have 3 tables : events, likes, subs (and users ofc)
events table : event_id, event_name
subs table: sub_id, event_id, uid
likes table: liker, liked (the two uid from the users table)
Now I can make pairs from the likes table (i self-joined the table where liker = liked AND liked = liker) and randomized, but how can I join the subs and the events tables to the likes table to achieve that a randomized pair will be a subscribed users for a particular event too ?
My current query looks like this :
SELECT L.liked AS T1, L.liker AS T2
FROM likes AS L, likes AS K
WHERE L.liked = K.liker
AND L.liker = K.liked
ORDER BY rand( )
LIMIT 0 , 1
I googled everything about joins for one week, but i cant achieve that.

Best way to store a lot of data with timestamp in MySQL

what I should do?
Imagine tennis match.
Operator pushing buttons (actions) "Ace", "Fault", "Winner", "Unforced error" etc
We have a lot of operators, matches at the same time. And we have a lot of requests to db from users (~1000 per min).
What is the best way to store match_id, player, action, time_of_action?
1) table with 1 row for every match: match_id, actions. Actions, players,timestamp coded into 1 string #of player TINYINT id of action CHAR timestamp TIMESTAMP
example: actions = "1A2014-11-28 09:01:21 2W2014-11-28 09:01:33 1F2014-11-28 09:01:49"
2) table with multiple rows for one match: id, match_id, player, action_id, current timestamp (id PRIMARY KEY)
its will be about 250K rows after one day (300 per match * 40 matches in 1 tournament * 20 tournaments per day)
what is better: a lot of rows and a lot of requests SELECT player, action_id, timestamp FROM scores WHERE match_id = N
or
same number of requests, less rows ( /300 ) but much bigger data in rows?
sry for my ugly language, I hope you understand me, if not, tell me
add:
Im going to use it for match statistics on live or after match.
Users open page Statistics of match Federer - Nadal and every 10-30 seconds page refreshing
Example: http://www.wimbledon.com/en_GB/slamtracker/slamtracker.html?ts=1419259452680&ref=www.wimbledon.com/en_GB/slamtracker/index.html&syn=none&
I suggest you create reference tables called
match match_id, name, venue A row for each distinct match
player player_id, name A row for each distinct player
action action_id, name This is a codelist 1=Ace 2=Fault, etc.
These tables will be relatively static.
Then, I suggest you create an event table containing the following items in the following order.
match_id
ts (TIMESTAMP)
action_id
player_id
You should include all four of these columns in a composite primary key, in the order I have shown them.
Every time your scorers record an action you'll insert a new row to this table.
When you want to display the actions for a particular match, you can do this:
SELECT event.ts,
action.name AS action,
player.name AS player
FROM event
JOIN player ON event.player_id = player.player_id
JOIN action ON event.action_id = action.action_id
WHERE event.match_id = <<whatever match ID>>
ORDER BY event.match_id, event.ts
Because of the order of columns in the composite primary key on the event table, this kind of query will be very efficient even when you're inserting lots of new rows to that table.
MySQL is made for this kind of application. Still, when your site begins to receive tons of user traffic, you probably should arrange to run these queries just once every few seconds, cache the results, and use the cached results to send information to your users.
If you want to retrieve the match IDs for all the matches presently active (that is, with an event within the last ten minutes) you can do this.
SELECT DISTINCT match.id, match.name, match.venue
FROM event
JOIN match on event.match_id = match.match_id
WHERE event.ts >= NOW() - INTERVAL 10 MINUTE
If you need to do this sort of query a lot, I suggest you create an extra index on (ts, match_id).

Database design for a Fantasy league

Here's the basic schema for my database
Table user{
userid numeber primary key,
count number
}
Table player{
pid number primary key,
}
Table user-player{
userid number primary key foreign key(user),
pid number primary key foreign key(player)
}
Table temp{
pid number primary key,
points number
}
Here's what I intend to do...
After every match the temp table is updated which holds the id of players that played the last match and the points they earned.
Next run a procedure that will match the pid from temp table with every uid of user-player table having the same pid.
add the points from temp table to the count of user table for every matching uid.
empty temp table.
My questions is considering 200 players and 10000 users,Will this method be efficient?
I am going to be using mysql for this.
People often seem to be worried about performance for small databases. Let the DBMS do what it is designed to do. If you find in practice - or preferably under load testing - that you have a performance problem, then take steps to deal with it. Otherwise don't pre-optimize!
Instead of using a temporary table to store one batch of player scores, store all player scores in a tranactional table.
Remove the user.count column and replace your temp table with something like this:
Table player_points{
pid number primary key,
match_date datetime primary key,
points number
}
With this you can easily calculate any user's score. You can even recalculate any user's score as of a given date. This is much more powerful and much simpler to maintain. Keeping a current snapshot only makes it impossible to manage should anything go wrong or should one of your users challenge their score.
This query gives you the scores for all users. You can add filters to it to do other things like finding the score for a single user or showing a leader board.
select
U.userid as UserID
, sum(S.points) as TotalScore
from user S
inner join user-player J
on S.userid = J.userid
inner join player_points S
on J.pid = S.pid
group by
U.userid
This query would give you a leader board:
select
U.userid as UserID
, sum(S.points) as TotalScore
from user S
inner join user-player J
on S.userid = J.userid
inner join player_points S
on J.pid = S.pid
group by
U.userid
order by TotalScore desc
limit 10
This query would give you points awarded to a user by date, which you could graph as-is or cumulatively, for example.
select
S.match_date as MatchDate
, sum(S.points) as TotalScore
from user-player J
inner join player_points S
on J.pid = S.pid
where J.userid = 123 -- The user ID you want.
group by
S.match_date
order by S.match_date

SQL get polls that specified user is winning

Hello all and thanks in advance
I have the tables accounts, votes and contests
A vote consists of an author ID, a winner ID, and a contest ID, so as to stop people voting twice
Id like to show for any given account, how many times theyve won a contest, how many times theyve come second and how many times theyve come third
Whats the fastest (execution time) way to do this? (Im using MySQL)
After using MySQL for a long time I'm coming to the conclusion that virtually any use of GROUP BY is really bad for performance, so here's a solution with a couple of temporary tables.
CREATE TEMPORARY TABLE VoteCounts (
accountid INT,
contestid INT,
votecount INT DEFAULT 0
);
INSERT INTO VoteCounts (accountid, contestid)
SELECT DISTINCT v2.accountid, v2.contestid
FROM votes v1 JOIN votes v2 USING (contestid)
WHERE v1.accountid = ?; -- the given account
Make sure you have an index on votes(accountid, contestid).
Now you have a table of every contest that your given user was in, with all the other accounts who were in the same contests.
UPDATE Votes AS v JOIN VoteCounts AS vc USING (accountid, contestid)
SET vc.votecount = vc.votecount+1;
Now you have the count of votes for each account in each contest.
CREATE TEMPORARY TABLE Placings (
accountid INT,
contestid INT,
placing INT
);
SET #prevcontest := 0;
SET #placing := 0;
INSERT INTO Placings (accountid, placing, contestid)
SELECT accountid,
IF(contestid=#prevcontest, #placing:=#placing+1, #placing:=1) AS placing,
#prevcontest:=contestid AS contestid
FROM VoteCounts
ORDER BY contestid, votecount DESC;
Now you have a table with each account paired with their respective placing in each contest. It's easy to get the count for a given placing:
SELECT accountid, COUNT(*) AS count_first_place
FROM Placings
WHERE accountid = ? AND placing = 1;
And you can use a MySQL trick to do all three in one query. A boolean expression always returns an integer value 0 or 1 in MySQL, so you can use SUM() to count up the 1's.
SELECT accountid,
SUM(placing=1) AS count_first_place,
SUM(placing=2) AS count_second_place,
SUM(placing=3) AS count_third_place
FROM Placings
WHERE accountid = ?; -- the given account
Re your comment:
Yes, it's a complex task no matter what to go from the normalized data you have to the results you want. You want it aggregated (summed), ranked, and aggregated (counted) again. That's a heap of work! :-)
Also, a single query is not always the fastest way to do a given task. It's a common misconception among programmers that shorter code is implicitly faster code.
Note I have not tested this so your mileage may vary.
Re your question about the UPDATE:
It's a tricky way of getting the COUNT() of votes per account without using GROUP BY. I've added table aliases v and vc so it may be more clear now. In the votes table, there are N rows for a given account/contest. In the votescount table, there's one row per account/contest. When I join, the UPDATE is evaluated against the N rows, so if I add 1 for each of those N rows, I get the count of N stored in votescount in the row corresponding to each respective account/contest.
If I'm interpreting things correctly, to stop people voting twice I think you only need a unique index on the votes table by author (account?) ID and contestID. It won't prevent people from having multiple accounts and voting twice but it will prevent anyone from casting a vote in a contest twice from the same account. To prevent fraud (sock puppet accounts) you'd need to examine voting patterns and detect when an account votes for another account more often then statistically likely. Unless you have a lot of contests that might actually be hard.