mysql pagination to display a page with a specific row in it - php

Now I have a question on mysql paging.
A user's record is displayed on a table with many other user's record. and the table is sorted/paged.
Now i need to display the page that containing the user's row directly after the user login. How can I achieve this?
A simple thought would be firstly find out the rownum of the user, then do the paging accordingly, but I'm wondering if there are better ways to do it.
thanks.

Sample table for discussion
create table t_users (id int auto_increment primary key, username varchar(100));
insert t_users(username) values
('jim'),('bob'),('john'),('tim'),('tom'),
('mary'),('elise'),('karl'),('karla'),('bob'),
('jack'),('jacky'),('jon'),('tobias'),('peter');
The query to show the page on which the user is on, not specifically putting the user to the top of the page (which would have been a lot easier)
select username, id
from
(select count(*) pos
from t_users
where username <= 'tobias') pos,
(select #row:=#row+1 row, u.*
from (select #row:=0) initvars, t_users u
order by u.username, u.id) numbered
where floor((numbered.row + 3)/4) = floor((pos.pos+3)/4);
Notes:
The page size here is 4, the number 3 is the result of taking 1 off the page size
The username of the user who just logged in is 'tobias', which is used in the first sub-query
There had better be an index along the ORDER BY clause (in this case on username)
This will cause a table scan to fully row-number the last subquery

select count(*)
FROM table
WHERE [sort_by_field] < [actual_records_value]

Related

Simplifing a mysql query

Scenario
I have three elements in this problem. One is an array of ids in this format: (1,3,5,6,8). That array is a list of id of users I want to display. The second element is table that contains user information something simple like: id name surname email. The third element is a second table that contains users configuration. There are two parameters in that last table, one is lid, and the other is usraction (among others, but the important are those two). lid represent a permission type and usraction the value, if the user wants his data to be public there will be a row on that table where lid=3 and usraction="accepted", also I register the datetime of the action every time the user changes this permission, so each time he change it a new row is added, and in order to retrieve the actual state of the permission i have to retrieve the last row for the user an check the value of usraction like this:
SELECT * FROM legallog WHERE uid = '.$user['id'].' AND lid = 3 ORDER BY ABS(`registration` - NOW()) LIMIT 1
Then in php:
if($queryresult && $queryresult[0]['usraction']=='accepted') //display data
The problem
In the scenario id described how im getting the actual state of the permission set by one user at the time, the problem now is I want to sort of clean an array of ids in one or two sql calls. Lets say I want to print the user information of 4 users, one function gives me the ids in this format: (2,6,8,1), but those users may not want to display their information, using the query I showed before I can make a call to the sql server for each user and then generate a new array, if the users who authorize are 1 and 8 the result array will be (8,1).
The think is whit an array of 100 users I will make 100 calls, and I dont want this, is there a way to solve this in one or two querys?
A query such as this gets you the information you want:
select u.*,
(select usraction from configuration c where c.userid = u.userid and c.lid = 3 order by datetime limit 1
) as lastLid3Action
from users u
where u.userid in (1,3,5,6,8)
If you only want "accepted" values, then make this a subquery:
select t.*
from (select u.*,
(select usraction from configuration c where c.userid = u.userid and c.lid = 3 order by datetime limit 1
) as lastLid3Action
from users u
where u.userid in (1,3,5,6,8)
) t
where LastLid3Action = 'Accepted'
As I understand you have two tables in the first table, where the user information is stored; and the second table, where the user permission is stored. And you want to get information from the first table using permission from the second table, then you need this query:
SELECT a.*
FROM first-table-name AS a
RIGHT JOIN second-table-name AS b a.uid = b.lid
WHERE uid in (1,3,5,6,8) AND usraction='accepted'
ORDER BY ABS)
LIMIT 1
You question is somewhat vague, but if you are asking how to select a number of records when you have a list of ids, the answer is:
select column, list, goes, here from tablename
where id in (1,5,8,12,413);
That will get you the values of the columns you list for just the records that match your array of ids.

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

Using an array in an SQL query

