Mysql Select query - not able to limit records - php

I have a ranking table in which i have all players with their ranks.
id | rank | playername | is_available | ranking_name | ranking_id
-------------------------------------------------------------------
1 | 1 | testname1 | 1 | australia open | 1
2 | 2 | testname2 | 1 | australia open | 1
3 | 3 | testname3 | 0 | australia open | 1
4 | 4 | testname4 | 1 | australia open | 1
5 | 1 | testname5 | 1 | japan open | 2
This table is huge and for each ranking_id there can be more than 500 players.
Now every player can challenge a match to upper players x% above him. This x is set by superadmin. If x = 10, player "testname4" can challenge (num of players in a ranking_id * 10/100) = 4*10/100=0.4 round to 1. so testname4 can challenge one one player above him. But his above player "testname3" is not available. So he should be given next available player. I want the output like
//testname4 can challenge below players
id | rank | playername | status |
--------------------------------------
2 | 2 | testname2 | available |
3 | 3 | testname3 | not available |
What i did:
//testname4 wants to challenge. So i know his rank and other information
$selectSql = mysql_query("SELECT * from rankingTable where ranking_id = 1 AND rank < 4");
This is giving me "testname1" record as well. how can i limit this ? And most biggest problem is i need to show the ranks in ascending order. Its not easy using order by here.

First determine the users to be challenged by performing a SELECT ... WHERE is_available = 1 ORDER BY rank DESC LIMIT ?, then take MIN(rank) to find the highest ranked such user, then use that information to filter the table to users ranked between oneself and that user:
SELECT rankingTable.*
FROM rankingTable, (
SELECT MIN(a_rank) AS lower, b_rank - 1 AS upper
FROM (
SELECT a.rank AS a_rank, b.rank AS b_rank
FROM rankingTable AS a JOIN rankingTable AS b USING (ranking_id)
WHERE ranking_id = ?
AND b.playername = ?
AND a.rank < b.rank
AND a.is_available = 1
ORDER BY a.rank DESC
LIMIT ?
) AS ranks
) AS limits
WHERE rank BETWEEN limits.lower AND limits.upper
ORDER BY rank DESC
See it (including determination of the number of users to be selected) on sqlfiddle.

Try just adding LIMIT 1 or LIMIT 0, 1 (beginning, how many records) to the end of your query

Related

Get rows above and below (neighbouring rows) a certain row, based on two criteria SQL

