Select row based on a rank and previously selected row - mysql

I am attempting to do a forum rank system, which can get one record at a time based on the previous record.
The example table is:
| POST_ID | POST_REPLIES |
--------------------------
| 1 | 5 |
| 2 | 2 |
| 3 | 8 |
| 4 | 8 |
| 5 | 12 |
--------------------------
If I do a simple query ORDER BY POST_REPLIES DESC, I get POST_ID 5, 4, 3, 1, 2.
But what I want to do is get onlythe next row (so a single row at a time), and based on the post it is currently at.
For example: if I am currently viewing post #3, there would be a button labeled 'next post with most replies' which would point to post # 4.
I am currently having trouble dealing with duplicates, as I run into a loop between 3 and 4 (3 points to 4, and 4 points to 3 rather than 5)
I had played around with joining the table onto itself and comparing the rows to see which one was greater or less, but since I am using a limit of 1, the row is always 1 and thus useless. So the basic query I had was:
SELECT * FROM posts
WHERE post_id != '$currentPost'
ORDER BY POST_REPLIES DESC, POST_ID DESC LIMIT 1
How can I do this?

The first step you would need would be to "rank" the results. The best way I have found to do this in MySQL is with a variable like so:
SELECT posts.post_id, posts.post_replies, #rank := #rank + 1 AS rank
FROM posts, (SELECT #rank := 0) r
Then you would probably have to nest that query in another one to accomplish what you need. Let me know if that points you in the right direction

SELECT p.*
FROM (
SELECT POST_ID, POST_REPLIES
FROM posts
WHERE POST_ID = #currentId) as cur
JOIN posts p
ON p.POST_REPLIES >= cur.POST_REPLIES
AND p.POST_ID > cur.POST_ID
ORDER BY p.POST_REPLIES DESC, p.POST_ID
LIMIT 1 ;

Limit using a range, for example
limit 0,1
where zero is the starting record and one is the number of records to fetch.
limit 5,1
would get you the sixth record and so on. You can track the page number via post, get, or session and use it to manipulate the query in this way.
It is also common to fetch and store all the records, then present them per page, however this can be problematic if you expect to generate a large number of records.

Related

MySQL query loads forever

I have a table with 1v1 matches like this:
match_number|winner_id|loser_id
------------+---------+--------
1 | 1 | 2
2 | 2 | 3
3 | 1 | 2
4 | 1 | 4
5 | 4 | 1
and I would like to get something like this:
player|matches_won|matches_lost
------+-----------+------------
1 | 3 | 1
2 | 1 | 2
3 | 0 | 1
4 | 1 | 1
My MySQL Query looks like this
SELECT win_matches."winner_id" player, COUNT(win_matches."winner_id") matches_won, COUNT(lost_matches."loser_id") matches_lost FROM `matches` win_matches
JOIN `matches` lost_matches ON win_matches."winner_id" = lost_matches."winner_id"
I don't know what I did wrong, but the query just loads forever and doesn't return anything
You want to unpivot and then aggregate:
select player_id, sum(is_win), sum(is_loss)
from ((select winner_id as player_id 1 as is_win, 0 as is_loss
from t
) union all
(select loser_id, 0, 1
from t
)
) wl
group by player_id;
Your query is simply not correct. The two counts will produce the same same value -- COUNT(<expression>) returns the number of non-NULL rows for that expression. Your two counts return the same thing.
The reason it is taking forever is because of the Cartesian product problem. If a player has 10 wins and 10 losses, then your query produces 100 rows -- and this gets worse for players who have played more often. Processing all those additional rows takes time.
If you have a separate players table, then correlated subqueries may be the fastest method:
select p.*,
(select count(*) from t where t.winner_id = p.player_id) as num_wins,
(select count(*) from t where t.loser_id = p.player_id) as num_loses
from players p;
However, this requires two indexes for performance on (winner_id) and (loser_id). Note these are separate indexes, not a single compound index.
You are joining the same table twice.
Both the alias win_matches and lost_matches are on the table matches, causing your loop.
You probably don't need separate tables for win and losses, and could do both in the same table by writing one or zero in a column for each.
I don't to change your model too much and make it difficult to understand, so here is a slight modification and what it could look like:
SELECT m."player_id" player,
SUM(m."win") matches_won,
SUM(m."loss") matches_lost
FROM `matches` m
GROUP BY player_id
Without a join, all in the same table with win and loss columns. It looked to me like you wanted to know the number of win and loss per player, which you can do with a group by player and a sum/count.

MySQL: Increase table retrieval time with subqueries and links to other tables?

Following example: In my database I have two tables: One that stores user posts and their content and another that stores the likes of other users from these posts. If a user likes a post a new row gets inserted into the likes table.
If I make a SELECT call on the posts table it also returns the number of likes of the respective post using subqueries (SQL Fiddle):
SELECT allPosts.*,
(SELECT COUNT(*) FROM likes WHERE likes.postID = allPosts.id) AS likeCount
FROM posts allPosts
ORDER BY likeCount DESC
LIMIT 3;
| id | content | likeCount |
|----|---------|-----------|
| 2 | Post 2 | 2 |
| 3 | Post 3 | 1 |
| 1 | Post 1 | 0 |
Problem: If, for example, you want to sort by likeCount using ORDER BY, likeCount must also be generated for each individual post at the same time, which is particularly problematic for very large tables with several thousands of posts. For example, it happened to me that a call took up to 10 seconds for a table containing about 2000 posts, which of course is too slow.
How can you solve this problem? How can I sort by likeCount without having to query likeCount for each individual post, but still sort the posts based on their number of likes?
I am grateful for any help!
Correlated subquery could be rewritten as JOIN:
SELECT allPosts.id, allPosts.content,
COUNT(likes.postID) AS likeCount
FROM posts allPosts
LEFT JOIN likes
ON likes.postID = allPosts.id
GROUP BY allPosts.id, allPosts.content
ORDER BY likeCount DESC
LIMIT 3;
SQLFiddle demo

Grab the first row that causes the sum of the previous rows to be greater that 10

I am having an issue that I can't seem to wrap my head around when it comes to creating an effective SQL.
Below is the mysql setup:
id | countValue | name
1 | 1 | b
2 | 1 | b
3 | 4 | b
4 | 6 | b
5 | 1 | b
What I am looking to do is grab the earliest row that the sum(countValue) of the previous rows (ORDER BY id DESC) become greater than 10
So in this case it would return: 3
It would return 3 because:
5.countValue + 4.countValue + 3.countValue = 12
So it would return id=3
My initial try:
SELECT id FROM user WHERE sum(countValue) > 3 ORDER by id DESC
Then I changed to:
SELECT id From users WHERE HAVING SUM(countValue) > 10 ORDER BY id DESC
The second one will only return the id if the individual countValue has more than 10 in it. Where I need the sum of the previous values which is why I am stuck.
Hope this makes sense and would love any help you guys can offer.
What you want is a cumulative or running sum. In MySQL, the best way to do this uses variables:
select u.*
from (select u.*, (#s := #s + countValue) as runningCV
from users u cross join
(select #s := 0) params
order by u.id desc
) u
where runningCV - countValue <= 10 and runningCV > 10;
The variable #s is used to calculate the cumulative sum. The outer where clause returns the first value that crosses the "10" threshold.
You can also do this with a correlated subquery. Unless your data is small, though, that will be expensive.

Return records from first record to get the number of records needed to return

Ok I really don't know how to explain this even in the title but this is what I want.
first I do have 5 records
id | name |
------------
1 | ringo |
------------
2 | nashi |
------------
3 | momo |
------------
4 | manga |
------------
5 | tokyo |
now I produce a random number on what row I will start querying. For example I got a random number of 4 and so I will get rows 4 and 5. But my problem is I need 4 records on every query. So it means I will go back to the first record and get the first two rows.
Is there any possible way that I can go back on the first row if my query results lacks the number of records I want?
This is a connected question Select from nth record and so on in MySQL that shows what I have done so far.
If you sort the table first by records greater than the pivot id, then by id, you need only LIMIT the resultset to the first four records:
SELECT * FROM my_table ORDER BY id >= 4 DESC, id LIMIT 4
See it on sqlfiddle.
Here's what I would do.
SELECT * FROM `your_table`
WHERE `id` >= 3 /* 3 is some random number */
UNION
SELECT * FROM `your_table`
WHERE `id` < 5
LIMIT 4
EDIT: eggyal's question in his comment is something worth considering. My suggestion with that option considered is below.
SELECT * FROM `your_table`
WHERE `id` >= 3 /* 3 is some random number */
UNION
SELECT * FROM (
SELECT * FROM `your_table` LIMIT 4
) a
LIMIT 4

Group by - Overriding default behaviour of deciding row under each group in result

Extending further from this question Query to find top rated article in each category -
Consider the same table -
id | category_id | rating
---+-------------+-------
1 | 1 | 10
2 | 1 | 8
3 | 2 | 7
4 | 3 | 5
5 | 3 | 2
6 | 3 | 6
There is a table articles, with fields id, rating (an integer from 1-10), and category_id (an integer representing to which category it belongs). And if I have the same goal to get the top rated articles in each query (this should be the result):-
Desired Result
id | category_id | rating
---+-------------+-------
1 | 1 | 10
3 | 2 | 7
6 | 3 | 6
Extension of original question
But, running the following query -
SELECT id, category_id, max( rating ) AS max_rating
FROM `articles`
GROUP BY category_id
results into the following where everything, except the id field, is as desired. I know how to do this with a subquery - as answered in the same question - Using subquery.
id category_id max_rating
1 1 10
3 2 7
4 3 6
In generic terms
Excluding the grouped column (category_id) and the evaluated columns (columns returning results of aggregate function like SUM(), MAX() etc. - in this case max_rating), the values returned in the other fields are simply the first row under every grouped result set (grouped by category_id in this case). E.g. the record with id =1 is the first one in the table under category_id 1 (id 1 and 2 under category_id 1) so it is returned.
I am just wondering is it not possible to somehow overcome this default behavior to return rows based on conditions? If mysql can perform calculation for every grouped result set (does MAX() counting etc) then why can't it return the row corresponding to the maximum rating. Is it not possible to do this in a single query without a subquery? This looks to me like a frequent requirement.
Update
I could not figure out what I want from Naktibalda's solution too. And just to mention again, I know how to do this using a subquery, as again answered by OMG Ponies.
Use:
SELECT x.id,
x.category_id,
x.rating
FROM YOUR_TABLE x
JOIN (SELECT t.category_id,
MAX(t.rating) AS max_rating
FROM YOUR_TABLE t
GROUP BY t.category_id) y ON y.category_id = x.category_id
AND y.max_rating = x.rating