MySQL Multiple Left Joins - mysql

I am trying to create a news page for a website I am working on. I decided that I want to use correct MySQL queries (meaning COUNT(id) and joins instead of more than one query or num_rows.) I'm using a PDO wrapper, that should function fine, and this still fails when run directly through the MySQL CLI application.
Basically, I have 3 tables. One holds the news, one holds the comments and one holds the users. My aim here is to create a page which displays all (will paginate later) the news posts titles, bodies, authors and dates. This worked fine when I used a second query to get the username, but then I decided I'd rather use a JOIN.
So what's the problem? Well, I need two joins. One is to get the author's username and the other to get the number of comments. When I simply go for the author's username, all works as expected. All the rows (there are 2) in the news table are displayed. However, when I added this second LEFT JOIN for the comments row, I end up only receiving one row from news (remember, there are 2,) and COUNT(comments.id) gives me 2 (it should display 1, as I have a comment for each post.)
What am I doing wrong? Why is it only displaying one news post, and saying that it has two comments, when there are two news posts, each with one comment?
SELECT news.id, users.username, news.title, news.date, news.body, COUNT(comments.id)
FROM news
LEFT JOIN users
ON news.user_id = users.id
LEFT JOIN comments
ON comments.news_id = news.id
Also, just to be sure about one other thing, my left join to comments is the correct way to get all posts regardless of whether they have comments or not, correct? Or would that be a right join? Oh, one last thing... if I switch comments.news_id = news.id to news.id = comments.news_id, I get 0 results.

You're missing a GROUP BY clause:
SELECT news.id, users.username, news.title, news.date, news.body, COUNT(comments.id)
FROM news
LEFT JOIN users
ON news.user_id = users.id
LEFT JOIN comments
ON comments.news_id = news.id
GROUP BY news.id
The left join is correct. If you used an INNER or RIGHT JOIN then you wouldn't get news items that didn't have comments.

To display the all details for each news post title ie. "news.id" which is the primary key, you need to use GROUP BY clause for "news.id"
SELECT news.id, users.username, news.title, news.date,
news.body, COUNT(comments.id)
FROM news
LEFT JOIN users
ON news.user_id = users.id
LEFT JOIN comments
ON comments.news_id = news.id
GROUP BY news.id

Related

MySQL EXISTS and ORDER BY

I have two tables 'Comments' and 'Likes' and I can show all the comments that have been liked and order them by when the comments where added. What I can't seem to do at the moment is order the comments according to when they were liked.
This is what I have at the moment:
SELECT *
FROM comments AS c
WHERE EXISTS (
SELECT *
FROM likes AS l
WHERE c.commentID=l.commentID)
Could anyone help me with the SQL to show the comments in order with the one that was most recently liked first and so on...
Just to add - I only want to show the comment once and avoid showing any comments that have not been liked.
You want to join the tables.
SELECT comments.*
FROM comments JOIN likes ON comments.commentID = likes.commentID
GROUP BY comments.commentID
ORDER BY MAX(likes.date) DESC;
The JOIN makes rows with all of the fields from comments and likes. If you use LEFT JOIN it will include comments that have not been liked, but using a plain JOIN should do what you want.
The GROUP BY collapses rows so you only have one per comment.
The ORDER BY orders the rows by the like date. I used MAX(likes.date) because you will have potentially many like dates for each comment, and you want to choose a specific one. You could try MIN(likes.date) as well, depending on what you're looking for (most recently liked vs first liked).
If you have multiple likes for a given comment, then you need an aggregation, such as:
SELECT c.*
FROM comments c join
(select l.commentId, MIN(likedate) as FirstLikeDate, MAX(likedate) as MaxLikeDate
from likes l
group by l.commentId
) l
on c.commentId = l.CommentId
order by MaxLikeDate desc
Since you only want to show the comments that have like, you can do this:
SELECT * FROM comments INNER JOIN likes USING(commentID) ORDER BY like_date DESC;

Complex SQL query, checking column values in multiple tables

