I'm developing a blog system with php and mysql with the following db structure:
Article
-id
-firstMessage
-lastMessage
-body
Comment
- id
- article_id
- publiched_date
- body
The idea here is make use of pagination, where the article with a lot of comments shows a link tree like [first][1][2][3][last], 10 comments by page. Everything goes fine, I have create a nice sql that select 10 messages according to the page number by url:
example.com/?article=3&page=2
Where is the ploblem? Well, supponse that I have this url in my homepage:
example.com/?article=3&message=3565
According to the url above, How can I determinate the page number where this message is? Do you have any idea to guide me to the right direction?
Edit
The messages ids are not consecutives, for example, an article could have the comments: 125, 364, 561, 1522
If you show 10 comments per page and request message 3565, you can do this:
$pageNumber = floor($_GET['message'] / 10) + 1;
EDIT
Thanks #Alix.
EDIT #2
After the edit made to the OP, without seeing what the database structure looks like, worst-case scenario, you'd have to fetch the whole list of comments as it would appear on the site and find the index of the message you're looking for.
I realize that's not necessarily what you wanted to hear, but there's no real other way to know without seeing what your database looks like.
Basically, select the comments from the same article, sorted by ID (or another column if the id can be out of order--non-consecutive is fine), and do a little math with the result. Here's the code (demo):
SELECT (
SELECT CEILING((count(*) + 1) / 10)
FROM `Comment`
WHERE `id` < `comment`.`id`
AND `article_id` = `comment`.`article_id`
) AS `page`
FROM `Comment`
WHERE `id` = ?
AND `article_id` = ?
Simply plug in the comment ID and article ID where the ? are (or, even better, use this exact code in a prepared statement). If you change the number of comments per page, make sure you change the 10 in the query as well.
For this query, you just need an index on article_id (and a PRIMARY index on id).
I guess you have to make a query, something like that should work :
SELECT CEIL((COUNT(id) + 1) / $nb_message_per_page) AS page_for_message
FROM comment
WHERE article_id = $article_id
AND published_date < (SELECT published_date FROM comment WHERE id = $message_id)
Depending of the sorting choose for displaying comments you have to change the < for a >, that query assume a published_date DESC sorting
PS: I don't know if it's a typo or not but you have write publiched_date in you DB schema
EDIT
If no sorting are made, rows are probably sort by PRIMARY KEY which will be like a published_date DESC sorting
EDIT 2
As #bfrohs says this query give inaccurate results (for one case but it will happen) if the test (<) is on published_date (or any other column containing non-unique data) instead of id.
As there are no ordering, using id is a better solution.
You need to set the number of items to display per page and use that to divide the messages into pages
It's rather non-trivial to go from a message-number back to a page. Easiest method is to simply pass the page number in to the message reading script, so you can simply embed that page number in your "back" link, eg...
messages.php:
1234
readmessage.php:
Back
this'll save you the trouble of having to calculate which page you came from, since you simply carry the page number along with you.
Related
OK, my problem is little bit specific and harder to explain. So I will try to simplify as much as possible.
I have MySQL table with "hints" that are beeing shown on website. These hints are ordered by int in column "order". So table looks like this: id(int autoincrement), hint (varchar), order(int).
Now when first hint is displayed to user, it is displayed until user acknowledges it and then next hint is displayed and so on.
I came up with query, which gets me next hint based on which hint specific user acknowledged last time:
SELECT hint FROM hints WHERE `order` > $last_seen_item_order ORDER BY `order` DESC LIMIT 1
This works fine. However we also need to add sometimes new hint and it's not usually added as the last hint but somewhere in between. So for example, user has seen last hint with order #6, but we added new hint at position e.g. #3. And it will never be displayed to this user, because we have saved for him that he has seen hint #6.
Is there a way how to manage this ? Possible with only by one or two MySQL queries ?
Thank you for any help and hints in advance.
EDIT: Each user has its own "seen-state". We keep that simply in PHP $_SESSION['last_seen_item_order']
You can't manage it by this logic.
For this, you need to maintain an extra column - seen
You can set this to 1 if the user have seen the hints
So your query would be -
SELECT
hint
FROM
hints
WHERE
`order` > last_seen_item_order
OR seen = 0
ORDER BY
CASE WHEN `order` > last_seen_item_order THEN `order` END DESC
CASE WHEN `order` <= last_seen_item_order THEN `id` END ASC
LIMIT 1
NOTE - This is my suggestion for doing that way.You can have number of ideas of doing it.
Edit -
If you want to maintain user wise hints, then you probably have 2 options to maintain seen.
Store user wise json for hints seen by user.
Make a separate table user_hints_see with columns id(Autoincrement), user_id, hint_id, seen.
I've searched everywhere else, but I could not find any reference or tutorials that shows you or explain a best way to show top comments (like the one on facebook, or youtube).
I have accomplished fetching records (20 top comments that is order by their votes). But where I always get stuck at is how am I gonna supposed to fetch the next 20 top comments
Below is what my tables looks like:
comments table
id | comments | comment_id
1 Hi nj3b21das
2 Cool jh3lkjb32
3 How are you? bn32j1343
4 What's up? 3kl213543
votes_comments table (1 is equal to thumbs up, and -1 is equal to thumbs down)
id | user_id | comment_id | votes
1 4326542 nj3b21das 1
2 2356453 jh3lkjb32 -1
3 8764354 bn32j1343 1
4 3213543 3kl213543 1
Then I combined these two tables to get the top comments by using the query below:
SELECT `comments`.comments, SUM(`votes_comments`.votes) AS total_votes
FROM comments
LEFT JOIN `votes_comments `
ON `votes_comments`.comment_id = `comments`.comment_id
GROUP BY comment_id
ORDER BY total_votes
DESC
LIMIT 20
The query above will fetch the first 20 top comments. But what if I want to fetch the next 20 results using ajax and leave the previous records displayed, what is the best option for this?
Note: Keep in mind that the votes are changing constantly. Therefore, fetching the next top 20 comments will might result a duplication of the comments that has been fetched already. What is the best way to handle this (Like Facebook, Youtube etc..).
There are 2 ways i can think of.
One is, you can store the current shown comment ids in an array and pass those with your ajax call and then filter in SQL statement exclude those comment ids and fetch the rest of the top comments and once you get the response, append those ids to the same array and you can continue with this.
Or
You define a timestamp on page load that indicates the server time (Ex: var loadTimeStamp = "<?php echo time(); ?>") and then pass this value along with limit as part of your Ajax call.
Then on the server side, you can exclude any comments that were added after this time, hence preserve the comments list. But for this to work, you need to store the time when people comment and looking at the AJAX calls facebook makes for fetching more comments, they seem to be following this type of method as i can see they pass timestamp with the call (I might be wrong, but their AJAX calls pass timestamp, so I'm assuming..)
With this method, you can go one step further and use this time along with a basic ajax long polling technique to notify the user of any new comments since the page was loaded/last loading of new comments, similar to Facebook and Twitter feeds.
Hope you got it.
You just have to precise from which response you want to begin in the LIMIT By the way I added votes_comments. inside GROUP BY to avoid ambiguous error in MySQL.
SELECT `comments`.comments, SUM(`votes_comments`.votes) AS total_votes
FROM comments
LEFT JOIN `votes_comments`
ON `votes_comments`.comment_id = `comments`.comment_id
GROUP BY `votes_comments`.comment_id
ORDER BY total_votes
DESC
LIMIT 20, 20
I have one table called cf_posts
ID pk
user INT
subject VARCHAR
body TEXT
datetime TIMESTAMP
parent INT
category INT
mod INT
When a post is submitted to the forum the default parent is 0, when a post is submitted as a reply, then its parent is the ID of the original post.
How can I make it so that the default view of the forum main page is ordered that the most recently updated post (including the latest replies) would be at the "top" of the pile, and working down in date order? What would be the PHP/MySQL query?
The only workarounds I have seen for this are separate topics and reply tables, but I'd like to stay away from this approach if possible.
One workaround that I tried and failed was GROUP BY parent.
But this grouped all topics that had no replies together as one.
Another idea that I have yet to try is to make the parent id of the original post match the post ID, and not include matching ID and parent IDs in the output.
I look forward to hearing your thoughts.
SELECT mainPost.subject, lastPost.datetime
FROM cf_posts cfp,
(
SELECT *
FROM cf_posts subPost
WHERE subPost.parent = mainPost
ORDER BY subPost.datetime DESC
LIMIT 1
)lastPost
WHERE mainPost.parent IS NULL
This is done briefly, so there may be some syntax issues but I think this should help.
You can do the following: query each separate thing that you need, so maybe a query for each topic, Then you can use UNION to bunch all of them together to get one list. Now the trick to preserve an order is as followed. For each separate query append a column to the returned result called sort and set each instance of that to a higher int value, then you can guarantee that the final result is properly sorted. Go review UNION for select statements to get a better understanding of what I'm talking about.
I know i am writing query's wrong and when we get a lot of traffic, our database gets hit HARD and the page slows to a grind...
I think I need to write queries based on CREATE VIEW from the last 30 days from the CURDATE ?? But not sure where to begin or if this will be MORE efficient query for the database?
Anyways, here is a sample query I have written..
$query_Recordset6 = "SELECT `date`, title, category, url, comments
FROM cute_news
WHERE category LIKE '%45%'
ORDER BY `date` DESC";
Any help or suggestions would be great! I have about 11 queries like this, but I am confident if I could get help on one of these, then I can implement them to the rest!!
Putting a wildcard on the left side of a value comparison:
LIKE '%xyz'
...means that an index can not be used, even if one exists. Might want to consider using Full Text Searching (FTS), which means adding full text indexing.
Normalizing the data would be another step to consider - categories should likely be in a separate table.
SELECT `date`, title, category, url, comments
FROM cute_news
WHERE category LIKE '%45%'
ORDER BY `date` DESC
The LIKE '%45%' means a full table scan will need to be performed. Are you perhaps storing a list of categories in the column? If so creating a new table storing category and news_article_id will allow an index to be used to retrieve the matching records much more efficiently.
OK, time for psychic debugging.
In my mind's eye, I see that query performance would be improved considerably through database normalization, specifically by splitting the category multi-valued column into a a separate table that has two columns: the primary key for cute_news and the category ID.
This would also allow you to directly link said table to the categories table without having to parse it first.
Or, as Chris Date said: "Every row-and-column intersection contains exactly one value from the applicable domain (and nothing else)."
Anything with LIKE '%XXX%' is going to be slow. Its a slow operation.
For something like categories, you might want to separate categories out into another table and use a foreign key in the cute_news table. That way you can have category_id, and use that in the query which will be MUCH faster.
Also, I'm not quite sure why you're talking about using CREATE VIEW. Views will not really help you for speed. Not unless its a materialized view, which MySQL doesn't suppose natively.
If your database is getting hit hard, the solution isn't to make a view (the view is still basically the same amount of work for the database to do), the solution is to cache the results.
This is especially applicable since, from what it sounds like, your data only needs to be refreshed once every 30 days.
I'd guess that your category column is a list of category values like "12,34,45,78" ?
This is not good relational database design. One reason it's not good is as you've discovered: it's incredibly slow to search for a substring that might appear in the middle of that list.
Some people have suggested using fulltext search instead of the LIKE predicate with wildcards, but in this case it's simpler to create another table so you can list one category value per row, with a reference back to your cute_news table:
CREATE TABLE cute_news_category (
news_id INT NOT NULL,
category INT NOT NULL,
PRIMARY KEY (news_id, category),
FOREIGN KEY (news_id) REFERENCES cute_news(news_id)
) ENGINE=InnoDB;
Then you can query and it'll go a lot faster:
SELECT n.`date`, n.title, c.category, n.url, n.comments
FROM cute_news n
JOIN cute_news_category c ON (n.news_id = c.news_id)
WHERE c.category = 45
ORDER BY n.`date` DESC
Any answer is a guess, show:
- the relevant SHOW CREATE TABLE outputs
- the EXPLAIN output from your common queries.
And Bill Karwin's comment certainly applies.
After all this & optimizing, sampling the data into a table with only the last 30 days could still be desired, in which case you're better of running a daily cronjob to do just that.
I want to do a function to my users, so on index.php there is e.g:
You have 2 new comments on your clip
How should i do this? I mean i want ideas to do this the easiest way. Table for the videos is member_videos, and tables for the comment is member_videocomments, a comment inside the table is connected by their "videoID", which is the id of the column in member_videos.
Should i do the classic, making a field, which all is 0, until it has been seen by the user its 1 or what should i do.
One alternative to the unread flag on each comment is a last_read_comments timestamp on the user record. Whenever they read their new comments, update the timestamp to the current time. When they land on your homepage, query for all comments newer than that timestamp value.
Why don't you just do a check when you load the video against the comments table that says something like
SELECT COUNT(*) FROM member_videocomments WHERE videoID = videoIDLoaded;
Then get the result set back as an integer and check if that integer is equal to 0, if it is then display 0, else query the database for the comments get the result set back and display all the comments on the page however you like.
Just update a field in the table member_videocomments, something like readbyuser. Leave it at zero until the user views that specific comment.
$result = mysql_query("SELECT id FROM member_videocomments WHERE !readbyuser");
$num_rows = mysql_num_rows($result);
You've given the answer yourself. Just mark the comments when they are actually being displayed to the user. Assuming new comments are not viewed all at once but instead per video clip, I don't think the timestamp approach would be a good solution, because when you update the timestamp once the user has opened one of their newly commented videos, you will lose track of other new comments.
I have a similar situation, and I am using a is_read flag. When comments are added to the database, is_read is set to 0. When a user logs in, I check for any unread comments (it's here that I grab the # of unread comments so I can display it). Then when they view them, I grab the IDs of the comments and run a query to set their is_read to 1.
Matthew's timestamp solution is also good, so really it's up to what you feel more comfortable with.