SQL issue with RIGHT JOINS - sql

The following SQL query does exactly what it should do, but I don't know how to change it so it does what I need it to do.
SELECT
a.id AS comment_id, a.parent_comment_id, a.user_id, a.pid, a.comment, a.blocked_at, a.blocked_by, a.created_at, a.updated_at,
b.name, b.is_moderator, COUNT(IF(d.type = 1, 1, NULL)) AS a_count, COUNT(IF(d.type = 2, 1, NULL)) AS b_count
FROM
comments AS a
RIGHT JOIN
users AS b ON b.id = a.user_id
RIGHT JOIN
node_user AS c ON c.user = b.id
RIGHT JOIN
nodes AS d ON d.id = c.node
WHERE
a.pid = 999
GROUP BY
comment_id
ORDER BY
a.created_at ASC
It gets all comments belonging to a specific pid, it then RIGHT JOINS additional user data like name and is_moderator, then RIGHT JOINS any (so called) nodes including additional data based on the user id and node id. As seen in the SELECT, I count the nodes based on their type.
This works great for users that have any nodes attached to their accounts. But users who don't have any, so whose id doesn't exist in the node_user and nodes tables, won't be shown in the query results.
So my question:
How can I make it so that even users who don't have any nodes, are still shown in the query results but with an a_count and b_count of 0 or NULL.

I'm pretty sure you want left joins not right joins. You also want to fix your table aliases so they are meaningful:
SELECT . . .
FROM comments c LEFT JOIN
users u
ON u.id = c.user_id LEFT JOIN
node_user nu
ON nu.user = u.id LEFT JOIN
nodes n
ON n.id = nu.node
WHERE c.pid = 999
GROUP BY c.id
ORDER BY c.created_at ASC;
This keeps everything in the first table, regardless of whether or not rows match in the subsequent tables. That appears to be what you want.

Related

The SQL query succeeds, but does not make any changes to the table

I have the following problem. I run this request, which passes successfully but it says "0 (rows affected)". In short, what I want to do. I'm trying to write a query that adds elements to the main table because I've linked Child tables I'm trying to retrieve the IDs and put them in the main table
INSERT INTO Articles
([ID],
[ArtName],
[ArtType],
[SerNo],
[MACNo],
[UserID],
[Available],
[CityID],
[StoreID],
[WorkplaceID],
[ItemPrice],
[IP_01],
[IP_02],
[Note])
SELECT
(SELECT max([ID])+1 FROM Articles),
'HP',
art.ID,
'123',
'А18Н31',
u.ID,
av.ID,
c.ID ,
s.ID,
w.ID,
'14.23',
'192.168.11.3',
'192.168.11.3',
GetDate()
FROM Articles a
INNER JOIN Workplace w ON a.WorkplaceID = w.ID
INNER JOIN Store s ON a.StoreID = s.ID
INNER JOIN City c ON a.CityID = c.ID
INNER JOIN Avaiable av ON a.Available = av.ID
INNER JOIN Users u ON a.UserID = u.ID
INNER JOIN ArtType art ON a.ArtType = art.ID
WHERE c.CityName LIKE '%Sofia%' AND art.ArtTypeName LIKE '%FirstType%' AND s.StoreName LIKE '%First%' AND av.AvaiableName LIKE '%yes%' AND u.UserName LIKE '%Valq%' AND w.WorkplaceName LIKE '%FWorkplace%'
This says "0 rows affected" because the SELECT returns no rows. This could be because nothing matches the JOINs. This could be because the WHERE clause filters out all rows. Without sample data, there is no way to tell. You have to investigate yourself.
That said, this is highly suspicious:
(SELECT max([ID])+1 FROM Articles),
This is not the right way to have an incremental id in a table. You should be using an identity column. Or perhaps default to a sequence. In either case, the value of id would be set automatically when rows are inserted.
Also note that if this inserts multiple rows, all would get the same id, which is presumably not what you want.

How to join three tables having relation parent-child-child's child. And I want to access all records related to parent