Say I have a table like so:
+---+-------+------+---------------------+
|id | level |score | timestamp |
+---+-------+------+---------------------+
| 4 | 1 | 70 | 2021-01-14 21:50:38 |
| 3 | 1 | 90 | 2021-01-12 15:38:0 |
| 1 | 1 | 20 | 2021-01-14 13:10:12 |
| 5 | 1 | 50 | 2021-01-13 12:32:11 |
| 7 | 1 | 50 | 2021-01-14 17:15:20 |
| 8 | 1 | 55 | 2021-01-14 09:20:00 |
| 10| 2 | 99 | 2021-01-15 10:50:38 |
| 2 | 1 | 45 | 2021-01-15 10:50:38 |
+---+-------+------+---------------------+
What I want to do is show 5 of these rows in a table (in html), with a certain row (e.g. where id=5) in the middle and have the two rows above and below it (in the correct order). Also where level=1. This will be like a score board but only showing the user's score with the two above and two below.
So because scores can be the same, the timestamp column will also need to be used - so if two scores are equal, then the first person to get the score is shown above the other person.
E.g. say the user is id=5, I want to show
+---+-------+------+---------------------+
|id | level |score | timestamp |
+---+-------+------+---------------------+
| 4 | 1 | 70 | 2021-01-14 21:50:38 |
| 8 | 1 | 55 | 2021-01-14 09:20:00 |
| 5 | 1 | 50 | 2021-01-13 12:32:11 |
| 7 | 1 | 50 | 2021-01-14 17:15:20 |
| 2 | 1 | 45 | 2021-01-15 10:50:38 |
| 1 | 1 | 20 | 2021-01-14 13:10:12 |
+---+-------+------+---------------------+
Note that id=7 is below id=5
I am wondering does anyone know a way of doing this?
I have tried this below but it is not outputting what I need (it is outputting where level_id=2 and id=5, and the other rows are not in order)
((SELECT b.* FROM table a JOIN table b ON b.score > a.score OR (b.score = a.score AND b.timestamp < a.timestamp)
WHERE a.level_id = 1 AND a.id = 5 ORDER BY score ASC, timestamp DESC LIMIT 3)
UNION ALL
(SELECT b.* FROM table a JOIN table b ON b.score < a.score OR (b.score = a.score AND b.timestamp > a.timestamp)
WHERE a.level_id = 1 AND a.id = 5 ORDER BY score DESC, timestamp ASC LIMIT 2))
order by score
If it is easier to output all rows in the table, say where level = 1, so it is a full score board.. and then do the getting a certain row and two above and below it using PHP I'd also like to know please :) ! (possibly thinking this may keep the SQL simpler)?
You can use cte and inner join as follows:
With cte as
(select t.*,
dense_rank() over (order by score) as dr
from your_table t)
Select c.*
From cte c join cte cu on c.dr between cu.dr - 2 and cu.dr + 2
Where cu.id = 5
Ordwr by c.dr, c.timestamp
I would suggest window functions:
select t.*
from (select t.*,
max(case when id = 7 then score_rank end) over () as id_rank
from (select t.*,
dense_rank() over (order by score) as score_rank
from t
where level = 1
) t
) t
where score_rank between id_rank - 2 and id_rank + 2;
Note: This returns 5 distinct score values, which may result in more rows depending on duplicates.
Here is a db<>fiddle.
EDIT:
If you want exactly 5 rows using the timestamp, then:
select t.*
from (select t.*,
max(case when id = 7 then score_rank end) over () as id_rank
from (select t.*,
dense_rank() over (order by score, timestamp) as score_rank
from t
where level = 1
) t
) t
where score_rank between id_rank - 2 and id_rank + 2
order by score;
Note: This still treats equivalent timestamps as the same, but they seem to be unique in your data.

Get neighboring rows (2 above and 2 below) of a certain row in PHP while loop

I have a table like so (after doing a query on it to order it by score):
+---+-------+------+
|id | level |score |
+---+-------+------+
| 4 | 1 | 30 |
| 3 | 1 | 35 |
| 1 | 1 | 40 |
| 5 | 1 | 45 |
| 7 | 1 | 50 |
| 8 | 1 | 55 |
+---+-------+------+
I will output that to php in a while loop. So each row in the while loop will be the same as in the table above.
Essentially what I want to do is show 5 of these rows in a table (in html), with a certain row (e.g. where id=5) in the middle and have the two rows above and below it (in the correct order). This will be like a score board but only showing the user's score with the two above and two below.
E.g. say the user is id=5, I want to show
+---+-------+------+
|id | level |score |
+---+-------+------+
| 3 | 1 | 35 |
| 1 | 1 | 40 |
| 5 | 1 | 45 |
| 7 | 1 | 50 |
| 8 | 1 | 55 |
I am wondering does anyone know a way of doing this in php?
Basically
//select query output is in while loop
//get a certain row of the loop
//get the two rows above it and two rows below it
One method uses a lot of variables:
select t.*
from (select t.*,
lag(id, 1) over (order by score) as prev_id,
lag(id, 2) over (order by score) as prev_id2,
lead(id, 1) over (order by score) as next_id,
lead(id, 2) over (order by score) as next_id2
from t
) t
where 5 in (prev_id, prev_id2, next_id, next_id2, id)
order by score;
An alternative method is something like this:
(select t.*
from t
where t.score <= (select t2.score from t t2 where t2.id = 5)
order by score desc
limit 3
) union all
(select t.*
from t
where t.score > (select t2.score from t t2 where t2.id = 5)
order by score
limit 2
)
order by score;
This exactly syntax may not work in all databases, but the idea can easily be translated in whatever dialect of SQL. This also assumes that the scores are unique.