Okay, so I want to have a news feed on my website. I have 3 tables named Users, Follow, and Posts. Basic user data goes into the Users table, who is following who is stored in the Follow table, and whatever the user posts goes into Posts. Now, I know how to select every post from a database table and limit it using the WHERE clause. But how would I write my query to select all all of the posts from only user's they are following, and display them by DESC date? Thanks.
Here's a general layout for you, make three tables like you mentioned, I've outlined below just as an example
[TABLES]
users
followers
posts
In the users table you should have at least columns like userid (auto incremented value / primary key).
The followers table should have something like userid which would match to the users table userid, a followid column which would also have the id # for the follower from the users table.
Then for your posts table you would want to have userid too so when each post is made it would have the id from users table.
Then you would need to do something like:
SELECT p.*
FROM posts AS p
WHERE p.userid IN (SELECT followid FROM followers WHERE userid = ###)
ORDER BY p.date DESC
Now it really depends on how you are getting the users id to figure this out. If your passing the users id using a session after they logged in similar to something like Facebook, then you could change userid = ### to something like userid = ".$_SESSION['userid']." But again it really depends on how you pass the users id but the above should at least get you somewhat started.
Make sure to put indexes on the userid, followid columns so that when the table becomes larger it will do the joins quickly.
An alternative to #Shane's answer is to use the JOIN operator:
'SELECT p.* // I prefer to name all the fields, but for brevity's sake I'll use the shorthand
FROM Posts AS p
INNER JOIN Follow AS f ON p.userid = f.followid
WHERE f.userid = '.$selectedUserID.'
ORDER BY p.date DESC;'
For an inputed User ID ($selectedUserID), it will find all User ID's of the people they follow (matching follow ID to user ID on the Follow x-ref table) and then find their respective posts from the Post table in descending order by date. It will return empty if they do not follow anyone or the people they follow have no posts.
I also hope I do not need to remind you to sanitize your input to the database and escape your output to the web.
Is this what you're looking for?

Optimise & improve performance of this MYSQL query

SELECT u.id, u.honour, COUNT(*) + 1 AS rank
FROM user_info u
INNER JOIN user_info u2
ON u.honour < u2.honour
WHERE u.id = '$id'
AND u2.status = 'Alive'
AND u2.rank != '14'
This query is currently utterly slowing down my server. It works out based on your honour what rank you are within the 'user_info' table which stores it out of all our users.
Screenshot for explain.
http://cl.ly/370z0v2Y3v2X1t1r1k2A
SELECT u.id, u.honour, COUNT(*)+1 as rank
FROM user_info u
USE INDEX (prestigeOptimiser)
INNER JOIN user_info u2
ON u.honour < u2.honour
WHERE u.id='3'
AND u2.status='Alive'
AND u2.rank!='14'
I think the load comes from your join condition '<'.
You could try to split your query or (or if you prefer a subquery) and use the honour index for the count.
SELECT id, honour INTO #uid, #uhonour
FROM user_info
WHERE id = '$id';
SELECT #uid, #uhonour, COUNT(honour) + 1 as rank
FROM user_info
WHERE status = 'Alive'
AND rank != '14'
AND #uhonour < honour;
Firstly, you should add a group by clause so that your query makes sense.
Secondly, you should change the status column to hold an integer to make the index smaller.
Thirdly, you should create an index on id and status like this:
alter table user_info add index idxID_Status (id, status)
Finally, to obtain ranks you should take a look at this answer. Additionally you should add a way to order them... getting a rank without order is not really a rank.
As we can see from explain, MySQL uses the wrong index here. To start with, just drop all indexes and create a new one, containing at least these two fields: Id and Honour. It should boost up performance considerably.
ALTER TABLE user_info ADD INDEX myIndex (id, honour);

Save the rank in the DB

I'm using this query to classify pages for which users vote for :
SELECT p.page_ID , h.point
FROM pages p
INNER JOIN history h ON h.page_ID=p.page_ID
ORDER BY h.point DESC
So I know how to display my pages ranking, but I'd like to save the rank of each page in my table. How do ?
Assuming you have in PHP $rank as the rank value and $pageid as the page_ID you wish to update (and assuming they're both integers not requiring quotes):
UPDATE history SET point=$rank WHERE page_ID=$pageid;
Or if the page does not already exist in the history table:
INSERT INTO history (page_ID, point) VALUES ($pageid, $rank);

Categories