How to query for both posts and post upvotes in MySQL? - mysql

I am attempting to model post upvotes and posts in MySQL. Currently I have an elements table for posts and a likes table for upvotes, structured as follows.
likes (id, elementID, googleID)
elements (id, googleID, title, body, type)
I have a URL route that will either return the most recent posts, or the posts with the most upvotes depending on a parameter. I want to query for a set of posts, each listing how many upvotes they have. The website won't display who upvoted, but the database should keep track of this to prevent multiple upvotes.
I tried to do something such as:
SELECT elements.id, elements.googleID, elements.title, likes.id, likes.elementID
FROM elements
INNER JOIN likes
ON elements.id=likes.elementID
This did not work well.
How would I get a set of posts, each showing how many upvotes they have when the upvotes are stored in a separate table?

I want to query for a set of posts, each listing how many upvotes
they have.
You can try this query. I used LEFT JOIN so you can still get the posts without likes.
SELECT E.id
, E.googleID
, E.title
, L.likeCount
FROM elements E
LEFT JOIN (
SELECT elementId
, COUNT(id) AS likeCount
FROM likes
GROUP BY elementId
) L ON L.elementId = E.id
Or (not pretty sure if this will run properly in MySQL)
SELECT E.id
, E.googleID
, E.title
, COUNT(L.id)
FROM elements E
LEFT JOIN likes L ON L.elementID = E.id
GROUP BY E.id
The website won't display who upvoted, but the database should keep
track of this to prevent multiple upvotes
You can create a UNIQUE CONSTRAINT in your LIKES table, for elementID and googleID columns. This will make sure that a googleID can only like one elementID. Otherwise, it will throw a unique constraint violation.
With that, when a user is upvoting a post, you can check in the database first if the user has already an existing record in the LIKES table before inserting one.

Related

Searching multiple rows that are not in a relationship n:m

Similar question here
Very similar to the question above but with a slight difference, I need to find a list of users that haven't seen at least one film in a list of movies.
Assuming two tables 'movies' and 'users', there's an n:m relationship between those, and a table 'seen' describing that relationship.
I need to find out for any number of given users, and any number of given movies all the users, from that given list, that have not watched at least one of the given movies.
Is this achievable in a single query? I can't figure out a way of do that.
Edit: Here's a demo with an attempt to solve the problem, the issue with that is it returns users that not have seen all of the movies from the given list. What we need is a user that has not seen ANY of the movies from that list: http://rextester.com/DEIH39789
This query should give you your desired result. I'm assuming your basic structure is:
users (id int, name varchar(20));
movies (id int, title varchar(20));
seen (user_id int, movie_id int);
SELECT u.*
FROM users u
LEFT JOIN seen s
ON s.user_id = u.id AND s.movie_id IN (movielist)
WHERE s.user_id IS NULL AND u.id IN (userlist)
The WHERE s.user_id IS NULL condition means the LEFT JOIN gives you all the users who have not seen any of the movies in movielist, and the u.id IN (userlist) then restricts the results to only that set of users.
You would modify the IN clauses to match the list of movies and users you were interested in. I've made a small demo on Rextester.
Update
I had misinterpreted the question; the desired result is for users who have not seen one (or more) of the movies in the list. This query solves that problem:
SELECT u.*
FROM musers u
LEFT JOIN seen s
ON s.user_id = u.id AND s.movie_id IN (1, 2)
WHERE u.id IN (1, 2, 3)
GROUP BY u.id
HAVING COUNT(s.movie_id) < 2
The result of the JOIN and WHERE is users (1, 2, 3) and the movies they have seen. If they have seen all of the movies in the movie list (1, 2), the COUNT of movies in seen will be 2, otherwise, if they have not seen one (or more) it will be less than 2. Here's an updated demo. Note that when the length of the movie list changes, the 2 in the HAVING clause must change to match the length of the movie list.
considering a n:m relationship between Users and Movies table, with intermediate table Seen.
SELECT * FROM Users u WHERE NOT EXISTS (SELECT UserId FROM Seen s WHERE s.UserId = u.ID)
this query will return Users which does not have any related record in Seen table
I would say something like a left-joining the users-table to the seen-table (and join that table to the movies-table).
(edited the code due to a comment from MatBailie)
Add the list-restritctions in the JOIN-clause (and not the WHERE-clause as MatBailie pointed out to me) and you get something like (Code below should work on SQL-Server but something similar should work for MySql as well):
SELECT COUNT(Users.ID)
FROM Users
LEFT JOIN Seen ON Users.ID = Seen.UserID AND Users.something IN (list)
LEFT JOIN Movies ON Seen.MovieID = Movie.ID AND Movies.something IN (list)
WHERE Movies.ID IS NULL
GROUP BY User.ID -- <-- This is probably optional
But as there are usually multiple ways to get the same result, the adjusted version of my previous answer:
SELECT COUNT(Users.ID)
FROM Users
LEFT JOIN Seen ON Users.ID = Seen.UserID
LEFT JOIN Movies ON Seen.MovieID = Movie.ID
WHERE (Users.something IN (list) OR Users.something IS NULL)
AND (Movies.something IN (list) OR Movies.something IS NULL)
AND Movies.ID IS NULL
GROUP BY User.ID -- <-- This is probably optional
Third attempt: Get all user-ids in list that have seen one of the movies. Next, get all user-ids in list and subtract the user-ids that have seen one of these movies. Know that for large data sets (a couple of thousand is not large) this query might become slow. To test it I've removed "userid = 2 and movieid = 3" from the list of seen, else I would not get a result. Now I see that Nick has not seen any of the first three movies (referring to your rextester example)
SELECT *
FROM musers
WHERE musers.id IN (1,2,3)
AND musers.id NOT IN (
SELECT musers.id
FROM musers
JOIN Seen ON musers.ID = Seen.UserID
JOIN Movies ON Seen.MovieID = movies.ID
WHERE movies.id IN (1,2,3) )

