I'm not too good with explaining things, apologies.
I have 3 tables that are similar to the below:
users
id
username
threads
id
title
user_id
lastpost_id
posts
id
content
thread_id
user_id
On a page listing forum threads, I want the username of both the thread author, and the last post author of that thread to be displayed, I'm attempting to achieve this in a single query.
My query looks like this:
SELECT t.*,u.username FROM threads t
INNER JOIN users u ON t.user_id=u.id
INNER JOIN posts p ON t.lastpost_id=p.id
ORDER BY t.id DESC
The first join enables me to get the username of the user id that started the thread.
The second join is what I'm not sure on, it can get me the user id but how do I get the username from that, as a 3rd join?
You can select the same table multiple times if you give it a different alias. You can give the fields aliases too:
SELECT
t.*,
tu.username as threadusername, /* Result field is called 'threadusername' */
p.*,
pu.username as lastpostusername
FROM threads t
INNER JOIN users tu ON t.user_id=tu.id /* thread user */
INNER JOIN posts p ON t.lastpost_id=p.id
INNER JOIN users pu ON p.user_id=pu.id /* post user */
ORDER BY t.id DESC
You can join to a joined table like this:
SELECT t.*,u.username,u2.username FROM threads t
INNER JOIN users u ON t.user_id=u.id
INNER JOIN posts p ON t.lastpost_id=p.id
INNER JOIN users u2 ON p.user_id=u2.id
ORDER BY t.id DESC
Note, I haven't had time to test it, but it should work (at least in MySQL).
I don't know if I got it correctly, but as per my understanding you can have a inner query to fetch the thread ids and then have a outer query to fetch the posts based on the thread id, have a max on post id and group by user id. Also join to user to have the name. Hope that helps.
Related
I have two tables in database, named users(store user details) and posts(store post details). Now i want to get data from both tables. Like user_image from users and post description from post.
I am using this query
SELECT * FROM `users`AS u,`posts` WHERE u.user_id IN (SELECT user_id FROM `posts`)
But it returns duplicate data. I have 2 users and 3 posts but it returns 6 posts.
Try something like:
Select a.user_image, b.post_description from users as a join posts as b on a.user_id = b.user_id
Do an inner join & you shall get the desired result
In the above query a & b are alias for the two different tables. I you do not want to use alias you can also write it as users.user_image in your select statement.
Write the fields you want from both the tables in your select statements.
The below image will help you understand the inner join
Inner Join Circle for understanding
Use group by as below:
SELECT * FROM `users`AS u,`posts` WHERE u.user_id IN (SELECT user_id FROM `posts`) group by u.user_id
What about
Select * FROM user u right join posts p on u.id = p.user_id
?
if you want to get data from both tables you need to use joins.and you make sure the two tables are interlinked by primary keys
so use this can help
select user_image,post_description from users join posts on users.user_id=posts.user_id;
At the moment, we have 3 queries. In php, we loop over the first, then execute the 2nd multiple times, then which I'd like to have in one single query:
The first query is:
SELECT id FROM users
Then inside looping over those results, the 2nd is
SELECT id AS rid, count(recommendedById) FROM users WHERE id=$id
where $id is users.id from the first query.
The 3rd query is which is executed inside the 2nd loop is:
SELECT count(likes) AS likeCounter FROM posts WHERE author_id=$rid
and likeCounter is summed up to the first query.
Anyone able to bring this into one query?
Desired result
The result should be a row per user with a count of users he recommended and a sum of likes his recommended users got on their posts.
SELECT u.id,COUNT(DISTINCT ruid),sum(p.likes)
FROM users as u
LEFT JOIN (SELECT recommendedById as rid,id as ruid from users) as r ON r.rid = u.id
LEFT JOIN posts p ON p.author_id = ruid
GROUP BY u.id
You can do this:
SELECT u.id AS rid, count(recs.id), count(p.likes) AS likeCounter
FROM users u
LEFT JOIN posts p ON p.author_id=u.id
LEFT JOIN users recs ON recs.recommendedById=u.id
GROUP BY u.id
But a user has an id, and you use id from the users table. Isn't that always 1?
I know this question has been asked multiple times (however, I could still not find a solution):
PHP MYSQL showing posts with comments
mysql query - blog posts and comments with limit
mysql structure for posts and comments
...
Basic question: having tables posts, comments, user... can you with one single select statement select and show all posts and all comments (with comment.user, comment.text, comment.timestamp)? How would such a select statement look like? If not, what is the easiest solution?
I also tried to JOIN the comments table with the posts table and use GROUP BY, but I got either only one comment in each row or each comment but also those posts multiple times!?
I tried the solution of the first link (nested mysql_query and then fetch) as well as the second link (with arrays). However, the first caused a bunch of errors (the syntax in that post seems to be not correct and I could not figure out how to solve it) and in the second I had problems with the arrays.
My query looks like this till now:
SELECT p.id, p.title, p.text, u.username, c.country_name, (SELECT SUM(vote_type) FROM votes v WHERE v.post_id = p.id) AS sum_vote_type FROM posts p LEFT JOIN user u ON ( p.user_id = u.id ) LEFT JOIN countries c ON ( c.country_id = u.country_id ) ORDER BY $orderby DESC
I was wondering if this issue was not very common, having posts and comments to show...?
Thank you for every help in advance!
Not knowing your database structure, it should look something like this. Note that you should replace the * characters with more explicit lists of columns you actually need.
SELECT p.*, c.*, u.* FROM posts p
LEFT JOIN comments c ON c.post_id = p.id
LEFT JOIN users u ON u.id = p.author_id
Note that if you're just trying to get counts, sums and things like that it's a good idea to cache some of that information. For instance, you may want to cache the comment count in the post table instead of counting them every query. Only count and update the comment count when adding/removing a comment.
EDIT:
Realized that you also wanted to attach user data to each comment. You can JOIN the same table more than once but it gets ugly. This could turn into a really expensive query. I also am including an example of how to alias columns so it's less confusing:
SELECT p.*, c.*, u.name as post_author, u2.name as comment_author FROM posts p
LEFT JOIN comments c ON c.post_id = p.id
LEFT JOIN users u ON u.id = p.author_id
LEFT JOIN users u2 ON u2.id = c.author_id
So, I understand how the relationships work in mysql but I'm having a hard time figuring out how its implemented in my code.
For example, say I have the 3 tables.
Table 1: users - user id, username, user city
Table 2: categories - category id, category name
Table 3: user_categories - user id, category id
If I were to query the database for every user that was in a particular city and list them out with the all of the categories they belong to... How would I do this? Would I need to loop through the results and do a separate query for each user, then list the results? Or, is there some magic query that will return a multidimensional array?
I believe the above would be many-to-many, correct me if I'm wrong....
EDIT In the user_categories table, a user can contain more than 1 category, I'm trying to figure out how to return all of them
Thanks!
You're absolutely right, it is a many-to-many query.
And from what I understand, what you're looking for is the ability to have some kind of hierarchical result to display, meaning for one user, have an array of all the categories he's assigned to...
Couple of things you could do:
Option 1: Query the users table:
SELECT u.user_id, u.username, u.user_city WHERE city = 'somecity';
From the results, get all the user_id's that match, put them in an array.
array(1,3,4,5)
Then execute a query by joining the 2 tables categories and user_categories, and passing the array as a comma separated list in a where in:
SELECT user_categories.user_id, categories.category_name
FROM user_categories INNER JOIN categories ON user_categories.category_id = categories.category_id
WHERE user_categories.user_id IN (1,3,4,5)
This will give you a list of user-id, category name that you can use in your script with the previous results to build your result set
option 2: my preferred, use MySQL's GROUP_CONCAT(http://dev.mysql.com/doc/refman/5.0/en/group-by-functions.html#function_group-concat).
SELECT users.user_id, users.user_name, GROUP_CONCAT(categories.category_name) AS categories
FROM users
INNER JOIN user_categories ON users.id = users_categories.user_id
INNER JOIN categories ON user_categories.category_id = category.id
WHERE user.user_city = 'somecity'
GROUP BY user.user_id
This will return something like:
user_id username categories
1 u1 cat1, cat2, cat3
2 u2 cat1, cat3
You can specify the separator by using SEPARATOR in group_concat.
You need to JOIN the tables.
If I were to query the database for every user that was in a particular city and list them out with the all of the categories they belong to
SELECT *
FROM users
INNER JOIN user_categories
ON (user_id)
INNER JOIN categories
ON (category_id)
WHERE ...
You could try:
SELECT u.user_id, u.username, u.user_city, c.category_id, c.category_name
FROM users u
INNER JOIN user_categories uc ON u.user_id = uc.user_id
INNER JOIN categories c ON uc.category_id = c.category_id
WHERE u.user_city = 'Cityname';
I haven't tested this, and there might be a more efficient way to do it, but it should work.
If you are unfamiliar with joins in mysql, check this out.
I'm working on building a forum with kohana. I know there is already good, free, forum software out there, but it's for a family site, so I thought I'd use it as a learning experience. I'm also not using the ORM that is built into Kohana, as I would like to learn more about SQL in the process of building the forum.
For my forum I have 4 main tables:
USERS
TOPICS
POSTS
COMMENTS
TOPICS table: id (auto incremented), topic row.
USERS table: username, email, first and last name and a few other non related rows
POSTS table: id (auto incremented), post-title, post-body, topic-id, user-id, post-date, updated-date, updated-by(which will contain the user-id of the person who made the most recent comment)
COMMENTS table: id (auto incremented), post-id, user-id and comment
On the main forum page I would like to have:
a list of all of the topics
the number of posts for each topic
the last updated post, and who updated it
the most recently updated topic to be on top, most likely an "ORDER BY updated-date"
Here is the query I have so far:
SELECT topics.id AS topic-id,
topics.topic,
post-user.id AS user-id,
CONCAT_WS(' ', post-user.first-name, post-user.last-name) AS name,
recent-post.id AS post-id,
post-num.post-total,
recent-post.title AS post-title,
recent-post.update_date AS updated-date,
recent-post.updated-by AS updated-by
FROM topics
JOIN (SELECT posts.topic-id,
COUNT(*) AS post-total
FROM POSTS
WHERE posts.topic-id = topic-id
GROUP BY posts.topic-id) AS post-num ON topics.id = post-num.topic-id
JOIN (SELECT posts.*
FROM posts
ORDER BY posts.update-date DESC) AS recent-post ON topics.id = recent-post.topic-id
JOIN (SELECT users.*,
posts.user-id
FROM users, posts
WHERE posts.user-id = users.id) as post-user ON recent-post.user_id = post-user.id
GROUP BY topics.id
This query almost works as it will get all of information for topics that have posts. But it doesn't return the topics that don't have any posts.
I'm sure that the query is inefficient and wrong since it makes two sub-selects to the posts table, but it was the only way I could get to the point I'm at.
Dash is not a valid character in SQL identifiers, but you can use "_" instead.
You don't necessarily have to get everything from a single SQL query. In fact, trying to do so makes it harder to code, and also sometimes makes it harder for the SQL optimizer to execute.
It makes no sense to use ORDER BY in a subquery.
Name your primary key columns topic_id, user_id, and so on (instead of "id" in every table), and you won't have to alias them in the select-list.
Here's how I would solve this:
First get the most recent post per topic, with associated user information:
SELECT t.topic_id, t.topic,
u.user_id, CONCAT_WS(' ', u.first_name, u.last_name) AS full_name,
p.post_id, p.title, p.update_date, p.updated_by
FROM topics t
INNER JOIN
(posts p INNER JOIN users u ON (p.updated_by = u.user_id))
ON (t.topic_id = p.topic_id)
LEFT OUTER JOIN posts p2
ON (p.topic_id = p2.topic_id AND p.update_date < p2.update_date)
WHERE p2.post_id IS NULL;
Then get the counts of posts per topic in a separate, simpler query.
SELECT t.topic_id, COUNT(*) AS post_total
FROM topics t LEFT OUTER JOIN posts p USING (topic_id)
GROUP BY t.topic_id;
Merge the two data sets in your application.
to ensure you get results for topics without posts, you'll need to use LEFT JOIN instead of JOIN for the first join between topics and the next table. LEFT JOIN means "always return a result set row for every row in the left table, even if there's no match with the right table."
Gotta go now, but I'll try to look at the efficiency issues later.
This is a very complicated query. You should note that JOIN statements will limit your topics to those that have posts. If a topic does not have a post, a JOIN statement will filter it out.
Try the following query.
SELECT *
FROM
(
SELECT T.Topic,
COUNT(AllTopicPosts.ID) NumberOfPosts,
MAX(IFNULL(MostRecentPost.Post-Title, '') MostRecentPostTitle,
MAX(IFNULL(MostRecentPostUser.UserName, '') MostRecentPostUser
MAX(IFNULL(MostRecentPost.Updated_Date, '') MostRecentPostDate
FROM TOPICS
LEFT JOIN POSTS AllTopicPosts ON AllTopicPosts.Topic_Id = TOPICS.ID
LEFT JOIN
(
SELECT *
FROM Posts P
WHERE P.Topic_id = TOPICS.id
ORDER BY P.Updated_Date DESC
LIMIT 1
) MostRecentPost ON MostRecentPost.Topic_Id = TOPICS.ID
LEFT JOIN USERS MostRecentPostUser ON MostRecentPostUser.ID = MostRecentPost.User_Id
GROUP BY T.Topic
)
ORDER BY MostRecentPostDate DESC
I'd use a left join inside a subquery to pull back the correct topic, and then you can do a little legwork outside of that to get some of the user info.
select
s.topic_id,
s.topic,
u.user_id as last_updated_by_id,
u.user_name as last_updated_by,
s.last_post,
s.post_count
from
(
select
t.id as topic_id,
t.topic,
t.user_id as orig_poster,
max(coalesce(p.post_date, t.post_date)) as last_post,
count(*) as post_count --would be p.post_id if you don't want to count the topic
from
topics t
left join posts p on
t.id = p.topic_id
group by
t.topic_id,
t.topic,
t.user_id
) s
left join posts p on
s.topic_id = p.topic_id
and s.last_post = p.post_date
and s.post_count > 1 --0 if you're using p.post_id up top
inner join users u on
u.id = coalesce(p.user_id, s.orig_poster)
order by
s.last_post desc
This query does introduce coalesce and left join, and they are very good concepts to look into. For two arguments (like used here), you can also use ifnull in MySQL, since it is functionally equivalent.
Keep in mind that that's exclusive to MySQL (if you need to port this code). Other databases have other functions for that (isnull in SQL Server, nvl in Oracle, etc., etc.). I used coalesce so that I could keep this query all ANSI-fied.