Search code examples
sqlpostgresqlsuminner-joinsql-null

Problems with multiple joins and a sum


If have the following three PostgreSQL tables:

Post table:

postid | title | author | created

Vote table:

postid | username | vote

where vote is equal to 1 if the user voted the post up, 0 if the user did not vote and -1 if the user voted the post down.

Comment table:

commentID | parentID | postID | content | author | created

where the parentID is null if the comment is not a reply.

I want to receive now for every post its title, author, created date, sum of all votes and the vote of the current logged in user and the number of comments. I already had problems with the vote of the user and asked here and someone helped me to get the following query:

SELECT post.postID as postID, post.title as title, post.author as author,
       post.created as created,
       COALESCE(sum(votes.vote), 0) as voteCount,
       COALESCE(sum(votes.vote) FILTER (WHERE votes.username = :username), 0) as userVote
       FROM post 
       LEFT JOIN votes ON post.postID = votes.postID 
       GROUP BY post.postID 
       ORDER BY voteCount DESC

Now I tried another LEFT JOIN to fetch the number of comments like this:

COUNT(DISTINCT comments) FILTER (WHERE comments.parentID IS NULL) as numComments
LEFT JOIN comments on post.postID = comments.postID

However, while the number of comments work, the number of votes on each post is wrong since due to the other join the rows seem to appear multiple times yielding a wrong sum and I have some trouble figuring out a way to solve this.

I already tried to fetch the number of comments as a subquery so that it is independent from the number of votes without success. Any further help would be very appreciated! :-)


Solution

  • You would typically pre-aggregate in subqueries before joining, like so:

    SELECT p.*
        COALESCE(v.voteCount, 0) as voteCount,
        COALESCE(v.userVote, 0) as userVote,
        COALESCE(c.numComments, 0) as numComments
    FROM post p
    LEFT JOIN (
        SELECT postID, 
            SUM(vote) as voteCount, 
            SUM(vote) FILTER (WHERE username = :username) userVote
        FROM votes 
        GROUP BY postID
    ) v ON v.postID = p.postID 
    LEFT JOIN (
        SELECT postID, count(*) numComments
        FROM comments
        WHERE parentID IS NULL
        GROUP BY postID
    ) c ON c.postID = p.postID
    ORDER BY voteCount DESC