Retrieving data from 3 Mysql tables

Suppose I have 3 different tables relationships as following
1st is tbl_users(id,gender,name)
2nd is tbl_feeds(id,user_id,feed_value)
3rd is tbl_favs(id,user_id,feed_id)
where id is primary key for every table.
Now suppose I want to get data where those feeds should come which is uploaded by Gender=Male users with one field in every row that should say either the user who is calling this query marked that particular feed as favourite or not.
So final data of result should be like following :
where lets say the person who is calling this query have user_id=2 then is_favourite column should contain 1 if that user marked favourite that particular feed otherwise is_favourite should contain 0.
user_id feed_id feed_value is_favourite gender
1 2 xyz 1 M
2 3 abc 0 M
3 4 mno 0 M
I hope you getting my question , I m able to get feeds as per gender but problem is I m facing problem to get is_favourite flag as per particular user for every feed entry.
I hope some one have these problem before and I can get help from those for sure.
I would be so thankful if some one can resolve my this issue.
Thanks
Something like this should work:
SELECT
u.id AS user_id.
fe.id AS feed_id,
fe.feed_value,
IFNULL(fa.is_favourite, 0),
u.gender
FROM
tbl_users u
JOIN
tbl_feeds fe ON (fe.user_id = u.id)
LEFT JOIN
tbl_favs fa ON (
fa.user_id = u.id
AND
fa.feed_id = fe.id
)
In order to link your tables, you need to find the most common link between them all. This link is user_id. You'll want to create a relationship between all tables with JOIN in order to make sure each and every user has data.
Now I don't know if you're planning on making sure all tables have data with the user_id. But I would use INNER JOIN as it will ONLY show records of that user_id without nulls. If the other tables could POSSIBLY (Not always guaranteed) you should use a LEFT JOIN based on the tables that is it possible with.
Here is an SQLFiddle as an example. However, I recommend you name your ID fields as appropriate to your table's name so that way, there is no confusion!
To get your isFavorite I would use a subquery in order to validate and verify if the user has it selected as a favorite.
SELECT
u.userid,
u.gender,
f.feedsid,
f.feedvalue,
(
SELECT
COUNT(*)
FROM
tbl_favs a
WHERE
a.userid = u.userid AND
a.feedsid = f.feedsid
) as isFavorite
FROM
tbl_users u
INNER JOIN
tbl_feeds f
ON
u.userid = f.userid
~~~~EDIT 1~~~~
In response to your comment, I have updated the SQLFiddle and the query. I don't believe you really need a join now based on the information given. If you were to do a join you would get unexpected results since you would be trying to make a common link between two tables that you do not want. Instead you'll want to just combine the tables together and do a subquery to determine from the favs if it is a favorite of the user's.
SQLFiddle:
SELECT
u.userid,
f.feedsid,
u.name,
u.gender,
f.feedvalue,
(
SELECT
COUNT(*)
FROM
tbl_favs a
WHERE
a.userid = u.userid AND
a.feedsid = f.feedsid
) as isFavorite
FROM
tbl_users u,
tbl_feeds f
ORDER BY
u.userid,
f.feedsid