MySQL - Grouping multiple rows where criteria is the same

I have a table of movie ratings that contains millions of rows containing userid's, movieid's and ratings.
| userId | movieId | rating |
------------------------------
| 1 | 213 | 5 |
| 1 | 245 | 4 |
| 2 | 213 | 4 |
| 2 | 245 | 4 |
| 3 | 657 | 5 |
| 3 | 245 | 5 |
I'm trying to figure out a way of grouping together userId's that contain matching sets of movieId's. Ideally I want the query to only find matches if they have at least 5 movieId's in common and if the rating is above 4, but I've simplified it for this example.
In the instance above, userId 1 and 2 would be the only users that match as they both contain the same movieIds. I need a statement that would essentially replicate this. Thanks in advance for any help.
You can perform a self-join on matching movies, filter out records with uninteresting ratings, group by user-pairs and then filter the resulting groups for only those that have at least the requisite number of matching records:
SELECT a.userId, b.userId
FROM myTable a JOIN myTable b USING (movieId)
WHERE a.userId < b.userId
AND a.rating > 4
AND b.rating > 4
GROUP BY a.userId, b.userId
HAVING COUNT(*) >= 5
select movieId, rating
from tablename
group by movieId
having count(userId) > 1 and rating > 4;
this gives me movieId 245 and rating 5, which should be correct according to your provided example data, have more than 1 userId and a rating greater than 4.

Php/mysql ranking system - Placement inside a result set ordered by column

