-1

I'm pretty new to the world of databases and SQL.

I'm trying to build a system for users to submit posts and like posts. I've been looking at a few similar questions on this site, reading about third normal form, etc. and I think I have a pretty solid DB schema at this point:

CREATE TABLE IF NOT EXISTS USERPROFILES (
  ID INT AUTO_INCREMENT PRIMARY KEY,
  USERNAME VARCHAR(64) NOT NULL,
  EMAIL_ADDRESS VARCHAR(255) NOT NULL,
  COUNTRY_CODE CHAR(2) NOT NULL,
  CREATED_DATE DATETIME(9) NOT NULL
)
ALTER TABLE USERPROFILES ADD CONSTRAINT USERPROFILES_USERNAME_UNIQUE UNIQUE (USERNAME)
ALTER TABLE USERPROFILES ADD CONSTRAINT USERPROFILES_EMAIL_ADDRESS_UNIQUE UNIQUE (EMAIL_ADDRESS)

CREATE TABLE IF NOT EXISTS USERPOSTS (
  ID INT AUTO_INCREMENT PRIMARY KEY,
  THREAD_ID INT NOT NULL,
  PARENT_POST_ID INT NULL,
  PROFILE_ID INT NOT NULL,
  WRITTEN_TEXT VARCHAR(500) NOT NULL,
  MEDIA_LOCATION VARCHAR(255) NULL,
  CREATED_DATE DATETIME(9) NOT NULL,
  CONSTRAINT FK_USERPOSTS_PROFILE_ID__ID FOREIGN KEY (PROFILE_ID) REFERENCES USERPROFILES(ID) ON DELETE RESTRICT ON UPDATE RESTRICT
)

CREATE TABLE IF NOT EXISTS USERVOTES (
  POST_ID INT,
  PROFILE_ID INT,
  CREATED_DATE DATETIME(9) NOT NULL,
  CONSTRAINT pk_UserVotes PRIMARY KEY (POST_ID, PROFILE_ID),
  CONSTRAINT FK_USERVOTES_POST_ID__ID FOREIGN KEY (POST_ID) REFERENCES USERPOSTS(ID) ON DELETE RESTRICT ON UPDATE RESTRICT,
  CONSTRAINT FK_USERVOTES_PROFILE_ID__ID FOREIGN KEY (PROFILE_ID) REFERENCES USERPROFILES(ID) ON DELETE RESTRICT ON UPDATE RESTRICT
)

Now what I want to do is perform a single query for my application to fetch all USERPOSTS with a given THREAD_ID (on behalf of a requesting user), but also join with the USERVOTES and USERPROFILES tables such that I can get a list of posts combined with the following data:

  • total like count for the post
  • if the requesting user liked the post or not
  • the username of the submitter of the post (not just their userprofile ID)

With this I can render everything I need in the application.

Can anyone help construst a single query like this given my table schema? I'm assuming I will need at least two INNER JOINs (one on USERVOTES and one on USERPROFILE) as well as a COUNT on the USERVOTES (but also, in addition, how do I query USERVOTES with a WHERE for a specific requesting USERID)?

Thanks!

5
  • you would use LEFT JOINS, because there may not be a vote. SUM(USERVOTES.PROFILE_ID={cost}) as Xvoted will show if a specific user voted.
    – danblack
    Jan 10, 2022 at 2:03
  • "total like count for the post" - Where do you store likes in your schema?...is the mere existence of there being a record in the USERVOTES table representative of a like?...if so, then the USERVOTES table will never store dislikes or other types of voting?
    – J.D.
    Jan 10, 2022 at 3:29
  • @J.D. yes, that is correct. Perhaps I should rename this table to USERLIKES - because there is only one kind of vote (a like)
    – idolize
    Jan 10, 2022 at 5:17
  • @idolize Yes probably makes more sense, and if I'm giving away my 2 cents, I don't see a need to prefix everything with the word USER. In fact, the likes describe the posts not the users so if you're looking for a prefix, I'd actually name it PostLikes to be more clear.
    – J.D.
    Jan 10, 2022 at 5:22
  • Good point - thank you!
    – idolize
    Jan 11, 2022 at 1:42

1 Answer 1

1

Something like this should do the trick (note the aliases, and feel free to adjust the casing to your preferred format):

SELECT 
    Posts.ID, 
    SUM(CASE WHEN Likes.ID IS NULL THEN 0 ELSE 1 END) AS TotalLikesCount,
    SubmitterProfiles.USERNAME,
    MAX(CASE WHEN Likes.PROFILE_ID = 98765 THEN 1 ELSE 0 END) AS DidRequestingUserLikeThisPost
FROM USERPOSTS AS Posts
INNER JOIN USERPROFILES AS SubmitterProfiles -- Inner Join should since a Post must be submitted by a User
    Posts.PROFILE_ID = SubmitterProfiles.ID
LEFT JOIN USERVOTES AS Likes -- Outer (Left) Join because not all Posts necessarily have Likes
    ON Posts.ID = Likes.POST_ID
LEFT JOIN USERPROFILES AS VoterProfiles -- Need to continue with an Outer (Left) Join here because we're pulling the profile of the User who liked the Post (if there is a like)
    ON Likes.PROFILE_ID = VoterProfiles.ID
WHERE Posts.THREAD_ID = 12345
GROUP BY Posts.ID, SubmitterProfiles.USERNAME

Please read the comments for more information, but basically this starts with your USERPOSTS table and inner joins your USERPROFILES table (which should always have a many-to-one relationship), then outer joins the remaining information (because some posts may not have likes).

Since the relationship between USERPOSTS and USERVOTES is one-to-many (when there are likes) the results set becomes a single row per like, so we need to group up by the ID of the post so we can count how many likes (rows) there are which I'm doing using the SUM() function with a CASE statement to handle NULL cases (when there's no likes for a given post).

We also have to group by the USERPROFILES.USERNAME so that we can SELECT it and this doesn't change the cardinality of our results, since every post can only be submitted by a single user.

5
  • Thank you! I'm dissecting this now and it generally makes a lot of sense. Only question I have is how would I also get not only the total likes for each post, but also a boolean for if the specific requesting user liked the post or not? I'm thinking it would be another CASE in the SELECT? Perhaps ,CASE WHEN Likes.ID = 12345 THEN TRUE ELSE FALSE AS ReqUserLiked
    – idolize
    Jan 11, 2022 at 1:40
  • 1
    @idolize Sorry I missed that criteria. But I think I updated my answer to account for that now. Correct you'd need a CASE statement for that and this time using the MAX() aggregate function to see if any of the likes was the requesting user by the PROFILE_ID. Note SQLite doesn't have a boolean data type so typical convention is to use the integer data type it has and return 1 for true and 0 for false.
    – J.D.
    Jan 11, 2022 at 1:52
  • @idolize Also I just noticed your table definitions use data types that don't exist in SQLite either. Please see Data types for more information on which types exist so you can pick the appropriate one's for your tables.
    – J.D.
    Jan 11, 2022 at 1:59
  • 1
    Amazing - makes sense. Thanks again for your help!
    – idolize
    Jan 11, 2022 at 2:19
  • @idolize No problem at all, best of luck!
    – J.D.
    Jan 11, 2022 at 2:53

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.