MySQL Query -- based on user and business ID's

I am working on a project right now and am rather stumped with a specific sql query I (need) to execute. Let me start off by showing the DB structure I need to pull from.
--posts_table--
ID
post_title
post_text
bus_id
This next table is what is screwing with me. The only way data related to the logged in user is in here is if they have "liked" a specific post -- otherwise there is no data related to that user in this table. Now there could be plenty of data related to a particular post, just generated from other users.
--likes_table--
ID
user_id
post_id
like
What I need this to do is grab all the posts from the post_table above where a specific business id is specified. From there, I need it to grab the "like" column in the likes_table if there is data in there related to the logged in user. If there is no data there, just leave that field null in the query. Below is a query I wrote that works until there is other "like" data in the like_table from other users.
SELECT posts.id, posts.post_text, posts.post_title, likes.post_id, likes.like
FROM posts LEFT JOIN likes ON posts.id = likes.post_id WHERE
posts.bus_id = 1 AND likes.user_id IS NULL OR likes.user_id = 1;
This works up until data has been entered in the table about a specific post being liked by a different user before that user has done anything with that post, whether they like or dislike it. I am not sure if this specific type of query is even possible, any help would be much appreciated.
Edit:
After looking at it again -- I got it, finally. I just needed to add one more AND. Below is the proper query I was looking for.
SELECT posts.id, posts.post_text, posts.post_title, likes.post_id, likes.like
FROM posts LEFT JOIN likes ON posts.id = likes.post_id AND posts.user_id = 1 WHERE
posts.bus_id = 1 AND likes.user_id IS NULL OR likes.user_id = 1;
Ahh, I think I get you -- is it that if a particular post hasn't been commented on by user_id number 1 at all, the row for that doesn't show up at all?
In that case, put your l.user_id=1 into the JOIN condition instead of the WHERE condition --- this will put a NULL in if user_id 1 hasn't liked or disliked a particular post.
SELECT p.id, p.post_text, p.post_title, l.post_id, l.likes
FROM posts p
LEFT JOIN likes l ON p.id = l.post_id AND l.user_id=1
WHERE p.bus_id = 1
The l.user_id IS NULL OR l.user_id=1 has been incorporated into the LEFT JOIN -- it doesn't make rows for the other user_ids.

Display only best result out of one-to-many select