I have three tables:
articles(id,title,message)
comments(id,article_id,commentedUser_id,comment)
comment_likes(id, likedUser_id, comment_id, action_like, action_dislike)
I want to show comments.id, comments.commentedUser_id, comments.comment, ( Select count(action_like) where action_like="like") as likes and comment_id=comments.id where comments.article_id=article.id
Actually I want to count all action_likes that related to any comment. And show all all comments of articles.
action_likes having only two values null or like
SELECT c.id , c.CommentedUser_id , c.comment , (cl.COUNT(action_like) WHERE action_like='like' AND comment_id='c.id') as likes
FROM comment_likes as cl
LEFT JOIN comments as c ON c.id=cl.comment_id
WHERE c.article_id=article.id
It shows nothing, I know I'm doing wrong way, that was just that I want say
I guess you are looking for something like below. This will return Article/Comment wise LIKE count.
SELECT
a.id article_id,
c.id comment_id,
c.CommentedUser_id ,
c.comment ,
COUNT (CASE WHEN action_like='like' THEN 1 ELSE NULL END) as likes
FROM article a
INNER JOIN comments C ON a.id = c.article_id
LEFT JOIN comment_likes as cl ON c.id=cl.comment_id
GROUP BY a.id,c.id , c.CommentedUser_id , c.comment
IF you need results for specific Article, you can add WHERE clause before the GROUP BY section like - WHERE a.id = N
I would recommend a correlated subquery for this:
SELECT a.id as article_id, c.id as comment_id,
c.CommentedUser_id, c.comment,
(SELECT COUNT(*)
FROM comment_likes cl
WHERE cl.comment_id = c.id AND
cl.action_like = 'like'
) as num_likes
FROM article a INNER JOIN
comments c
ON a.id = c.article_id;
This is a case where a correlated subquery often has noticeably better performance than an outer aggregation, particularly with the right index. The index you want is on comment_likes(comment_id, action_like).
Why is the performance better? Most databases will implement the group by by sorting the data. Sorting is an expensive operation that grows super-linearly -- that is, twice as much data takes more than twice as long to sort.
The correlated subquery breaks the problem down into smaller pieces. In fact, no sorting should be necessary -- just scanning the index and counting the matching rows.

Access SQL LEFT JOIN not returning results

I have three tables: Comments, Users and CommentsHelpfulness.
Users can submit several comments, which are stored in the Comments table.
CommentsHelpfulness has three columns: UserID, CommentID and a "Helpful" Boolean. Every user can indicate for every comment if they find it useful, which will create an entry in the CommentsHelpfulness table.
I want a query that gives me all Comment IDs, with the name of the user that submitted it and shows whether the currently logged in user found it helpful, did not find it helpful or did not say anything about it. So the ID of a comment the current user did not express his opinion about should still be output, just without the helpful Boolean.
To me that sounds like it should be done like this using a left join:
SELECT Comments.ID, Users.Nom, CommentsHelpfulness.Helpful
FROM (Comments INNER JOIN Users
ON Comments.UserID = Users.ID)
LEFT JOIN CommentsHelpfulness
ON (CommentsHelpfulness.CommentID = Comments.ID
AND (CommentsHelpfulness.UserID = ?))
Unfortunately this does not output Comment IDs without an entry in the CommentsHelpfulness table. Why does this not work? Is it because of Access?
I think the issue is the inner join, not the left join.
Try removing that table:
SELECT c.ID, ch.Helpful
FROM Comments as c LEFT JOIN
CommentsHelpfulness as ch
ON ch.CommentID = c.ID AND
ch.UserID = ?
select c.id, c.Nom, d.Helpful from (
select a.ID, b.Nom from Comments a left join Users b
on a.UserID = b.ID) c left join CommentsHelpfulness d
on d.CommentID = c.ID;

Outer Joining SQL Tables?

