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.
Related
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.
I have a table in MySQL that I'm accessing from PHP. For example, let's have a table named THINGS:
things.ID - int primary key
things.name - varchar
things.owner_ID - int for joining with another table
My select statement to get what I need might look like:
SELECT * FROM things WHERE owner_ID = 99;
Pretty straightforward. Now, I'd like users to be able to specify a completely arbitrary order for the items returned from this query. The list will be displayed, they can then click an "up" or "down" button next to a row and have it moved up or down the list, or possibly a drag-and-drop operation to move it to anywhere else. I'd like this order to be saved in the database (same or other table). The custom order would be unique for the set of rows for each owner_ID.
I've searched for ways to provide this ordering without luck. I've thought of a few ways to implement this, but help me fill in the final option:
Add an INT column and set it's value to whatever I need to get rows
returned in my order. This presents the problem of scanning
row-by-row to find the insertion point, and possibly needing to
update the preceding/following rows sort column.
Having a "next" and "previous" column, implementing a linked list.
Once I find my place, I'll just have to update max 2 rows to insert
the row. But this requires scanning for the location from row #1.
Some SQL/relational DB trick I'm unaware of...
I'm looking for an answer to #3 because it may be out there, who knows. Plus, I'd like to offload as much as I can on the database.
From what I've read you need a new table containing the ordering of each user, say it's called *user_orderings*.
This table should contain the user ID, the position of the thing and the ID of the thing. The (user_id, thing_id) should be the PK. This way you need to update this table every time but you can get the things for a user in the order he/she wants using ORDER BY on the user_orderings table and joining it with the things table. It should work.
The simplest expression of an ordered list is: 3,1,2,4. We can store this as a string in the parent table; so if our table is photos with the foreign key profile_id, we'd place our photo order in profiles.photo_order. We can then consider this field in our order by clause by utilizing the find_in_set() function. This requires either two queries or a join. I use two queries but the join is more interesting, so here it is:
select photos.photo_id, photos.caption
from photos
join profiles on profiles.profile_id = photos.profile_id
where photos.profile_id = 1
order by find_in_set(photos.photo_id, profiles.photo_order);
Note that you would probably not want to use find_in_set() in a where clause due to performance implications, but in an order by clause, there are few enough results to make this fast.
I have a table with about 150 websites listed in it with the columns "site_name", "visible_name" (basically a formatted name), and "description." For a given page on my site, I want to pull site_name and visible_name for every site in the table, and I want to pull all three columns for the selected site, which comes from the $_GET array (a URL parameter).
Right now I'm using 2 queries to do this, one that says "Get site_name and visible_name for all sites" and another that says "Get all 3 fields for one specific site." I'm guess a better way to do it is:
SELECT * FROM site_list;
thus reducing to 1 query, and then doing the rest post-query, which brings up 2 questions:
The "description" field for each site is about 200-300 characters. Is it bad from a performance standpoint to pull this for all 150 sites if I'm only using it for 1 site?
How do I reference the specific row from the MySQL result set for the site specificed in the URL? For example, if the URL is "mysite.com/results?site_name=foo" how do I do the post-query equivalent of SELECT * FROM site_list where site_name=foo; ?
I don't know how to get the data for "site_name=foo" without looping through the entire result array and checking to see if site_name matches the URL parameter. Isn't there a more efficient way to do it?
Thanks,
Chris
PS: I noticed a similar question on stackoverflow and read through all the answers but it didn't help in my situation, which is why I'm posting this.
Thanks,
Chris
I believe what you do now, keeping sperated queries for getting a list of sites with just titles and one detailed view with description for a single given site, is good. You don't pull any unneeded data and both queries being very simple are fast.
It is possible to combine both your queries into one, using left join, something maybe like:
SELECT s1.site_name, s1.visible_name, s2.description
FROM site_list s1
LEFT JOIN
( SELECT site_name, description
FROM site_list
WHERE site_name = 'this site should go with description' ) s2
ON s2.site_name = s1.site_name
resulting in all sites without matching name having NULL as description, you could even sort it using
ORDER BY description DESC, site_name
to get the site with description as first fetched row, thus eliminating need to iterate through results to find it, but mysql would have to do a lot more work to give you this result, negating any possible gain you could hope for. So basically stick to what you have now, its good.
Generally, it's good practice to have an 'id' field in the table as an auto_increment value. Then, you would:
SELECT id,url,display_name FROM table;
and you'd have the 'id' value there to later:
SELECT * FROM table WHERE id=123;
That's probably your most efficient method if you had WAAAY more entries in the table.
However, with only 150 rows in the table, you're probably just fine doing
SELECT * FROM table;
and only accessing that last field for a matching row based on your criteria.
If you only need the description for the site named foo you could just query the database with SELECT * FROM site_list WHERE site_name = 'foo' LIMIT 1
Otherwise you would have to loop though the result array and do a string comparison on site_name to find the correct description.
Ill try to keep this simple and to the point. Essentially I have a news feed, and a comments section. The comments section has two tiers: responses and then replies to responses. Basically structured like so for a given news post:
-> comment
---> reply
---> reply
Each comment can have multiple replies. Obviously, the WRONG way to do this is to do an SQL query for every comment to check for replies and list them out. EDIT Comments only have 1 tier of replies, ie replies CANNOT have replies. - Thanks JohnP
My Questions for this kind of query:
Should I keep the comments and replies in separate tables and use a JOIN, or can I keep the replies and comments in the same table and use a qualifier to separate the type?
Should I attempt to sort them using the query or pull all the data into an array and sort & display that way?
My table currently is as follow:
ID (unique, auto increment)
NEWS_ID (ties the comment to a particular news post)
REPLY_ID (ties the comment to a parent comment if it is a reply to another comment)
USER_ID
BODY
PUBLISHED_DATE
Any suggestions from those wiser than me would be greatly appreciated! Im still in the very early stages of fully understanding JOINS and other higher level mysql query structures. (IE: I suck at mysql, but im learning :)
Since you said replies are one level deep..
I would make comments 1 table and have a comment_id field to denote ownership and a news_id field to add the relationship to the news item. This way you can simply query for all comments that match the news_id and sort it by comment_id. And then a wee bit of PHP array magic will get you a sorted list of comments/replies.
So having a look at your current table, you're on the correct path.
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.