Reputation: 3225
I have 4 tables user
, posts
, follows
, notifications
. The posts
table has a field called privacy
in which a value of 1 means "anyone can see" and 2 means "only friends can see".
My notifications
table looks like this:
id user_id tonotify_id notification post_id
1 2 3 --- 1
2 3 2 --- 2
3 2 4 --- 3
And my follows
table looks like this:
id user_id tofollow_id status fstatus
1 1 2 1 1
2 1 3 1 1
The field fstatus
value of 1 means that they are also friends.
Now I am trying to get all notifications sent out by people who a certain user is following (lets say a user with id 1). Assuming 1 is following user 2 and 3, I need to get all notifications from the above table. However, before this I need to make sure that the post (posted by the users in the tonotify_id
column) is available. That is, I need to check the privacy setting. I have the following code:
SELECT
u.username AS sender,
ux.username AS receiver,
p.id
FROM notifications n
JOIN follows f ON (n.user_id = f.tofollow_id)
JOIN follows fr ON (n.tonotify_id = fr.tofollow_id)
JOIN user u ON (u.id = n.user_id)
JOIN user ux ON (ux.id = n.tonotify_id)
LEFT JOIN posts p ON (n.posts_id = p.id)
WHERE f.user_id = 1
AND fr.user_id = 1
AND f.status = 1
AND p.privacy = 1 OR (p.privacy = 2 AND fr.fstatus = 1)
ORDER BY n.id DESC
It seems to be working except for one glitch. Since user 1 is not following user 4 all notifications aimed at 4, even if the posts are public don't show up. This my guess has to do with JOIN follows fr ON (n.tonotify_id = fr.tofollow_id)
because, since the user 1 hasn't followed 4, there are no rows matching this join. Any suggestions to solving this?
Upvotes: 1
Views: 106
Reputation: 727097
I did try [the outer join], but the output is the same.
When you use an outer join, and then use one of the "outer" columns in an equality check in the WHERE
clause, you convert your outer join to an inner join. This is because your condition that checks post's privacy requires the post to be there:
AND p.privacy = 1 OR (p.privacy = 2 AND fr.fstatus = 1)
When an outer join is about to produce a row that corresponds to a notification without a post, it would check the above condition. Since the post is not there, p.privacy
would evaluate to NULL
, "contaminating" both sides of the OR
, and eventually making the whole condition evaluate to false
.
Moving this condition into the ON
condition of the join will fix the problem:
SELECT
u.username AS sender,
ux.username AS receiver,
p.id
FROM notifications n
JOIN follows f ON (n.user_id = f.tofollow_id)
JOIN follows fr ON (n.tonotify_id = fr.tofollow_id)
JOIN user u ON (u.id = n.user_id)
JOIN user ux ON (ux.id = n.tonotify_id)
LEFT JOIN posts p ON (n.posts_id = p.id)
AND (p.privacy = 1 OR (p.privacy = 2 AND fr.fstatus = 1))
WHERE f.user_id = 1
AND fr.user_id = 1
AND f.status = 1
ORDER BY n.id DESC
Another way to fix this would be adding an IS NULL
condition to your OR
, like this:
SELECT
u.username AS sender,
ux.username AS receiver,
p.id
FROM notifications n
JOIN follows f ON (n.user_id = f.tofollow_id)
JOIN follows fr ON (n.tonotify_id = fr.tofollow_id)
JOIN user u ON (u.id = n.user_id)
JOIN user ux ON (ux.id = n.tonotify_id)
LEFT JOIN posts p ON (n.posts_id = p.id)
WHERE f.user_id = 1
AND fr.user_id = 1
AND f.status = 1
AND (p.privacy IS NULL OR p.privacy = 1 OR (p.privacy = 2 AND fr.fstatus = 1))
ORDER BY n.id DESC
Upvotes: 1
Reputation: 2761
You need to use an outer join such as LEFT JOIN
instead of an inner join using just JOIN
. An outer join will always return rows from one "side" (which is why it is called LEFT [OUTER] JOIN
and RIGHT [OUTER] JOIN
) even if the other "side" does not match anything.
Upvotes: 0