I have 3 tables:
matchdays:
matchday_id | season_id | userid | points | matchday
----------------------------------------------------
1 | 1 | 1 | 33 | 1
2 | 1 | 2 | 45 | 1
etc
players
userid | username
-----------------
1 | user1
2 | user2
etc.
seasons
seasons_id | title | userid
----------------------------
1 | 2011 | 3
2 | 2012 | 10
3 | 2013 | 5
My query:
SELECT s.title, p.username, SUM(points) FROM matchdays m
INNER JOIN players p ON p.userid = m.userid
INNER JOIN seasons s ON m.userid = s.userid
group by s.season_id
This results in (example!):
title | username | SUM(points)
------------------------------
2011 | user3 | 3744
2012 | user10 | 3457
2013 | user5 | 3888
What it should look like is a table with the winner (max points) of every season. Right now, the title and username is correct, but the sum of the points is way too high. I couldn't figure out what sum is calculated. Ideally, the sum is the addition of every matchday of a season for every user.
Your main issue is that you group by seasons only. Thus your SUM is running on all points over a season, regardless of the player.
The whole approach is wrong anyway. The "flaw" with userid in the season table is your biggest issue, and you seem to know it.
I will explain you how to calculate your rankings in the database one time for all, and to have them at your disposal at all times, which will save you a lot of headaches, and obviously save some CPU and loading times as well.
Start by creating a new table "Rankings":
CREATE table rankings (season_id INT, userid INT, points INT, rank INT)
If you have a lot of players, index all columns but points
Then, populate the table for each season:
This is a oneshot operation to run each time a season has ended.
So for the time being, you will have to run it several times for each season.
The key here is to compute the rank of each player for the season, which is a must-have that will be super-handy for later. Because MySQL doesnt have a window function for that, we have to use an old trick : incrementing a counter.
I decompose.
This will compute the points of a season, and provide the ranking for that season:
SELECT season_id, userid, SUM(points) as points
FROM matchdays
WHERE season_id = 1
GROUP BY season_id, userid
ORDER BY points DESC
Now we adapt this query to add a rank column :
SELECT
season_id, userid, points,
#curRank := #curRank + 1 AS rank
FROM
(
SELECT season_id, userid, SUM(points) as points
FROM matchdays
WHERE season_id = 1
GROUP BY season_id, userid
) T,
(
SELECT #curRank := 0
) R
ORDER BY T.points DESC
That's it.
Now we can INSERT the results of this computation into our ranking table, to store it once for good :
INSERT INTO rankings
SELECT
season_id, userid, points,
#curRank := #curRank + 1 AS rank
FROM
(
SELECT season_id, userid, SUM(points) as points
FROM matchdays
WHERE season_id = 1
GROUP BY season_id, userid
) T,
(
SELECT #curRank := 0
) R
ORDER BY T.points DESC
Change the season_id = 1 and repeat for each season.
Save this query somewhere, and in the future, run it once each time a season has ended.
Now you have a proper database-computed ranking and a nice ranking table that you can query whenever you want.
You want the winner for each season ? As simple as that:
SELECT S.title, P.username, R.points
FROM Ranking R
INNER JOIN seasons S ON R.season_id=S.season_id
INNER JOIN players P ON R.userid=P.userid
WHERE R.rank = 1
You will discover over the time that you can do a lot of different things very simply with your ranking table.
You're join is wrong, try something like:
SELECT s.title, p.username, SUM(m.points) as points FROM matchdays m
JOIN players p ON p.userid = m.userid
JOIN seasons s ON m.season_id = s.season_id
group by s.season_id, p.userid
ORDER by points DESC;
As pointed out, userid does'nt belong/is not needed in 'seasons' table.
Related
I have an assignment where I am working with a product database and I need to find the highest priced phone at each store location where the carrier is Sprint. I want to find the phone name and ID.
I.E I want to find the phone.name and phone.id of each Sprint phone store.carrier = "Sprint" where item.price is the greatest of its store.
I have tried a variety of SQL commands sorting by Max(price) and then trying to take distinct values by brand.
Relations:
store(id, name, carrier)
phone(id, name, location, price)
Where
store.id is the primary key of store.
product.id is the primary key of product.
product.location is the foreign key that references store.id.
I have tried
SELECT distinct phone.name, phone.id
FROM phone
JOIN store ON phone.location = store.id
WHERE store.carrier = 'sprint'
ORDER BY price DESC LIMIT 1;
and have also tried
SELECT distinct phone.name, phone.id
FROM phone
JOIN store ON phone.location = store.id
WHERE store.carrier = 'sprint'
AND phone.price >= (SELECT MAX
FROM (SELECT MAX(price)
FROM phone
INNER JOIN store ON phone.location = store.id
GROUP BY location))
GROUP BY phone.id;
I expect an output like:
+-----+--------------------------+
| ID | NAME |
+-----+--------------------------+
| 12 | iPhone XS MAX |
| 97 | Samsung Galaxy S10 Plus |
| 143 | iPhone XS MAX |
| 163 | Google Pixel 3XL |
| 194 | iPhone XS MAX |
+-----+--------------------------+
Instead, I get either a list of all the Sprint phones, or just the most valuable phone overall.
To get top 1 rows, you can use an recursive iteration within the subquery. And then use all row ranking values equals 1 to filter out in the main query :
SELECT q.id phone_id, q.name phone_name, q.store_id
FROM
(
SELECT p.id, p.name, p.location as store_id, p.price,
#rn := IF(#store = p.location, #rn + 1, 1) AS phone_rank,
#store := p.location as sto
FROM phone p
JOIN (SELECT #store := 1, #rn := 0) as q_iter
WHERE EXISTS ( SELECT 1 FROM store s WHERE s.id = p.location and s.carrier ='sprint' )
ORDER BY p.location, p.price DESC
) q
WHERE phone_rank = 1
ORDER BY q.store_id;
Demo
I am having trouble combining data from multiple tables. I have tried joins and subqueries but to no avail. I basically need to combine 2 queries into one. My tables (simplified):
Stock:
id int(9) PrimaryIndex
lot_number int(4)
description text
reserve int(9)
current_bid int(9)
current_bidder int(6)
Members:
member_id int(11) PrimaryIndex
name varchar(255)
Bids:
id int(9)
lot_id int(9)
bidder_id int(5)
max_bid int(9)
time_of_bid datetime
I'm currently using 2 separate queries which with 1000's of lots, makes it very inefficient. 1st query:
SELECT S.id, S.lot_number, S.description, S.reserve FROM stock S ORDER BY
S.lot_number ASC
The 2nd query within a while loop then gets the bidding info:
SELECT DISTINCT B.bidder_id, B.lot_id, B.max_bid, B.time_of_bid,
M.fname, M.lname FROM bids B, members M WHERE B.lot_id=? AND
B.bidder_id=M.member_id ORDER BY B.max_bid DESC LIMIT 2
Below is what i would like as output from a single query, if possible:
Lot No. | Reserve | Current Bid | 1st Max Bid | 1st Bidder | 2nd Max Bid | 2nd Max Bidder
1 | $100 | $120 | $150 | Steve | $110 | John
2 | $500 | $650 | $900 | Tom | $600 | Paul
I have had partial success with just getting the MAX(B.bid) and then its related details (WHERE S.id=B.id), but i cant get the top 2 bids for each lot.
First assign a row number rn to rows within each group of lot_id in table bids (highest bid gets 1, 2nd highest bid gets 2 and so on). The highest bid and second highest bid will be on two different rows after the LEFT JOIN. Use GROUP BY to merge the two rows into one.
select s.lot_number, s.reserve, s.current_bid,
max( case when rn = 1 then b.max_bid end) as first_max_bid,
max( case when rn = 1 then m.name end) as first_bidder,
max( case when rn = 2 then b.max_bid end) as second_max_bid,
max( case when rn = 2 then m.name end ) as second_bidder
from
stock s
left join
(select * from
(select *,
(#rn := if(#lot_id = lot_id, #rn+1,
if( #lot_id := lot_id, 1, 1))) as rn
from bids cross join
(select #rn := 0, #lot_id := -1) param
order by lot_id, max_bid desc
) t
where rn <= 2) b
on s.lot_number = b.lot_id
left join members m
on b.bidder_id = m.member_id
group by s.lot_number, s.reserve, s.current_bid
order by s.lot_number
Thank you stackoverflow community! I have learned SO much from you over the years and I've recently created an account. I hope the answer to this question isn't obviously somewhere already, but I am going to go crossed eyed if I read another post. Here's my problem:
I recently used a nested SELECT to get the highest score for each of my students from a table. I did so by a little trick another post taught me. I can't find the exact post I learned it from, but here is a snippet that's essentially the same trick. I imagine, if you are well versed in sql, it's nothing new to you:
SELECT id, authorId, answer, votes
FROM ( SELECT id, authorId, answer, votes
FROM answers
ORDER BY votes DESC) AS h
GROUP BY authorId
The ORDER BY ____ DESC makes the last value, the highest overwrite all previous, so you end up with only it...if I understand correctly. So, that was great and I tailored it to my needs. The only problem is, now, I'd like to add one more feature to it and I'm racking my brain cells over it. I'm hoping some generous person will just straighten me out. I want to get a complete list of students from my "rosters" table and if there is no score for a given student, in my "holder" table, I'd like it to display a "0". Here is what I have, and I don't know exactly how to tweak it to do just that:
SELECT *
FROM (
SELECT
holder.id,
#IFNULL(holder.score, 0) AS score,
holder.score AS score,
holder.total,
holder.student_id AS stu_id,
holder.date AS date,
users.firstname AS first,
users.lastname AS last,
users.stu_number AS stuno,
assignments.name AS test,
rosters.p_id,
preps.period AS period,
preps.user
FROM holder
JOIN rosters
ON rosters.stu_id = holder.student_id
JOIN users
ON users.id = holder.student_id
JOIN assignments
ON assignments.id = holder.t_id
JOIN preps
ON preps.id = rosters.p_id
WHERE holder.visible = 0
AND preps.user = 1
AND assignments.user = 1
AND holder.t_id = 1
AND preps.period = 2
ORDER BY score DESC
) x
GROUP BY stuno
ORDER BY last
You can see that line I commented out is one of my feeble attempts to get it to display a "0" if NULL, but it's not working. I get a complete list, but if the score isn't found for a student, that student isn't showing up in my list. Anyone have a solution/idea for me to try?
Am I overusing JOINs and making my life harder than it needs to be? I'm mostly self-taught, so I know I have some holes in the fundamentals. It hasn't stopped me from creating some crazy cool projects though...but every now and then I'm sure I'm causing myself some unnecessary grief.
///////////////////////////////////////////////
Here is what I have done with the answer below, so that it grabs info from my tables:
SELECT au.stu_id,
COALESCE(t.id, 0) as id,
COALESCE(t.score , 0) as score
FROM rosters au
LEFT JOIN (
SELECT *
FROM (
SELECT a.*,
#rownum := if(#prev_value = student_id,
#rownum + 1,
1) rn,
#prev_value := student_id as prev
FROM holder a,
(SELECT #rownum := 0, #prev_value := '') r
ORDER BY student_id, score DESC
) T
WHERE T.rn = 1) T
ON au.stu_id = T.student_id
So, this is working great, except it doesn't show students who don't have scores for a given test. If their score isn't found in the "holder" table, I'd like it to show up as a "0".
/////////////////
Wait a minute! I may have mispoke...I think it is working correctly. I'll need to tweak a few things and get back to you. By the way, thanks SO much for taking the time to help me!
Your first aproach only work because a bad design on MySQL.
The right aproach should be
SQL Fiddle Demo
SELECT a.id, a.authorId, a.answer, a.votes
FROM ( SELECT authorId,
MAX(votes) as votes
FROM answers
GROUP BY authorId ) AS h
JOIN answers a
ON a.authorId = h.authorId
AND a.votes = h.votes;
OUTPUT
| id | authorId | answer | votes |
|----|----------|--------|-------|
| 2 | a | x2 | 21 |
| 4 | b | x1 | 23 | ==>
| 5 | b | x2 | 23 | ==> duplicates max value are possible
But this have issue if several answer has same score. You need to include some logic to decide which one to show.
Also you can use variable to get the highest score.
SELECT *
FROM (
SELECT a.*,
#rownum := if(#prev_value = authorId,
#rownum + 1,
1) rn,
#prev_value := authorId as prev
FROM answers a,
(SELECT #rownum := 0, #prev_value := '') r
ORDER BY authorId, votes DESC
) T
WHERE T.rn = 1;
OUTPUT
| id | authorId | answer | votes | rn | prev |
|----|----------|--------|-------|----|------|
| 4 | b | x1 | 23 | 1 | b | => only one is show but would
| 2 | a | x2 | 21 | 1 | a | be random unless you specify some rule.
Now for your question you also need to use LEFT JOIN instead of JOIN to get the students without scores.
Something like this
SELECT au.authorId,
COALESCE(t.id, 0) as id,
COALESCE(t.answer , 0) as answer ,
COALESCE(t.votes , 0) as votes
FROM authors au
LEFT JOIN (
SELECT *
FROM (
SELECT a.*,
#rownum := if(#prev_value = authorId,
#rownum + 1,
1) rn,
#prev_value := authorId as prev
FROM answers a,
(SELECT #rownum := 0, #prev_value := '') r
ORDER BY authorId, votes DESC
) T
WHERE T.rn = 1) T
ON au.authorId = T.authorId
OUTPUT
| authorId | id | answer | votes |
|----------|----|--------|-------|
| a | 2 | x2 | 21 |
| b | 4 | x1 | 23 |
| c | 0 | 0 | 0 |
I have these two tables:
popular_song
song_name | rate | country_id
------------------------------
Tic Tac | 10 | 1
Titanic | 2 | 1
Love Boat | 8 | 2
Battery | 9 | 2
country
conutry_id | country
--------------------------
1 | United States
2 | Germany
What I'd like to achieve is to get the most poular song in each country, e.g.:
song_name | rate | country
--------------------------
Tic Tac | 10 | United States
Battery | 9 | Germany
I've tried this query:
SELECT MAX(rate), song_name, country
FROM popular_song ps JOIN country cnt
ON ps.country_id = cnt.country_id
GROUP BY country
But this doesn't work. I've tried looking at questions like "Order by before group by" but didn't find an answer.
Which mysql query could achieve this result?
You can use another self join to popular songs table with the max rating
SELECT ps.*,cnt.country
FROM popular_song ps
JOIN (SELECT MAX(rate) rate, country_id FROM popular_song GROUP BY country_id) t1
ON(ps.country_id = t1.country_id and ps.rate= t1.rate)
JOIN country cnt
ON ps.country_id = cnt.conutry_id
See Demo
There is a trick that you can use with substring_index() and group_concat():
SELECT MAX(rate),
substring_index(group_concat(song_name order by rate desc separator '|'), '|', 1) as song,
country
FROM popular_song ps JOIN
country cnt
ON ps.country_id = cnt.country_id
GROUP BY country;
EDIT:
If you have big tables and lots of songs per country, I would suggest the not exists approach:
select rate, song country
from popular_song ps join
country cnt
on ps.country_id = cnt.country_id
where not exists (select 1
from popular_song ps2
where ps2.country_id = ps.country_id and ps2.rate > ps.rate
);
Along with an index on popular_song(country_id, rate). I recommended the group_concat() approach because the OP already had a query with a group by, so the trick is the easiest to plug into such a query.
Here is another way I'v learned from #Gordon Linoff. Here is that question you could learn too.
SELECT ps.*,cnt.country
FROM
(SELECT popular_song.*,
#rownum:= if (#c = country_id ,#rownum+1,if(#c := country_id, 1, 1) )as row_number
FROM popular_song ,
(SELECT #c := '', #rownum:=0) r
order by country_id, rate desc) as ps
LEFT JOIN country cnt
ON ps.country_id = cnt.conutry_id
WHERE ps.row_number = 1
This is the way of implementing row_number()(Partition by ...) window function in MySql.
You can do this with EXISTS like this:
SELECT rate, song_name, cnt.country_id
FROM popular_song ps JOIN country cnt
ON ps.country_id = cnt.country_id
WHERE NOT EXISTS
(SELECT * FROM popular_song
WHERE ps.country_id = country_id AND rate > ps.rate)
It is not specified in the question whether two songs can be returned per country if their rating is the same. Above query will return several records per country if ratings are not unique at country level.
I Have 2 tables
Users
--------------------------------------------
Uid | Name |
--------------------------------------------
1 | sdfa |
2 | dsf |
And Second Table as
Log
--------------------------------------------
Uid | log | size | file | time
--------------------------------------------
1 | dea | 2 | sadf | timestamp
1 | dea | 2 | sadf | timestamp
2 | eff | 25 | sadf | timestamp
Per user in log table get updated 3 times per 5 seconds.
I need an optimized QUERY to get Name, log,size,file,
time per user in a table. With the latest updated record each user.
i.e. Latest update of user only to be shown not ALL.
I tried
SELECT DISTINCT userid, log, size,file,time FROM log
WHERE userid IN (SELECT uid FROM users) As b ORDER BY time ASC
// PROBLEM here is that for every user order is not the latest
but Order by here orders the new table
So I tried,
SELECT a.userid,a.name, b.log, b.size,b.file,b.time
FROM users a LEFT JOIN ON log b WHERE a.userid=b.userid
ORDER BY b.time ASC
But the interviewer told this is also wrong, please advice.
There's no distinct rank function in MYSQL. But you could utilize a variable in that matter.
Check on SQLFIDDLE HERE for the sample. The time stamp is not formatted, which you may do on your own.
Table users
UID NAME
1 a
2 b
Table logs
UID LOG SIZE FILE TIME
1 lg1 2 f1 20140220173550
1 lg2 2 f2 20140220173551
2 lg3 25 f3 20140220173552
Check on execution plan for each of the answers to find the fastest for your case.
SELECT x.uid, u.name, x.log, x.size, x.file,
x.time FROM users u INNER JOIN
(
SELECT l.uid, l.log, l.size,
l.file, l.time,
#curRank := #curRank + 1 AS rank
FROM logs l, (SELECT #curRank := 0) r
ORDER BY rank DESC
) AS x
ON u.uid = x.uid
GROUP BY u.uid
ORDER BY x.time DESC
Results
UID NAME LOG SIZE FILE TIME
2 b lg3 25 f3 20140220173552
1 a lg2 2 f2 20140220173551
This will give you the latest log entry for each user. If your tables are properly indexed, it should be fast.
SELECT a.*
FROM log a
INNER JOIN
(
SELECT uid, MAX(timestamp) max_date
FROM log
GROUP BY uid
) b ON a.uid = b.uid AND
a.timestamp = b.max_date
join users u on u.uid = a.uid
With Oracle you would have used rank. But assuming that there is no logs with the same timestamp for the same user we can try:
SELECT u.name, l2.log, l2.file, l2.size
FROM (SELECT l1.uid, max(time) as last_log
FROM Log l1
GROUP BY l1.uid ) last_logs
JOIN Users u ON u.uid = last_logs.uid
JOIN Log l2 ON l2.uid = last_logs.uid
AND l2.time = last_logs.last_log
This way we only send the last log for each user.
Disclaimer. I have not tested this query in Mysql.