Mysql - How to get a row number after Order by? - php

Let's say I have a table with the following columns:
p_id
userid
points
Let's say these columns have over 5000 records. So we actually have users with points. Each user has an unique row for their point record. Imagine that every user can get points on the website by clicking somewhere. When they click I update the database with the points they get.
So we have a table with over 5000 records of people who have points, right? Now I would like to order them by their points (descending), so the user with the most point will be at the top of the page if I run a MySQL query.
I could do that by simply running a query like this:
SELECT `p_id` FROM `point_table` ORDER BY `points` DESC
This query would give me all the records in a descending order by points.
Okay, here my problem comes, now (when it is ordered) I would like to display each user which place are they actually. So I'd like to give each user something like this: "You are 623 of 5374 users". The problem is that I cannot specify that "623" number.
I would like to run a query which is order the table by points it should "search" or count the row number, where their records are and than return that value to me.
Can anyone help me how to build a query for this? It would be a really big help. Thank you.

This answer should work for you:
SET #rank=0;
SELECT #rank:=#rank+1 AS rank, p_id FROM point_table ORDER BY points DESC;
Update: You might also want to consider to calculate the rank when updating the points and saving it to an additional column in the same table. That way you can also select a single user and know his rank. It depends on your use cases what makes more sense and performs better.
Update: The final solution we worked out in the comments looked like this:
SELECT
rank, p_id
FROM
(SELECT
#rank:=#rank+1 AS rank, p_id, userid
FROM
point_table, (SELECT #rank := 0) r
ORDER BY points DESC
) t
WHERE userid = intval($sessionuserid);

Row number after order by
SELECT ( #rank:=#rank + 1) AS rank, m.* from
(
SELECT a.p_id, a.userid
FROM (SELECT #rank := 0) r, point_table a
ORDER BY a.points DESC
) m

For some reason the accepted answer doesn't work for me properly - it completely ignores "ORDER BY" statement, sorting by id (primary key)
What I did instead is:
SET #rn=0;
CREATE TEMPORARY TABLE tmp SELECT * FROM point_table ORDER BY points DESC;
SELECT #rn:=#rn+1 AS rank, tmp.* FROM tmp;

Add a new column for position to the table. Run a cron job regularly which gets all the table rows ordered by points and then update the table with the positions in a while loop.

Related

How to retrieve nested join select statement data from query

I have the following query and am unsure of how to retrieve the 'note' from the nested join query.
This Left join selects the latest note for this customer, but I am not sure how to echo this data...
LEFT JOIN (SELECT note AS latestnote, timestamp, renewalid FROM renewal_note ORDER BY timestamp DESC LIMIT 1) AS n ON n.renewalid=renewal.id
Full Query:
SELECT renewal.id AS rid, renewal.personid, renewal.enddate, renewal.assettype, renewal.producttype, renewal.vrm, renewal.make, renewal.model, renewal.submodel, renewal.derivative, renewal.complete, person.forename, person.surname, person.company, appointment.id AS appid, appointment.renewalid,
(SELECT COUNT(complete) FROM renewal WHERE complete=1 && enddate BETWEEN '".$month_start."' AND '".$month_end."' && dealershipid='".$dealership_id."' && assettype='U' && producttype!='CH' && complete=1) AS renewedcount
FROM renewal
LEFT JOIN person ON person.id=renewal.personid
LEFT JOIN appointment ON appointment.renewalid=renewal.id
LEFT JOIN (SELECT note AS latestnote, timestamp, renewalid FROM renewal_note ORDER BY timestamp DESC LIMIT 1) AS n ON n.renewalid=renewal.id
WHERE enddate BETWEEN '".$month_start."' AND '".$month_end."' && renewal.dealershipid='".$dealership_id."' && assettype='U' && producttype NOT LIKE '%CH%'
ORDER BY enddate ASC
The Below is currently what is returned in each loop (which is working fine), and I can access as normal; $row['COLUMNNAME'].
rid
personid
enddate
assettype
used/new
producttype
vrm
make
model
submodel
derivative
complete
forename
surname
company
appid
renewalid
renewedcount
BUT I also need to be able to get the NOTE from renewal_note (the third LEFT JOIN).
I dont have issues with normal inner joins, But I have never created a query with subqueries, so struggling to echo this data out within the php loop.
(Just to point out that the only reason I am nesting a query is that I need only the latest note for each customer to be returned.)
I have tried $row['latestnote'], (with no success) and I am sure this is definitely not the way to access this data.
Could someone please point me in the right direction?
UPDATE
With updates from the comments, I have tried a much more simplified query (with ALL columns included so can't miss anything out) specifically targetting the subquery:
SELECT
*
FROM
renewal
LEFT JOIN(
SELECT
*
FROM
renewal_note
ORDER BY
TIMESTAMP
DESC
LIMIT 1
) AS n
ON
n.renewalid = renewal.id
But this still returns NULL for every column on the renewal_note.
90% of 'renewal' records have a note linked to them in the renewal_note table, but none showing.
renewal table has a unique primary key; ID.
renewal_note table links via column name: renewalid.
I think I have sussed this out.
Thanks to the commenters :)
Purely posting this for anyone else with similar questions/issues.
To get data from a LEFT JOIN(SELECT... statement; include the alias into the outer SELECT statement. Then you can use $row['columnname'] as normal.
LIMIT 1 on the subquery returned only 1 record for the entire query. Remove LIMIT 1 to show all results for the subquery dependant on your 'link' to the main query.
From what I have tested, ORDER BY seems to be working, but not sure if this is a fluke, or if it just sorting naturally by the primary key; ID

SQL: Delete duplicated rows? (PHP)

I have the following database and want to delete the red ones because they are doubouled. So I have to check every row if another row is matching by pid, price, price_old, link and shop.
But how can I check that and how can I delete it then?
Maybe an easier way would be to generate a id from the values inside each row. So if the values inside a row would be equal also the id would be equal and who have only one value to compare with the other id's.
Is that a better way? - If yes, how can I do that?
Greetings!
Do the fact you have no way for get thi distinct row you could add uniqie id using
ALTER TABLE my_table
ADD id int NOT NULL AUTO_INCREMENT
Once done you could use not in where the id are not the min grouped by the value you need for define the duplication
delete from my_table
where id NOT in ( select min(id) from my_table
group by shop, link
)
The simplest way is to run a distinct query:
select distinct pid, price, price_old, link, shop
from t;
You can create a new table using into. That is the simplest way. Because all columns are the same, MySQL doesn't offer a simple method to delete duplicate rows (while leaving one of them).
However, it is possible that your current results are generated by a query. If so, you can just add select distinct to the query. However, it would be better to fix the query so it doesn't generate duplicates. If this is the case, then ask another question with sample data, desired results (as text, not an image), and the query you are currently using.
Test this first on a test table:
DELETE t1
FROM t t1, t t2
WHERE t1.id > t2.id AND t1.price = t2.price
AND t1.link = t2.link AND t1.shop = t2.shop
AND t1.price_old = t2.price_old;
Basically you are removing the one with the highest ID if those parameters are equal
select * from
(select pid, price, price_old, link ,
row_number() over(partition by pid, price, price_old, link, shop order by pid) as rank
from my_table) temp
where temp.rank = 1
This Query will group by all the columns first and rank them. Duplicate rows will have rank > 1. It does not matter we take first or second row as both are copy of each other. We just take rows with rank 1. Rows that are not duplicate will also be having rank 1 and hence won't be neglected.
One more way to this is by using union.
select * from my_table UNION select * from my_table

Join multiple tables and order by sum of row

Recently began working on a matchmaking system, where in this system there are 2 tables. One table is for matches and one table is for ranks.
The table for ranks starts off like this
table_ranks:
date
playeruid
rank
table_matches:
date
playeruid
playerpoints
I'm trying to now order the player by their points. However, in order to do this, I have made a query:
SELECT * FROM table_ranks ORDER by rank DESC
But now what I realize, is that I need to add the playerpoints on top of the rank. So basically, I need to add playerpoints to the player's current ranking. So, if I had this for an example row:
table_ranks:
2/20/15
Player1
56
table_matches:
2/27/15
Player1
5
I would need to build a query that takes the 56 of player1, looks for player1 in the matches and anywhere it sees it, it would need to add his 5 points making it a sum of 56. Once this is determined it would ORDER by this value in order to determine who is ranked with what. How do I begin my query? I understand that in order to join the tables, I need to start off like this:
"SELECT table_ranks., table_matches. from table_ranks, table_matches ORDER by RANK..."
Then to finish it,I would have to take the current value of the rank, then grab the specific player it's referring to and take all the matches and add up all the playerpoints to his rank then to determine how to order it by.
Try this:
SELECT r.playeruid, r.date AS rank_date, m.date AS macthes_date,
(r.rank + m.playerpoints) AS total_points
FROM table_ranks r INNER JOIN table_matches m ON r.playeruid = m.playeruid
ORDER BY total_points DESC
This query assumes that playeruid is unique in both tables.
Try the following query. I tested on a similartable structure and it should work
SELECT * , playeruid AS player_id, (
SELECT SUM( playerpoints )
FROM `table_matches`
WHERE playeruid = player_id
) AS points
FROM table_ranks
ORDER BY points DESC

Users Rating update, recalculation

I have a table with fields id, votes(for each users), rating.
Task: Counting user rating based on votes for him and for others. that is, each time i update the field votes needed recalculation field rating.
Which means some can be on the 3rd place. voted for him and that he would be stood up to 2rd place, and the other vice versa - from 2 to 3. (in rating fiels)
How to solve this problem? Each time update the field to count users ratings on php and do a lot of update query in mysql is very expensive.
If you want to get the ratings with a select without having a rating column, then this is the way. However from a performance perspective I cannot guarantee this will be your best option. The way it works is that if two users have the same amount of votes they will have the same rating and then it will skip ahead the necessary number for the next different rating:
set #rating:=0;
set #count:=1;
select id,
case when #votes<>votes then #rating:=#rating+#count
else #rating end as rating,
case when #votes=votes then #count:=#count+1
else #count:=1 end as count,
#votes:=votes as votes
from t1
order by votes desc
sqlfiddle
This gives you an extra column which you can ignore, or you could wrap this select in to a subquery and have:
select t2.id,t2.votes,t2.rating from (
select id,
case when #votes<>votes then #rating:=#rating+#count
else #rating end as rating,
case when #votes=votes then #count:=#count+1
else #count:=1 end as count,
#votes:=votes as votes
from t1
order by votes desc) as t2
but the sqlfiddle is strangely giving inconsistent results so you'd have to do some testing. If anyone knows why this is I'd be interested in knowing the reason.
If you want to get the rating for just one user then doing the subquery option and using a where after the from should give you the desired result. sqlfiddle - but again, inconsistent results, run it a few times and sometimes it gives rating as 10 other times as 30. I think testing in your db to see what happens will be best.
Well it depends on a lot of factors
Do you have a large system that is growing exponentially?
Do you require the voting data for historical reporting?
Do users need to register when they vote?
Will this system be use only for one voting type throughout the system life cycle or will more voting on different subjects take place?
If all of the answers are NO then your current update method will work just fine. Just ensure that you apply best coding and MySQL table practices anyway.
Let assume most or all your answers were YES then I would suggest the following:
Every time a vote takes place INSERT the record into your table
Using INSERT, add a timestamp, user id if not possible then maybe an ip address/location
Assign a subject id as foreign key from the vote_subject table. In this table store the subject and date of voting
Now you can create a SELECT statement that can count the votes and calculate the ratings. The person top of the vote count list will get rating 1 in the SELECT. Furthermore you can filter per subject, per day, per user and you should also be able to determine volume depending on the result required.
All this of course dependent on how your system will scale in future. This might be way overkill but something to think about.
Yes aggregations are expensive. You could update a rank table every five minutes or so and query from there. The query as you probably already now is this:
select id, count(*) as votes
from users
group by id
order by votes desc
Instead of having the fields id, votes and rating, alter the table to have the fields id, rating_sum and rating_count. Each time you have a new rating you quering the database like this:
"UPDATE `ratings` SET `rating_count` = `rating_count` + 1, `rating_sum` = `rating_sum`+ $user_rating WHERE `id` = $id"
Now the rating is just the average -> rating_sum / rating_count. No need to have a field with the rating.
Also, to prevent a user rate more than one times, you could create a table named rating_users that will have 2 foreign keys the users.id and ratings.id. The primary key will be (users.id, ratings.id). So each time a user tries to rate first you check this table.
I would recommend doing this when querying the data. It would be much simpler. Order by votes descending.
Perhaps create a view and use the view when querying the data.
You could try something like this:
SET #rank := 0
select id, count(*) as votes, #rank := #rank + 1
from users
group by id
order by votes desc
Or
SET #rank := 0
select id, votes, #rank := #rank + 1
from users
order by votes desc

MYSQL position of row by order

I have had a look through google results, but can't find a simple answer, I have a table
id, points (and more fields, but they dont affect anything)
How would I go about Getting position of record 24(id) ordered by points DESC?
I'm not sure I've understood your question
select * from table order by points desc limit 23,1
Select Count(*) from (
Select ID from UnNamedTable A where A.points>(Select B.Points from UnNamedTable B where B.ID=24)
)
Are you trying to figure out out what place a user is in based on a points system?
If so then just find how many users have more points than a particular user then add 1.
SELECT COUNT(*) + 1 AS 'position' FROM table WHERE points >
( SELECT points FROM table WHERE id = someIDhere )
You could create a variable to keep track of your row position after sorting. Something like this would work:
select #rownum:=#rownum+1 ‘rank’, p.* from table1 p, (SELECT #rownum:=0) ID order by Points desc limit 10;

Categories