I have 3 tables – posts, friendships, and likes — the important fields described below.
posts: id, user_id, title
friendships: requester_id, accepter_id, is_confirmed
likes: post_id, user_id
I have a method called get_relevant_posts() which should return posts that either I’ve written, my confirmed friends have written, or my confirmed friends have liked. I’ve already implemented this in SQLAlchemy, but there’s a bug in my query because it only works if the friendships table is not empty (doesnt matter whats in it, just cant be empty). If it is empty, nothing gets returned (improper join?).
This is not a big issue as the friendships table will have something in it 99.99% of the time. But I still want to take a step back and write this in plain SQL — I just can’t seem to wrap my head around it.
SQLALCHEMY CODE
Friendship1 = aliased(Friendship)
Friendship2 = aliased(Friendship)
Friendship3 = aliased(Friendship)
Friendship4 = aliased(Friendship)
return (DBSession.query(Post)
.outerjoin(Friendship1, Post.user_id==Friendship1.accepter_id)
.outerjoin(Friendship2, Post.user_id==Friendship2.requester_id)
.outerjoin(Like, Post.id==Like.post_id)
.filter(or_(
# My post
self.id==Post.user_id,
# My friends post, where I'm the requester of the friendship
and_(self.id==Friendship1.requester_id,
Friendship1.is_confirmed==True),
# My friends post, where I'm the accepter of the friendship
and_(self.id==Friendship2.accepter_id,
Friendship2.is_confirmed==True),
# Somebodies post, which my friends found interesting
and_(self.id==Friendship3.requester_id,
Friendship3.is_confirmed==True,
Like.user_id==Friendship3.accepter_id),
and_(self.id==Friendship4.accepter_id,
Friendship4.is_confirmed==True,
Like.user_id==Friendship4.requester_id)))
.order_by(Post.created_at.desc())
.limit(limit)
.offset(offset)
.all())
PRODUCES THIS
SELECT posts.title AS posts_title
FROM friendships AS friendships_1, friendships AS friendships_2, posts LEFT OUTER JOIN friendships AS friendships_3 ON posts.user_id = friendships_3.accepter_id LEFT OUTER JOIN friendships AS friendships_4 ON posts.user_id = friendships_4.requester_id LEFT OUTER JOIN likes ON posts.id = likes.post_id LEFT OUTER JOIN users AS users_1 ON users_1.id = posts.user_id
WHERE (posts.user_id = %s OR friendships_3.requester_id = %s AND friendships_3.is_confirmed = %s OR friendships_4.accepter_id = %s AND friendships_4.is_confirmed = %s OR friendships_1.requester_id = %s AND friendships_1.is_confirmed = %s AND likes.user_id = friendships_1.accepter_id OR friendships_2.accepter_id = %s AND friendships_2.is_confirmed = %s AND likes.user_id = friendships_2.requester_id) ORDER BY posts.created_at DESC
LIMIT %s
Assuming we have MS SQL 2005
or replace CTE rel with sub-query.