I'm having trouble getting my head around how to handle something I've thought of in SQL. I have bookmarks, and comments that the users posted about them. I'm using a single table for all comments. So we have a one-to-many relationship between the bookmarks and the comments.
There is another table, acting as the middle-man, linking each bookmark to all its comments.
There are two types of comments. Suggested titles for the bookmark, and general comments. Suggested titles have both a title and a description, while general comments have only a description. There's also a rating system for the suggested titles, so that the home page can pick the top-rated title for each bookmark to display.
So, main things to make clear. There's the Bookmarks table with BID and URL, and also the Comments table with CID, Title, Comment, and Rating. The BooksNComms is the connecting table between them.
SELECT comments.title, comments.comment
FROM comments
INNER JOIN booksncomms ON comments.cid=booksncomms.cid
WHERE booksncomms.bid=1
AND comments.title is not null
ORDER BY comments.rating
LIMIT 0, 1;
The above works in getting the best Title and Description (Comment) for a certain BID. What I want to do is make the above work for, say, the 10 newest bookmarks.
SELECT bookmarks.url, comments.title, comments.`comment`, comments.rating
FROM bookmarks
INNER JOIN booksncomms
ON bookmarks.bid=booksncomms.bid
INNER JOIN comments
ON comments.cid=booksncomms.cid
JOIN (
SELECT bookmarks.bid
FROM bookmarks
ORDER BY bookmarks.datecreated DESC
LIMIT 1
)
AS a
ON a.bid=bookmarks.bid
WHERE comments.title IS NOT NULL
ORDER BY bookmarks.url;
The above gives me all titles for the 10 newest bookmarks.
Is there a way I can select only the highest rated title for each of the 10 newest bookmarks?
(OP's own solution, split off from the question)
#LefterisAslanoglou says:
I realized I knew the answer just a few minutes after I posted the question here. It's been bugging me for hours, but it was a simple matter of getting a table that has the best rated title for each bookmark and then joining that to the one with all the titles for the latest bookmarks.
SELECT bookmarks.url, comments.title, comments.comment, comments.rating
FROM bookmarks
INNER JOIN booksncomms ON bookmarks.bid=booksncomms.bid
INNER JOIN comments ON comments.cid=booksncomms.cid
JOIN (
SELECT bookmarks.bid
FROM bookmarks
ORDER BY bookmarks.datecreated DESC
LIMIT 10
) AS a ON a.bid=bookmarks.bid
JOIN (
SELECT comments.cid, MAX(comments.rating)
AS maxrating
FROM comments
INNER JOIN booksncomms ON comments.cid=booksncomms.cid
GROUP BY booksncomms.bid
) AS b ON b.cid=comments.cid
WHERE comments.title IS NOT NULL
ORDER BY bookmarks.datecreated DESC;
Try
SELECT bookmarks.url, comments.title, comments.`comment`, MAX(comments.rating) rating
FROM bookmarks
INNER JOIN booksncomms
ON bookmarks.bid = booksncomms.bid
INNER JOIN comments
ON comments.cid = booksncomms.cid
WHERE comments.title IS NOT NULL
ORDER BY bookmarks.datecreated DESC, bookmarks.url
LIMIT 10

Making a user profile activity feed

I need to build an activity feed to go on each users profile page showing what they have been doing on the site.
There is three tables: comments, ratings, users
I want the feed to include the comments and ratings that the user has posted.
in the comments and ratings table it stores the user id of the user who posted it, not the username, so in for each item in the news feed it needs to select from the users table where the user id is the same to retrieve the username.
All the entries in the feed should be ordered by date.
Here is what ive got even though i know it is not correct because it is trying to match both with the same row in the users table.
SELECT comments.date, comments.url AS comment_url, comments.user_id, ratings.date, ratings.url AS rating_url, ratings.user_id, users.id, users.username
FROM comments, ratings, users
WHERE comments.user_id=%s
AND comments.user_id=users.id
AND ratings.user_id=%s
AND ratings.user_id=users.id
ORDER BY ratings.date, comments.date DESC
JOIN. It seems you know that, but here's how:
SELECT * FROM comments LEFT JOIN users ON comments.user_id = users.id
Thus, as far as I can tell, you're trying to order two separate things at the same time. The closest I think I can come up with would be something like:
(SELECT comments.date AS date, users.username AS name, comments.url AS url CONCAT('Something happened: ',comments.url) AS text
FROM comments LEFT JOIN users ON comments.user_id = users.id
WHERE users.id = %s)
UNION
(SELECT ratings.date AS date, users.username AS name, ratings.url AS url CONCAT('Something happened: ',ratings.url) AS text
FROM comments LEFT JOIN users ON comments.user_id = users.id
WHERE users.id = %s)
ORDER BY date DESC LIMIT 0,10
Note that the columns of both parts of the union match up. I'm pretty sure that that is required for something like this to work. That's why I have that CONCAT statement, which lets you build a string that works differently between ratings and comments.