I'm trying to select the top 3 entries from a table called games that has foreign keys to the players and 2 ints for individual scores for the host and opponent.
The Query:
SELECT
games.id, games.host_score, games.opponent_score, player.name
FROM games, player
WHERE player.id = games.host_player_id
|| player.id = games.opponent_player_id
ORDER BY games.host_score, games.opponent_score DESC LIMIT 3
The query completes but it comes back out of order:
id host_score opponent_score name
17 0 0 Temp2
17 0 0 Temp0
16 770 930 Temp0
When I run a query that doesn't have an OR it works. How can I get this method working?
Also is there a way to set a LIMIT of 50 but not count duplicates?
For example if i wanted a limit of 2 but 3 people have the score 50 and 2 people have the score 20 it would return :
id host_score opponent_score name
17 50 0 Temp2
17 50 0 Temp0
17 50 0 Temp1
17 20 0 Temp3
17 20 0 Temp4
Or would it be better to run it as seperate quesies in php?
If you want to order from highest to lowest, you need to specify it for each field
ORDER BY games.host_score DESC, games.opponent_score DESC
Because when you don't specify the order it assumes you want the ascending order
I think your query is wrong, because one game will be on two rows -- one for the host and one for the opponent.
You want to get both the host and opponent names, so you need to join twice to the player table:
SELECT g.id, g.host_score, g.opponent_score, hp.name as HostName, op.name as OpponentName
FROM games g join
player hp
on hp.id = g.host_player_id join
player op
on op.id = g.opponent_player_id
ORDER BY g.host_score, g.opponent_score DESC
LIMIT 3
You should use OR (instead of ||) in SQL, also add parenthesis to make it readable.
WHERE (player.id = games.host_player_id) OR (player.id = games.opponent_player_id)
To get top 50 scores by the score values, though the total row count returned may be higher.
Use the barebones query below and tweak it to your needs.
SELECT g1.id, g1.host_scores, COUNT(g2.host_scores) AS Rank
FROM games g1
WHERE Rank <= 50
JOIN games g2 ON (g1.host_scores < g2.host_scores) OR (g1.host_scores=g2.host_scores)
GROUP BY g1.id, g1.host_scores
ORDER BY g1.host_scores DESC;
I must add that for such things to avoid complexity you can also get the data to your
application and easily do this in a programming language like Java, PHP etc.
It may result in you making more than one query but is far more simpler and more
maintainable over time.
Related
I'm trying to get the cumulative sum for each user.
related tables(just example):
[user]
id
nickname
A
AA
B
BB
[pointTable] user_id -> [user]id
id
user_id
point
piA
A
10
piB
B
8
[pointHistoryTable] point_id -> [point]id
id
point_id
gain
use
phi1
piA
25
0
phi2
piB
10
0
phi3
piA
0
10
phi4
piB
0
9
phi5
piB
7
0
(For gain-use column, only one of them has a value.)
The result I want:
nickname
current
cGainSum
cUseSum
AA
10
25
10
BB
8
17
9
The query I used(mysql v5.7):
#1
SELECT
user.nickname AS nickname,
pointTable.point AS current,
sub.cGainSum AS cGainSum,
sub.cUseSum AS cUseSum
FROM
(SELECT
point_id, SUM(gain) AS cGainSum, SUM(`use`) AS cUseSum
FROM
pointHistoryTable
GROUP BY point_id) sub
INNER JOIN
pointTable ON pointTable.id = sub.point_id
INNER JOIN
user ON user.id = pointTable.user_id
ORDER BY cGainSum DESC
LIMIT 20 OFFSET 0;
#2
SELECT
user.nickname AS nickname,
pointTable.id AS pointId,
pointTable.point AS current,
(SELECT
IFNULL(SUM(gain), 0)
FROM
pointHistoryTable
WHERE
point_id = pointId AND gain > 0) AS cGainSum,
(SELECT
IFNULL(SUM(`use`), 0)
FROM
pointHistoryTable
WHERE
point_id = pointId AND `use` > 0) AS cUseSum
FROM
pointTable
INNER JOIN
user ON user.id = pointTable.user_id
ORDER BY cGainSum DESC
LIMIT 20 OFFSET 0;
Both work. But sorting takes a long time. (20,000 users)
When sorting with current, #1 takes about 25s and #2 takes about 300ms.
However, when sorting by cumulative sum(cGainSum or cUseSum), #1 takes about 25s again and #2 takes about 50s.
So #1 always causes a slow query, and #2 causes a slow query when sorting by cumulative sum.
Any other suggestions?
++
I'm using this query in node api. The data is sorted by the request query. The request query can be current, cGainSum, or cUseSum.
like this...
SELECT (...) ORDER BY ${query} DESC LIMIT 20 OFFSET 0;
The offset uses the pagination related request query.
(included in the details)
I imagine that without all those sub queries it would run faster with just simple joins and aggregate function if I misunderstood something you are welcome to correct me, this is anyways what I came up with
SELECT user.nickname,pointTable.point,SUM(pointHistoryTable.gain), SUM(pointHistoryTable.use) FROM user
LEFT JOIN pointTable
ON user.id=pointTable.user_id
LEFT JOIN pointHistoryTable
ON pointHistoryTable.point_id=pointTable.id
GROUP BY user.id
ORDER BY ${query} DESC LIMIT 20 OFFSET 0;
EDIT:
Besides improving the query, like not creating to complicated subqueries etc. A very easy way to improve performance is the use of indexes. I would for starters create indexes for all the id's in all the tables. You simple do this by using CREATE INDEX indexname ON tablename (column1,column2, etc) in your case for pointHistory the query would look something like this
CREATE INDEX pointHistoryIndex ON pointHistoryTable ('id','point_id')
I have the following tables:
client_purchases:
id_sale | id_client | timestamp
files_purchases:
id_sale | id_file
So with one purchase of the client he can buy many files and the files can be bought several times.
I select what I want like this:
SELECT cp.id_sale, fp.id_file
FROM client_purchases AS cp
JOIN file_purchases AS fp
ON cp.id_sale = fp.id_sale;
Works just fine. What I get is something like this:
id_sale | id_file
1 1
1 2
1 3
2 1
3 1
Now to make sure that it doesn't take forever to look through my database if it grows I wanted to limit the amount of rows.
SELECT cp.id_sale, fp.id_file
FROM client_purchases AS cp
JOIN file_purchases AS fp
ON cp.id_sale = fp.id_sale
LIMIT 0,25;
Whick returns me 25 rows. But what I acctually want is 25 different "id_sale". So is there a method to tell SQL to count the DESTINCTvalues of a column and stop if that value reaches a specified number? And I do need to be able to set the start and end value of the LIMIT.
You can use JOIN + Subquery
SELECT cp.id_sale, fp.id_file
FROM (SELECT id, id_sale FROM client_purchases ORDER BY id LIMIT 25) AS cp
JOIN (SELECT id FROM file_purchases ORDER BY id LIMIT 25) AS fp
ON cp.id_sale = fp.id_sale
However this may speed up your query or it may make it go even slower. It all depends on what kinds of indexes you have and how many records you have in the table.
What seems fast with 100 records might be slow with 100M records and vice verce.
There is no feature in general. You can do limit the number of ids using a subquery:
SELECT cp.id_sale, fp.id_file
FROM (SELECT cp.*
FROM client_purchases cp
LIMIT 25
) cp JOIN
file_purchases fp
ON cp.id_sale = fp.id_sale ;
Normally, there would be an ORDER BY before the LIMIT so the query returns consistent results.
However, this is not a general solution, because the 25 ids chosen in client_purchases may not match anything in file_purchases (they may match in your case, but perhaps not in general).
I want to gather all the details from a table PROD about rows containing particular triplet-sets of values. For example, I want to get all the data on the rows having columns (ID, NBR AND COP_I) with values (23534, 99, 0232) and (3423,5,09384), etc.
I was wondering about a way to select the triplets rows via a Join, which may be better than the way I am doing it below as that currently does not work.
The following Query produces the required triplets, associated with the top 100 rows:
SELECT ID, NBR, COP_I, SUM(PAD_MN) AS PAD_MN_SUMMED
FROM PROD
WHERE
PROD.FLAG = 0
GROUP BY 1,2,3
ORDER BY 4 DESC, 3,2,1
LIMIT 100 --TOP 100 ROWS
I tried joining to the Query above as follows to get all the details corresponding to those top 100 row triplets:
SELECT PROD.ID, PROD.NBR,PROD.COP_I,PROD.FLAG,PROD.TYPE,PROD.DATE, PROD.PAD_MN
FROM ( SELECT ID, NBR, COP_I, SUM(PAD_MN) AS PAD_MN_SUMMED
FROM PROD
WHERE
PROD.FLAG = 0
GROUP BY 1,2,3
ORDER BY 4 DESC, 3,2,1
LIMIT 100) TAB2
INNER JOIN PROD
ON (PROD.ID = TAB2.ID
AND PROD.NBR = TAB2.NBR
AND PROD.COP_I = TAB2.COP_I)
However, the above query gives me rows not even associated with any of the triplets. I feel like I may be making a mistake with the Join, but I don't know why and how to rectify it. I get a similar issue when using the answer provided below
UPDATE
PROD Table containing 10,000+ rows looks something like:
ID NBR COP_I FLAG TYPE DATE PAD_MN
3423 5 09384 0 BA 14-06-2016 18657.43
546 1098 098 1 CFA 22-03-1998 2394566.92
3423 5 09384 0 AA 28-11-2013 3423534.12
23534 99 0232 0 BA 05-01-2016 7304567.12
Results Required, which is to contain only the top 100 rows information:
ID NBR COP_I FLAG TYPE DATE PAD_MN
23534 99 0232 0 BA 05-01-2016 17370567.09
3423 5 09384 0 AA 28-11-2013 6321009.98
However, the output from my query gives rows, which have triplets (ID,NBR,COP_I) which are not actually outputted from the first Query above that produces the required triplets.
If I correctly understand you this is what is you want
with join
select prod.* from (select id, nbr, cop_i, sum(pad_mn) as pad_mn_total from prod where prod.flag = 0 group by 1,2,3 order by 4 desc,3,2,1 limit 100) as top_prod left join prod using (id, nbr, cop_i);
without join
select prod.* from (select id, nbr, cop_i, sum(pad_mn) as pad_mn_total from prod where prod.flag = 0 group by 1,2,3 order by 4 desc,3,2,1 limit 100) as top_prod, prod where prod.id = top_prod.id and prod.nbr = top_prod.nbr and prod.cop_i = top_prod.cop_i;
Better way is to use join. Before using queries in production mode I strongly recommend to check explain response for understanding how data will be collected by mysql and how your indexes works for each query.
Here you can find some info about join http://dev.mysql.com/doc/refman/5.7/en/join.html
How to use explain described here http://dev.mysql.com/doc/refman/5.7/en/using-explain.html
BTW: Reading manuals is a good way to resolve problems
UPD: after some discussions in comments:
Q: Is there a way to prevent these "grouped" rows from being restored whilst still retrieving the other info required only for the 100 sorted rows?
A: select sum(pad_mn) as pad_mn_total, prod.* from prod where prod.flag = 0 group by id,nbr,cop_i order by 1 desc,cop_i,nbr,id limit 100
I have a query which actually have a sorting using order by clause. i have a table like following...
user_id user_name user_age user_state user_points
1 Rakul 30 CA 56
2 Naydee 29 NY 144
3 Jeet 40 NJ 43
.....
i have following query...
select * from users where user_state = 'NY' order by user_points desc limit 50;
This gives me the list of 50 people with most points. I wanted to give least preference to few people who's id's were known. Incase if i do not have enough 50 records then those id's should come in the last in the list. I do not want the users 2 and 3 to come on top of the list even though they have higher points... those people should come on the last of the list from the query. Is there any way to push specific records to last on result set irrespective of query sorting ?
If you want to move specific records (like user_id = 2 and 3) down to the list; Then you can run below Query:
mysql> select *,IF(user_id=2 or user_id=3,0,1) as list_order from users where user_state = 'NY' order by list_order desc, user_points desc limit 50;
select * from (
select *
from users
where user_state = 'NY'
-- this order by ensures that 2 and 3 are included
order by case when user_id in (2,3) then 1 else 2 end, user_points desc
limit 50
) as top48plus2n3
-- this order by ensures that 2 and 3 are last
order by case when user_id in (2,3) then 2 else 1 end, user_points desc
Edit: changed id by user_id and corrected outside order by (sorry about that)
On the inner select:
By using this case calculation, what you do is ensuring that records with ids equal to 2 and 3 are "important" (firstly ordered in the order by). Those receive 1 while the others receive 2 as order value, only after that points are relevant.
On the outer select:
Records with ids 2 and 3 recieve 2 as order value, while the rest recieve 1. So they go last irrespective of its "default"
Here you have a reduced fiddle http://sqlfiddle.com/#!9/377c1/1
Can someone help me understand why the following query is not offsetting correctly?
It's meant to select all records in the games table, and add a column with a value of 0 or 1 based on whether a record in another table (wishlist) exists with the same gameId #memberId (in plain English, get me all records from games, and mark any game that exists in the wishlists table, under whatever memberId I give you)
SELECT *,
CASE WHEN wishlists.memberid IS NULL THEN 0 ELSE 1 END AS InMembersList
FROM games
INNER JOIN platforms ON games.platformid = platforms.id
LEFT OUTER JOIN wishlists ON games.id = wishlists.gameid and wishlists.memberid = #memberId
WHERE platforms.platformUrlId = #platformUrlId
ORDER BY releaseDate DESC
LIMIT 1,8
When I change the offset from 1 to 2, or 3, or whatever, many of the same records appear, which does not make any sense. Where am I going wrong?
Schema:
platforms(id, platform)
members(id, name)
games(id, platformId, releaseDate)
wishlists(id, memberId, gameId)
LIMIT 1,8 means start from row number 1 (they start from 0) and fetch 8 rows. So LIMIT 2,8 will give you 8 rows starting from row 2 - seven of which will be the same as with LIMIT 1,8