I have a pretty huge SQL query to check for notifications, and I have several different types of notifications in the table, IE: posts, likes, comments, photoComments, photoLikes, videoLikes, etc. (Always adding more to it) And I have come into a problem, I'm not really sure how to best do this anymore. Thus far the way I have done it is working perfectly, and really quite easy to add to, however this one notification Type I have to check more than just one other table, I have to check two others and I haven't been able to get it to work.
So here it is: (This is only one part of my huge query, the only relevant part really)
n.uniqueID = ANY (
SELECT photos.id
FROM photos INNER JOIN posts ON (photos.id=posts.post)
WHERE photos.state=0
AND posts.state=0
AND posts.id = ANY (
SELECT likes.postID FROM likes
INNER JOIN posts ON (posts.id=likes.postID)
WHERE likes.state=0 AND posts.state=0
)
)
So basically all I really need to do is check the state columns in each table because that says whether or not it is deleted or not (if it's not 0 then it's deleted and shouldn't be returned)
So it would be like:
IF photos.state=0 AND posts.state=0 AND likes.state=0 return it.
n.uniqueID, posts.post, and photo.id will all be the same
value.
posts.id and likes.postID will also be the same value.
My issue is that it doesn't seem to be checking the likes.state, I don't think.
I think you just want to join the three tables together in a single query:
n.uniqueID = ANY (
SELECT photos.id
FROM photos INNER JOIN
posts
ON photos.id=posts.post inner join
likes
on posts.id = likes.postId
WHERE photos.state=0 and
posts.state=0 and
likes.state = 0
)
Your logic is not to return when there is a like or post with the state of 0. It seems to be that all the likes and posts have a state of zero. For this, do an aggregation with a having clause:
n.uniqueID = ANY (
SELECT photos.id
FROM photos INNER JOIN
posts
ON photos.id=posts.post inner join
likes
on posts.id = likes.postId
where photos.state = 0
group by photos.id
having MAX(posts.state) = 0 and MAX(likes.state) = 0

Attempting left join with a 'where' that only applies if the join succeeds

I have articles, stored in an article table. Some articles have one or more photos, stored in a photo table with an article_id specified. Some photos are 'deactivated' (photo.active = '0') and should not be retrieved.
I'm trying to get articles for the home page, and one photo for each article with one or more photos. Like so:
SELECT article.id, article.date, article.title, photo.filename_small
FROM (article)
LEFT JOIN photo ON photo.article_id=article.id
WHERE photo.active = '1'
GROUP BY article.id
ORDER BY article.date desc
LIMIT 10
(The "group by" is so that I don't get multiple results for articles with multiple photos. It strikes me as awkward.)
When I have the WHERE photo.active = '1' like that, I only get results with a photo, which defeats the purpose of making the join a left join. The where is only relevant if the join matches the article with a photo, but it's ruling out all articles without active photos. Any ideas?
(Yes, there are similar questions, but I've read a lot of them and am still struggling.)
Try something like
SELECT article.id,
article.date,
article.title,
photo.filename_small
FROM (article) LEFT JOIN
photo ON photo.article_id=article.id
AND photo.active = '1'
GROUP BY article.id
ORDER BY article.date desc
LIMIT 10
Two options.
Put it in the join clause:
LEFT OUTER JOIN photo ON photo.article_id=article.id AND photo.active = 1
Explicitly allow nulls again:
WHERE (photo.active = 1 OR photo.id IS NULL)
The second seems unnecessarily complicated though as you already have the outer join. I'd recommend the first.

MySQL calling in Username to show instead of ID?

I have a users table, books table and authors table. An author can have many books, while a user can also have many books. (This is how my DB is currently setup). As I'm pretty new to So far my setup is like bookview.php?book_id=23 from accessing authors page, then seeing all books for the author. The single book's details are all displayed on this new page...I can get the output to display the user ID associated with the book, but not the user name, and this also applies for the author's name, I can the author ID to display, but not the name, so somewhere in the query below I am not calling in the correct values:
SELECT users.user_id,
authors.author_id,
books.book_id,
books.bookname,
books.bookprice,
books.bookplot
FROM books
INNER JOIN authors on books.book_id = authors.book_id
INNER JOIN users ON books.book_id = users.user_id
WHERE books.book_id=" . $book_id;
Could someone help me correct this so I can display the author name and user name both associated with the book! Thanks for the help :)
I think the right join conditions would be something like (changes bolded)
SELECT users.user_id,
**users.user_name,**
authors.author_id,
**authors.author_name,**
books.book_id,
books.bookname,
books.bookprice,
books.bookplot
FROM books
INNER JOIN authors on books.**author_id** = authors.**author_id**
INNER JOIN users ON books.**user_id** = users.user_id
WHERE books.book_id=" . $book_id;
Or you can use the less verbose syntax for simple inner joins.
SELECT users.user_id,
users.user_name,
authors.author_id,
authors.author_name,
books.book_id,
books.bookname,
books.bookprice,
books.bookplot
FROM books, authors, users
where books.author_id = authors.author_id
and books.user_id = users.user_id
and books.book_id=" . $book_id
It would help if I added the name fields into the select query :D
These two joins together:
INNER JOIN authors on books.book_id = authors.book_id
INNER JOIN users ON books.book_id = users.user_id
also imply authors.book_id = users.user_id which, on the face of it, makes no sense. Surely the code you've posted is not the code you're actually using...?

How can I join these two queries?

Here's what I want to accomplish:
Select all rows from table "posts" while also fetching linked entities, such as the author (from table "users") and the names of the categories the post belongs to (from table "categories").
These are my two queries so far:
This one fetches the posts:
SELECT
posts.*
, users.authorName AS author
FROM posts
INNER JOIN users
ON users.id = posts.author
And this one fetches a comma separated list of categories for a specific post:
SELECT
GROUP_CONCAT(categories.category) AS categories
FROM categories
INNER JOIN post_category
ON post_category.categoryID = categories.id
WHERE
post_category.postID = ?
The two queries on their own work fine.
Naturally when the two are combined, I wouldn't need the WHERE clause of the second query.
I tried using the second query as a sub-query within the first one's SELECT clause, but that fetched a comma separated list of ALL categories for ALL posts. I want only the categories for the post I'm currently iterating over.
Any help would be greatly appreciated, and I apologize if any of this is unclear - it's hard enough for me to think about, let alone describe to other people ;)
Something like this:
SELECT posts.*, users.authorName AS author,
GROUP_CONCAT(categories.category) AS categories
FROM posts, users, categories, post_category
WHERE users.id = posts.author
AND post_category.categoryID = categories.id
AND post_category.postID = posts.id
GROUP BY posts.* /* list actual columns here */, author
Or did I miss something?
I think your subquery approach should work fine, but you still need the where clause in your subquery. How else would MySQL know which rows to retrieve? Try:
SELECT posts.*, users.authorName AS author,
(SELECT GROUP_CONCAT(categories.category)
FROM categories
INNER JOIN post_category ON post_category.categoryID = categories.id
WHERE post_category.postID = posts.postID) AS categories
FROM posts
INNER JOIN users ON users.id = posts.author