Mysql Sub Select Query Optimization - php

I'm running a query daily to compile stats - but it seems really inefficient. This is the Query:
SELECT a.id, tstamp, label_id, (SELECT author_id FROM b WHERE b.tid = a.id ORDER BY b.tstamp DESC LIMIT 1) AS author_id
FROM a, b
WHERE (status = '2' OR status = '3')
AND category != 6
AND a.id = b.tid
AND (b.type = 'C' OR b.type = 'R')
AND a.tstamp1 BETWEEN {$timestamp_start} AND {$timestamp_end}
ORDER BY b.tstamp DESC
LIMIT 500
This query seems to run really slow. Apologies for the crap naming - I've been asked to not reveal the actual table names.
The reason there is a sub select is because the outer select gets one row from the table a and it gets a row from table b. But also need to know the latest author_id from table b as well, so I run a subselect to return that one. I don't want to run another select inside a php loop - as that is also inefficient.
It works correctly - I just need to find a much faster way of getting this data set.

If b.tstamp is unique within b.tid, take OMG Ponies' solution.
Otherwise you could try this solution. It sorts the whole result by b.tstamp DESC and adds a ranking per author_id. The outer selects takes only the row with rank = 1, which is the one with the greatest tstamp per author_id.
SELECT id, tstamp, label_id, author_id
FROM (SELECT id,
tstamp,
label_id,
author_id,
CASE
WHEN #author_id != author_id THEN #row_num := 1
ELSE #row_num := #row_num + 1
END AS rank,
#author_id := b.author_id
FROM a,
b,
(SELECT #row_num := 0, #author_id := NULL) y
WHERE a.id = b.tid
AND (status = '2' OR status = '3')
AND category != 6
AND (b.type = 'C' OR b.type = 'R')
AND a.tstamp1 BETWEEN {$timestamp_start} AND {$timestamp_end}
ORDER BY b.author_id, b.tstamp DESC
) x
WHERE x.rank = 1
LIMIT 500
I have not tried it, so please comment if it does not work.

Try:
SELECT a.id,
b.tstamp,
label_id,
y.author_id
FROM TABLE_A a
JOIN TABLE_B b ON b.tid = a.id
JOIN (SELECT b.tid,
MAX(b.tstamp) 'm_tstamp'
FROM TABLE_B b
GROUP BY b.tid) x ON x.tid = a.id
JOIN (SELECT b.tid,
b.author_id,
b.tstamp
FROM TABLE_B b
GROUP BY b.tid) y ON y.tid = a.id
AND y.tstamp = x.m_tstamp
WHERE status IN ('2', '3')
AND b.type IN ('C', 'R')
AND category != 6
AND a.tstamp1 BETWEEN {$timestamp_start} AND {$timestamp_end}
ORDER BY b.tstamp DESC
LIMIT 500

Related

Assign and display ranks to users from mysql data [duplicate]

This question already has answers here:
Rank function in MySQL
(13 answers)
Closed 4 years ago.
SELECT u.user_id, u.user_uid, s.ostats, s.attack, s.defense
FROM stats s JOIN
users u
ON s.id = u.user_id
ORDER BY s.ostats DESC;
So in above data, "ostats"(overall) is just a sum of attack+defense and by using this query I could display users in descending order of their "ostats" values..
But how do I assign and display rank of each user, like the one with most "ostats" valued user as Rank 1 and the second highest "ostats" valued user as Rank 2 and so on..?
What about using a variable to keep track of the row number?
SET #rank = 0;
SELECT
u.user_id,
u.user_uid,
s.ostats,
s.attack,
s.defense,
(#rank:=#rank + 1) AS rank
FROM stats s
JOIN users u on s.id = u.user_id
ORDER BY s.ostats DESC;
You can assign a row number using variables:
SELECT u.user_id,u.user_uid, s.ostats, s.attack, s.defense,
s.ranking
FROM (SELECT s.*, (#rn := #rn + 1) as ranking
FROM (SELECT s.* FROM stats s ORDER BY s.ostats DESC) s CROSS JOIN
(SELECT #rn := 0) params
) s JOIN
users u
ON s.id = u.user_id
ORDER BY s.ostats DESC;
In the event of ties, this will give different users different rankings. If that is an issue, you can use this modified form:
SELECT u.user_id,u.user_uid, s.ostats, s.attack, s.defense,
s.ranking
FROM (SELECT s.*,
(#rn := if(#o = ostats, #rn,
if(#o := ostats, #rn + 1, #rn + 1)
)
) as ranking
FROM (SELECT s.* FROM stats s ORDER BY s.ostats DESC) s CROSS JOIN
(SELECT #rn := 0, #o := -1) params
) s JOIN
users u
ON s.id = u.user_id
ORDER BY s.ostats DESC;
Of course, in MySQL 8.0, you can use row_number(), rank() or dense_rank() for this purpose.

mysql inner join get only the last 3 equals

I Hope someone could help me.
My Query.
SELECT * FROM `tbl_device`
INNER JOIN `tbl_temperature` ON tbl_device.ID = tbl_temperature.DevID
Result.
How can get something like this as shown in the photo on the link.
I need to get only the last 3 result of the tbl_device.DevID
I apologize for my bad english, it's really hard to explain. Any help is realy much appreciated.
Thanks a lot!
It should be something like
SELECT * FROM `tbl_device`
INNER JOIN `tbl_temperature` ON tbl_device.ID = tbl_temperature.DevID
ORDER BY tbl_temperature.DevID DESC
LIMIT 3 OFFSET 0
The order column can be not the same as in my example. Google MySQL LIMIT OFFSET
Or if you need to get only three last results from one of your tables then join it to another then it will be something like
SELECT * FROM tbl_device INNER JOIN
(SELECT .... FROM other_table ORDER BY your_column DESC LIMIT 3 OFFSET 0) as
T1 ON T1.id = tbl_device.ID
The simplest way is to use ANSI-standard window functions (row_number() in particular). But MySQL does not support those (yet).
In MySQL, probably the best way is to use variables:
SELECT . . .
FROM tbl_device d JOIN
(SELECT t.*,
(#rn := if(#d = t.devid, #rn + 1,
if(#d := t.devid, 1, 1)
)
) as rn
FROM (SELECT t.*
FROM tbl_temperature t
ORDER BY DevID, id DESC
) t CROSS JOIN
(SELECT #d := -1, #rn := 0) params
) t
ON tbl_device.ID = tbl_temperature.DevID
WHERE rn <= 3;
EDIT:
Here is a simpler way to express the logic. It might be performant with the right indexes:
SELECT d.*, t.*
FROM tbl_device d INNER JOIN
tbl_temperature t
ON d.ID = t.DevID
WHERE t.ID >= (SELECT t2.ID
FROM tbl_temperature t2
WHERE t2.DevId = t.DevId
ORDER BY t2.ID DESC
OFFSET 2 LIMIT 1
);
For performance, this can use an index on tbl_temperature(devid, id).

Multiple Counts in MYSQL PHP Query

I'm trying to create a leaderboard but i'm not sure how to do the mysql query.
I would like to count all the levels from a player in the skills table and get the total Level and count all the experience from a player in the experience table and get the Total Exp along with displaying the persons name from the users column.
There is 3 tables factions_mcmmo_users, factions_mcmmo_experience, factions_mcmmo_skills.
This is what i have so far but it doesn't work:
$sql = ("SELECT a.id,
(SELECT COUNT(*) FROM factions_mcmmo_experience WHERE user_id = a.id) as TotalXP,
(SELECT COUNT(*) FROM factions_mcmmo_skills WHERE user_id = a.id) as TotalLevel
FROM (SELECT DISTINCT id FROM factions_mcmmo_users) a LIMIT 10;");
Any help would be very appreciated
EDIT: I have it working now but i'm unsure if its the most efficient way to do things so if anyone could help me out if theres a better way, it would mean a lot.
I would also like to know if it's possible to display the total exp and level with commas if the number is in the thousands for example: total level 5,882 and total xp 582,882
EDIT 2:
I have figured out how to format the numbers but still don't know if my code is efficient
$sql = ("SELECT id, user,
(SELECT FORMAT(Sum(taming)+Sum(mining)+Sum(woodcutting)+Sum(repair)+Sum(unarmed)+Sum(herbalism)+Sum(excavation)+Sum(archery)+Sum(swords)+Sum(axes)+Sum(acrobatics)+Sum(fishing)+Sum(alchemy),0) FROM factions_mcmmo_skills b WHERE b.user_id = a.id) as TotalLevel,
(SELECT FORMAT(Sum(taming)+Sum(mining)+Sum(woodcutting)+Sum(repair)+Sum(unarmed)+Sum(herbalism)+Sum(excavation)+Sum(archery)+Sum(swords)+Sum(axes)+Sum(acrobatics)+Sum(fishing)+Sum(alchemy),0) FROM factions_mcmmo_experience c WHERE c.user_id = a.id) as TotalXP
FROM (SELECT id, user FROM factions_mcmmo_users) a group by id ORDER BY TotalLevel DESC, TotalXP DESC LIMIT 10;");
EDIT 3
Updated code from scaisEdge but was displaying everyones level as 1 and XP as 1, so i changed count(*) changed to sum, added an order By TotalLevel in Descending order and that seems to have worked but i can't get it to display the persons name (user column) in the user table? not sure if i was supposed to change to sum because it didn't work the other way.
$sql = ("SELECT a.id, b.TotalXP, c.TotalLevel
FROM (SELECT DISTINCT id FROM factions_mcmmo_users) a
INNER JOIN (
SELECT user_id, Sum(taming)+Sum(mining)+Sum(woodcutting)+Sum(repair)+Sum(unarmed)+Sum(herbalism)+Sum(excavation)+Sum(archery)+Sum(swords)+Sum(axes)+Sum(acrobatics)+Sum(fishing)+Sum(alchemy) as TotalXP
FROM factions_mcmmo_experience
GROUP By user_id
) b on b.user_id = a.id
INNER JOIN (
SELECT user_id, Sum(taming)+Sum(mining)+Sum(woodcutting)+Sum(repair)+Sum(unarmed)+Sum(herbalism)+Sum(excavation)+Sum(archery)+Sum(swords)+Sum(axes)+Sum(acrobatics)+Sum(fishing)+Sum(alchemy) as TotalLevel
FROM factions_mcmmo_skills
GROUP by user_id
) c on c.user_id = a.id
ORDER BY TotalLevel DESC
LIMIT 10;");
EDIT 4
Everything working but when i try to format the totals using "FORMAT(Sum(Columns), 0) on the inner joins, the EXP Total appears to work but the main Total Level is not displaying results that are over 1,000 and it breaks the leaderboard positioning, it should be sorting them on total level but it appears to be random, when u remove the format,0 it goes back to working
I would like it to display commas if the number number is the thousands for example: Total Level: 5,532 and Total EXP 5882,882
See live demo: http://mcbuffalo.com/playground/leaderboards/server/factions-mcmmo.php
Updated Code trying to use Format:
$sql = ("SELECT a.id, a.user, b.TotalXP, c.TotalLevel
FROM (SELECT id, user FROM factions_mcmmo_users) a
INNER JOIN (
SELECT user_id, FORMAT(Sum(taming)+Sum(mining)+Sum(woodcutting)+Sum(repair)+Sum(unarmed)+Sum(herbalism)+Sum(excavation)+Sum(archery)+Sum(swords)+Sum(axes)+Sum(acrobatics)+Sum(fishing)+Sum(alchemy), 0) as TotalXP
FROM factions_mcmmo_experience
GROUP By user_id
) b on b.user_id = a.id
INNER JOIN (
SELECT user_id, FORMAT(Sum(taming)+Sum(mining)+Sum(woodcutting)+Sum(repair)+Sum(unarmed)+Sum(herbalism)+Sum(excavation)+Sum(archery)+Sum(swords)+Sum(axes)+Sum(acrobatics)+Sum(fishing)+Sum(alchemy), 0) as TotalLevel
FROM factions_mcmmo_skills
GROUP by user_id
) c on c.user_id = a.id
ORDER BY TotalLevel DESC;");
EDIT 5
Changed number with PHP, everything works
Original Images
you could use an couple of inner join
$sql = ("SELECT a.id, a.name, b.TotalXP, c.TotalLevel
FROM (SELECT DISTINCT id, name FROM factions_mcmmo_users) a
INNER JOIN (
SELECT user_id, COUNT(*) as TotalXP
FROM factions_mcmmo_experience
GROUP By user_id
) b on b.user_id = a.id
INNER JOIN (
SELECT user_id, COUNT(*) as TotalLevel
FROM factions_mcmmo_skills
GROUP by user_id
) c on c.user_id = a.id
LIMIT 10

Loop through rows until you find a variable and then count the number of rows

I'm building a rank system, I have many horses and on the horse profiles I want to echo the rank of the horse depending on the amount of wins. 1st, 2nd, 3rd etc.
This is my MySQL table containing the horses, in PHP how would I count the rows to get the rank of the horse?
Here's my table, for instance if I was on "Pauls" profile, how would I echo out that he is in 4th position in the global ranking?
1.
select h.*, #rownum := #rownum + 1 AS rank
from horses h, (SELECT #rownum := 0) r
order by h.first DESC, h.second DESC, h.third DESC
2.
SELECT * FROM
(
select h.*, #rownum := #rownum + 1 AS rank
from horses h, (SELECT #rownum := 0) r
order by h.first DESC, h.second DESC, h.third DESC
) t
WHERE id = 428
Two queries to get position of your search record
set #name='Paul',#nr = 0, #namenr = 0;
select #namenr as nr from
(SELECT #nr:=#nr+1,if(name=#name, #namenr := #nr,#namenr=#namenr),name FROM SOME_TABLE order by first DESC ) subsel where subsel.name = #name;
second query will return one record with nr column which you are looking for.
Depending on your scoring system, this could be the right solution for you:
Say your 'first' means 3 points, 'second' means 2 points and and 'third' means 1 point.
This way you can calculate what horse has the most points based on the total.
Example:
Paul has 2 'first', 0 'second' and 1 'third', scoring him to 7 points.
Fletcher has 2 'first', no 'second' and no 'third', scoring him to 6 points.
Calculate the sum of all the points and order it by that field, DESC.
Example query:
SELECT (`first`*3)+(`second`*2)+(`third`*1) AS `total_score` FROM `horses` ORDER BY `total_score` DESC
Additional 'rank' field
SET #rank=0;
SELECT #rank:=#rank+1 AS `rank`, (`first`*3)+(`second`*2)+(`third`*1) AS `total_score` FROM `horses` ORDER BY `total_score` DESC
With the following query you can have your table, and next to it the rank of each horse:
SELECT *,
#curRank := #curRank + 1 AS rank
FROM (SELECT #curRank := 0) r, horses
order by first desc, second desc, third desc
I am using the (SELECT #curRank := 0) to initialize the curRank within the query
Use Order By on multiple columns according to priority.
$sql = "SELECT
#rownum := #rownum + 1 as row_number
FROM horses
CROSS JOIN (SELECT #rownum := 0) r
WHERE name = "Paul"
ORDER BY
first ASC,
second ASC,
third ASC";
now you can obtain the position value from query for any users.
To get the rank of a particular horse, you need only count the number of records that rank above it; sorting by row constructors is a very concise way of accomplishing this:
SELECT COUNT(*) + 1
FROM horses
WHERE (first, second, third)
> (SELECT first, second, third FROM horses WHERE id = ?)

How to get one user's rank from mysql db?

I have like this table at my mysql db for highscore.
and I got SQL for get rank of all users.
SELECT b.id
, b.name
, #rank_cnt := IF(#prev_score = b.score,#rank_cnt,#rank_cnt+1) AS rank
, #prev_score := b.score AS score
FROM BBR b
CROSS
JOIN ( SELECT #rank_cnt := 0, #prev_score := NULL) i
ORDER BY b.score DESC, b.id DESC
if I run above SQL, I get following result,
But I want to know from here, specific user's rank info only.
If I wrote WHERE name = 'sim' before ORDER BY, his rank become 1.
I expect here '4' as result.
How should I revise?
Thanks much.
SET #rank_cnt := 0;
SET #prev_score := NULL;
SELECT * FROM (
SELECT b.id
, b.name
, #rank_cnt := IF(#prev_score = b.score,#rank_cnt,#rank_cnt+1) AS rank
, #prev_score := b.score AS score
FROM BBR b
ORDER BY b.score DESC, b.id DESC
) AS subQ
WHERE subQ.name = "sim";
If you are using the same connection, you shouldn't need that bogus "JOIN" to initialize your session variables.
You can try below query,
select name
,Find_in_set(score, (select group_concat(distinct score order by score desc) from BBR)) rank
from BBR
where name='sim';

Categories