In a blog-like website, all the users can "star" a news (= bookmark it, mark it as "favourite").
I have a mysql table for stats.
table_news_stats
id_news
total_stars (int) //Total number of users who starred this news
placement (int)
The placement field is intuitive: if you order all the news by the total_stars field you get each news placement. So, the news with most stars will be number 1, and so on.
So, suppose I have 700 records in my table_news_stats, and for each one I have the id and the total_stars count, how can I update the placement field automatically for each record? Which query is faster/better?
Example of the table_news_stats content:
First record (A):
1-3654-?
Second record (B):
2-2456-?
Third record (C):
3-8654-?
If you order the record by stars count:
the sequence of records is C - A - B
So... the result will be:
First record (A):
1-3654-2
Second record (B):
2-2456-3
Third record (C):
3-8654-1
Clarification:
why would I ever need the placement field at all?
It's pretty simple... the placement field will be populated by a cronjob the first day of every month. Basically it will provide a 'snapshot' of the rank of each news in terms of popularity (as it was at the beginning of the current month). As a consequence, thanks to the placement field, I will have the following information:
"The 1st day of this month the 'top starred' news list was like this:
1- News C
2- NewsA
3- News B "
Then, with a query "SELECT * FROM table_news_stats ORDER BY total_stars DESC" I can obtain the new ranking (in real-time).
As a consequence, I will have the following information:
"At the time the page is loaded, the 'top starred' news list is like this:
1- News A
2- News C
3- News B "
Finally, by comparing the two rankings, I obtain the last piece of information:
"News A has gained a position" +1
"News C has lost a position" -1
"News B has no change in position" +0
If there is a better way of doing this, let me know.
I guess you don't need to update the table just:
SELECT *
FROM table_news_stats
ORDER BY total_stars DESC
But if you want to know the place of each one you can:
SELECT *, IF(#idx IS NULL,#idx:= 1,#idx:= #idx+1)
FROM table_news_stats
ORDER BY total_stars DESC
And if you still need to update something like:
UPDATE table_news_stats
SET placement = FIND_IN_SET(id_news,(SELECT GROUP_CONCAT(t.id_news) FROM (SELECT id_news
FROM table_news_stats
ORDER BY total_stars DESC) t ))
SQLFiddle
Consider the following
mysql> select * from test ;
+------+-------------+-----------+
| id | total_stars | placement |
+------+-------------+-----------+
| 1 | 3 | 0 |
| 2 | 6 | 0 |
| 3 | 7 | 0 |
| 4 | 2 | 0 |
| 5 | 9 | 0 |
| 6 | 2 | 0 |
| 7 | 1 | 0 |
+------+-------------+-----------+
Now using the following you can update the placement as
update test t1 join
(
select *,
#rn:= if(#prev = total_stars,#rn,#rn+1) as rank ,
#prev:= total_stars
from test,(select #rn:=0,#prev:=0)r
order by total_stars desc
)t2
on t2.id = t1.id
set t1.placement = t2.rank ;
mysql> select * from test order by placement ;
+------+-------------+-----------+
| id | total_stars | placement |
+------+-------------+-----------+
| 5 | 9 | 1 |
| 3 | 7 | 2 |
| 2 | 6 | 3 |
| 1 | 3 | 4 |
| 4 | 2 | 5 |
| 6 | 2 | 5 |
| 7 | 1 | 6 |
+------+-------------+-----------+
Note that in case of tie will have the same placement.

selecting the minimum amount records where the sum is greater than a certain number

I'm trying to put together a query that for a restaurant reservation system. The idea is that if there is no table big enough to sit the party size then to look through the other free tables and find two tables big enough to be put together to accommodate the party size.
Ideally I would like to be able to select the minimum of tables to as closely match the size of the party.
For example if there is a request for a table of twelve I would like to ideally find two of the tables for six and no more.
This is the query I've tried but it gives an empty result
select tbl_id, sum(max_seats) as sumseats from tbl_list
group by tbl_id having sumseats> 11
I have put a link to sql fiddle to show the table structure
http://sqlfiddle.com/#!2/5a6904/2/0
Try something like the following(You'll require something like PHP in addition to MySQL):
Check if a single table can seat the required number of people. If yes, print all the such tables in ascending order of capacity.
If no single table has capacity greater than or equal to the required capacity, reserve the table with highest capacity and deduct the capacity from the required capacity.
Goto step 1.
Code :
$bookedTables = array();
while ($requiredCapacity > 0) {
$query = "SELECT * FROM (SELECT tbl_id, max_seats FROM tbl_list WHERE max_seats > $requiredCapacity)table1 ORDER BY table1.max_seats ASC";
$result = mysql_query($query);
if (count($result)!=0) {
array_push($bookedTables, $result[0]['tbl_id'];
$requiredCapacity = $requiredCapacity - $result[0]['max_seats'];
}
else {
$query = "SELECT * FROM (SELECT tbl_id, max_seats FROM tbl_list)table1 ORDER BY table1.max_seats DESC";
if (count($result)!=0) {
array_push($bookedTables, $result[0]['tbl_id'];
$requiredCapacity = $requiredCapacity - $result[0]['max_seats'];
}
else {
echo "No more tables left";
break;
}
}
}
I'm not proposing this as a definitive answer, but it's something to think about...
SELECT * FROM tables;
+----+------+
| id | size |
+----+------+
| 1 | 2 |
| 2 | 2 |
| 3 | 2 |
| 4 | 2 |
| 5 | 4 |
| 6 | 4 |
| 7 | 4 |
| 8 | 6 |
| 9 | 6 |
| 10 | 8 |
+----+------+
SELECT *
, x.size + y.size + z.size pax
FROM tables x
LEFT
JOIN (SELECT * FROM tables UNION SELECT 0,0) y
ON y.size < x.size OR (y.size = x.size AND y.id < x.id)
LEFT
JOIN (SELECT * FROM tables UNION SELECT -1,0) z
ON z.size < y.size OR (z.size = y.size AND z.id < y.id)
HAVING pax >= 12
ORDER
BY pax
, x.size DESC
, y.size DESC
, z.size DESC
LIMIT 1;
+----+------+------+------+------+------+------+
| id | size | id | size | id | size | pax |
+----+------+------+------+------+------+------+
| 10 | 8 | 7 | 4 | -1 | 0 | 12 |
+----+------+------+------+------+------+------+

Categories