I have three table in the Database -
Activity table with activity_id, activity_type
Category table with category_id, category_name
Link table with mapping between activity_id and category_id
I need to write a select statement to get the following data:
activity_id, activity_type, Category_name.
The issue is some of the activity_id have no entry in the link table.
If I write:
select a.activity_id, a.activity_type, c.category_name
from activity a, category c, link l
where a.activity_id = l.activity_id and c.category_id = l.category_id
then I do not get the data for the activity_ids that are not present in the link table.
I need to get data for all the activities with empty or null value as category_name for those not having any linking for category_id.
Please help me with it.
PS. I am using MS SQL Server DB
I believe you're looking for a LEFT OUTER JOIN for your activity table to return all rows.
SELECT
a.activity_id, a.activity_type, c.category_name
FROM activity a
LEFT OUTER JOIN link l
ON a.activity_id = l.activity_id
LEFT OUTER JOIN category c
ON c.category_id = l.category_id;
You should use proper explicit joins:
select a.activity_id, a.activity_type, c.category_name
from activity a
LEFT JOIN link l
ON a.activity_id = l.activity_id
LEFT JOIN category c
ON l.category_id = c.category_id
If writing this type of logic will be part of your ongoing responsibilities, I would strongly suggest that you do some research on joins, including the interactions between joins and where clauses. Joins and where clauses combine to form the backbone of query writing, regardless of the technology used to retrieve the data.
Most critical join information to understand:
Left Outer Join: retrieves all information from the 'left' table and any records that exist in the joined table
Inner Join: retrieves only records that exist in both tables
Where clauses: used to limit data, regardless of inner or outer join definitions.
In the example you posted, the where clause is limiting your overall data to rows that exist in all 3 tables. Replacing the where clause with appropriate join logic will do the trick:
select a.activity_id, a.activity_type, c.category_name
from activity a
left outer join link l --return all activity rows regardless of whether the link exists
on a.activity_id = l.activity_id
left outer join category c --return all activity rows regardless of whether the link exists
on c.category_id = l.category_id
Best of luck!
What about?
select a.activity_id, a.activity_type, c.category_name from category c
left join link l on c.category_id = l.category_id
left join activity a on l.activity_id = a.activity_id
Actually, the first join seems that it could be an inner join, because you didn't mention that there might be some missing elements there

Optimizing simple query that takes 1 minute to execute

I have this rather simple SQL query, but it takes almost a minute to execute:
SELECT
i.id,
...,
a.id AS albums_id,
...,
u.id AS users_id,
...
FROM
images i
LEFT JOIN albums a ON i.albums_id = a.id
LEFT JOIN users u ON a.users_id = u.id
WHERE
a.access = 'public'
AND i.num_of_views > 0
ORDER BY
i.num_of_views DESC
LIMIT
0, 60
Result of EXPLAIN for the above query:
Tables involved:
images (~4,822,000 rows), albums (~149,000 rows), users (~43,000 rows)
Relevant indexes:
albums: access(access,num_of_images,album_time), access_2(access,num_of_images,num_of_all_comments,album_time), users_id(users_id,album_time)
images: browser_2(num_of_views), albums_id(albums_id,image_order)
All tabels are InnoDB, running on MySql v5.1.47
So how do I bring this down to under a second?
Please leave a comment if you need any additional info.
edit: users table can be joined either with albums or images does not matter to me.
edit2: moving a.access = 'public' from WHERE to JOIN does indeed solve my problem, but the results returned are not correct (I get images from albums that are not public), putting the a.access ... in both WHERE and JOIN slows the query down even more than before.
Add an index on albums.users_id. I also agree with the comments regarding a.access = 'public'. But the index should help either way.
UPDATE
Since the key above exists. Try adjusting the order of your JOIN, i.e. move users above albums or make a different table the primary. In rare cases this can help. Also to better join albums try:
LEFT JOIN albums a ON (i.albums_id = a.id AND a.access = 'public')
UPDATE
Based on the comments, I would remove as many of the LEFT JOIN as possible. As I am not sure what you require in your results, I will only show it for albums. This will not only decrease the result set, but solve the problem for applying the filter.
JOIN albums a ON (i.albums_id = a.id AND a.access = 'public')
I believe there's a little confusion going on here w/r/t the impact that a filter can have on a LEFT JOIN vs an INNER JOIN.
Jan, if what you are trying to ask in your query is "Get all images for all albums that are public, and get the users of those albums as well" then you do not want a left join, you want an inner join. A left join will return all images for all albums, but it will also return all images that have no matching album. You can add "and a.id IS NOT NULL" but that's the same as an INNER JOIN.
I believe what you want is the following:
SELECT
i.id,
...,
a.id AS albums_id,
...,
u.id AS users_id,
...
FROM images i
INNER JOIN albums a ON i.albums_id = a.id AND a.access = 'public'
INNER JOIN users u ON a.users_id = u.id
WHERE i.num_of_views > 0
ORDER BY i.num_of_views DESC
LIMIT 0, 60
If you left join albums to users you could return all albums that don't have users. Not sure which one you want.
Based on your most recent comments, you should use an INNER JOIN to albums instead of a LEFT JOIN.
SELECT
i.id,
...,
a.id AS albums_id,
...,
u.id AS users_id,
...
FROM images i
INNER JOIN albums a ON i.albums_id = a.id
LEFT JOIN users u ON a.users_id = u.id
WHERE a.access = 'public'
AND i.num_of_views > 0
ORDER BY i.num_of_views DESC
LIMIT 0, 60