query finding non associated records based on a user - mysql

For the user_id in friendships the friend_id would be the "other" user and I want to select all users where a friendship doesn't exist yet.
I am using Ruby on Rails and I've tried to write this in one query with rails syntax but having difficulty I've gone for a raw SQL which is exactly what I need but it gives me back no results.
SELECT u.id, u.name, u.avatar_url FROM users u LEFT JOIN friendships f on f.friend_id=u.id WHERE f.friend_id IS NULL AND f.user_id = ? ORDER BY u.updated_at DESC LIMIT 50

SELECT u.id, u.name, u.avatar_url
FROM users u
LEFT JOIN friendships f on f.friend_id = u.id
WHERE f.friend_id IS NULL
ORDER BY u.updated_at DESC
LIMIT 50
Get rid of AND f.user_id = ?. The above query is going to find all users with no friends, I'm not sure what the purpose of the additional AND was for, but it seems like why you're not getting any results back.

So I've got this working nicely with table indexes on all keys but I'd rather have a rails way of doing it than pure SQL.
User.find_by_sql(["SELECT id, avatar_url, name FROM users WHERE id NOT IN (SELECT user_id FROM friendships WHERE friend_id=?) ORDER BY updated_at DESC LIMIT 9", session[:ytuserid]]).to_a
The current user can now see 9 people where the friendship doesn't exist.

Related

MySQL - how to combine three tables to get the counts

There are three tables, I would like to get the count of a user's total tweets and the count of likes his total tweets received.
I tried to combine two queries to get what I want but failed. Have looked through several previous questions but still can't figure it out.
Users table
id
name
1
User1
Tweets table
id
UserId (foreign key)
content
1
User1
hello
Likes table
id
UserId (foreign key)
TweetId (foreign key)
1
User1
hello
First query:
SELECT Users.name, Users.id, COUNT(Tweets.UserId) AS UserTweetCount FROM Users
LEFT JOIN Tweets
ON Users.id = Tweets.UserId
GROUP BY Users.id
ORDER BY UserTweetCount DESC;
Second query:
SELECT Users.name, Users.id, COUNT(Likes.UserId) AS UserTweetBeLikedCount FROM Users
LEFT JOIN Likes
ON Users.id = Likes.UserId
GROUP BY Users.id;
I tried like below but would get wrong UserTweetBeLikedCount counts. The counts would be UserTweetCount's, not UserTweetBeLikedCount's. When I ran two queries separately, it worked well. But when I combined them together, it didn't work right.
Don't know how to display the right counts. Can someone give me hints to solve this, please?
SELECT Users.name, Users.id,
COUNT(Tweets.UserId) AS UserTweetCount, COUNT(Likes.UserId) AS UserTweetBeLikedCount
FROM Users
LEFT JOIN Tweets
ON Users.id = Tweets.UserId
LEFT JOIN Likes
ON Users.id = Likes.UserId
GROUP BY Users.id
ORDER BY UserTweetCount DESC;
I recommend using correlated subqueries for this:
SELECT u.*,
(SELECT COUNT(*)
FROM Tweets t
WHERE u.id = t.UserId
) AS UserTweetCount,
(SELECT COUNT(*)
FROM Likes l
WHERE u.id = l.UserId
) AS UserLikeCount
FROM Users u
ORDER BY UserTweetCount DESC;
As a note: For performance, you want indexes on Tweets(UserId) and Likes(UserId).

Join results from two tables when data can appear in multiple columns in one table

I have one table containing a list of users, all I need from this are users.id and users.username
I have a second table that links the users as "friends", all I need from this is friends.one and friends.two
I want to output a result that shows all friends of a user with a certain user id (this will be a variable, but for the sake of the example we'll use user id '1'). User id '1' can appear in either friends.one or friends.two.
I've tried a few different ideas, but I'm not sure I'm any closer. The code below is obviously awful but I think it describes the idea well(ish). Though I'm probably overly complicating something which there is an easier method for,
SELECT users.username, users.id
FROM users
INNER JOIN friends
ON users.id = friends.friendone
WHERE friends.friendtwo='1'
UNION
SELECT users.username, users.id
FROM users
INNER JOIN friends
ON users.id = friends.friendtwo
WHERE friends.friendone='1'
ORDER BY users.username ASC;
With conditional join:
SELECT u.username, u.id
FROM friends f INNER JOIN users u
ON u.id = CASE '1'
WHEN f.friendone THEN f.friendtwo
WHEN f.friendtwo THEN f.friendone
END

Mysql query for fetching videos posted by friends of a particular user

Could anyone please help me to write a query in mysql to fetch the videos posted by friends of a particular user. I have 3 tables (users,friends and videos) and the table structure are as follows.
users - id
friends - id, user_id [from user table], friend_id [from user table]
videos - id, user_id [from user table], video_name
For ex: User with id 1 has 3 friends 2,3,and 4 mapped in friends table. Each friends has posted 2 videos, mapped in videos table. So when I login as user 1, i need to list the videos posted by 2,3 and 4 with their details fetched from user table.
Thanks in advance
JOIN is what you are looking for. This can be used to "link" multiple tables based on what is common between them. This is the backbone of Normalization
The first part will be to link the "Friends" table back to the "Users" table. I am going to use a local variable (#UserID) and set it to be yours. I really don't know what information you will want from the friends information, so I am going to use generics for last name and first name in all of the queries
SET #UserID = 1 -- Your UserID
SELECT u.LastName, u.FirstName
FROM Friends AS f
INNER JOIN Users AS u ON f.Friends_ID = u.User_ID
WHERE f.User_ID = #UserID
Then the second part is going to be linking the Users to the videos. This query will return the user name information along with the names of all the videos:
SELECT u.LastName, u.FirstName, v.video_name
FROM Users AS u
INNER JOIN Videos AS v ON u.User_ID = v.User_ID
So now we have the basics. For your example you want a user to get all of the videos from all of their friends. We can actually combine these 2 queries into one query to get this:
SET #UserID = 1 -- Your UserID
SELECT u.LastName, u.FirstName, v.video_name
FROM Friends AS f
INNER JOIN Users AS u ON f.Friends_ID = u.User_ID
INNER JOIN Videos AS v ON u.User_ID = v.User_ID
WHERE f.User_ID = #UserID

Get users with followers, and without followers

I have a really simple table - follow - in which I store followers.
user | following
-----------------
1 | 2
The above means user 1 is following user 2.
I want to display all users on the home page and order them buy who has the most followers, and then return the rest of the users who have no followers. The below query is working as far as displaying the users, but I can't figure out how to retrieve the users who do not have any followers. I've tried RIGHT JOIN users u ON f.following=u.id but that gives me weird results.
This query returns user 2 who has a follower, but doesn't return users 1 and 3, who do not have followers.
Edit: this query is also checking to see if the user is following back, which is why I'm joining using the ID of 1 as a test.
SELECT
u.id
,u.username
,u.avatar
,COUNT(1) AS followers
,ul.*
,fo.*
FROM follow f
LEFT JOIN users u ON f.following=u.id
LEFT JOIN follow fo ON fo.following=u.id AND fo.user=1
LEFT JOIN users_likes ul ON ul.likes=u.id AND ul.user=1
GROUP BY f.following
ORDER BY COUNT(1) DESC
SQL Fiddle: http://sqlfiddle.com/#!2/98f65/1
The problem with your query in the question is that you are left-joining to the follow table. That means that all rows in the follow table are included regardless of their connection to another table. What you want is to show all users, so that is the table that should be on the outer end of the join.
I also think you're trying to do too many things at once here, which is why you're having trouble figuring it out. You want to know who has followers and who doesn't, who's following back, order them, consider the users_likes and so on. I recommend taking a step back and breaking them down into individual queries, and then building those into one result set as needed.
To get the users and number of followers, you can outer join the users table with the follow table like this:
SELECT u.id, u.username, u.avatar, (IFNULL(COUNT(f.following), 0)) AS numFollowers
FROM users u
LEFT JOIN follow f ON f.following = u.id
GROUP BY u.id
ORDER BY numfollowers DESC;
IFNULL is used to check the cases when there are no followers, and no link is made in the outer join so a null value appears.
If you want to work in the users_likes table, you should add it in as another left join. The problem this causes, is that it will return null values for all columns if there are no likes. (Example, if I left join the users_likes table here, I will see null for users 1 and 3 because nobody 'likes' them.) To make the result set a little more understandable, I recommend you don't collect all rows of the users_likes table. Perhaps this query would make more sense:
SELECT u.id, u.username, u.avatar, (IFNULL(COUNT(f.following), 0)) AS numFollowers, ul.user AS likedByUser, ul.created_at
FROM users u
LEFT JOIN follow f ON f.following = u.id
LEFT JOIN users_likes ul ON ul.likes = u.id
GROUP BY u.id
ORDER BY numfollowers DESC;
As far as whether or not a user is following back, I think this would change a bit, as the above only shows the number of followers, and doesn't produce a row for each follower.
Let me know if you have any more questions, here is an SQL Fiddle for the above. I will leave it up to you for handling the null values that occur right now.
You can use an outer join (left or right) from Users to your current query in any number of ways. An easy example that should get you started. This isn't a clean-up up solution, just a dmeo of a way that will work.
SELECT a.*
,b.*
FROM users a
LEFT JOIN (
SELECT
u.id
,u.username
,u.avatar
,COUNT(1) AS followers
FROM follow f
LEFT JOIN users u ON f.following=u.id
LEFT JOIN follow fo ON fo.following=u.id AND fo.user=1
LEFT JOIN users_likes ul ON ul.likes=u.id AND ul.user=1
GROUP BY f.following
) b
ON a.id = b.id
ORDER BY followers DESC
You can do this:
SELECT * FROM (
SELECT u.id, u.username, u.avatar, COUNT(f.user) as followers
FROM users AS u
LEFT JOIN follow AS f ON u.id = f.following
GROUP BY u.id
) AS subselect ORDER BY subselect.followers DESC

MySQL: 3 table join query?

I have three tables (user, friends, posts) and two users (user1 and user2).
When user1 adds user2 as friend then user1 can see the posts of user2 just like on Facebook. But only the posts after the date when user1 added user2 as friend. My query is like this:
$sql = mysql_query("SELECT * FROM posts p JOIN friends f ON
p.currentuserid = f.friendid AND p.time >= f.friend_since OR
p.currentuserid='user1id' WHERE f.myid='user1id'
ORDER BY p.postid DESC LIMIT 20");
it is working all the way fine but with a little problem.....!!
it displays user2, user3 (all the users as friends of user1) posts for single time but shows user1 posts multiple.......i.e
user2. hi
user1. userssfsfsfsfsdf
user1. userssfsfsfsfsdf
user3. dddddddd
user1. sdfsdsdfsdsfsf
user1. sdfsdsdfsdsfsf
but i in database it is single entry/post why it is happening........!!
How can I fix it?
I'm not a SQL expert, but I think your problem is in the JOIN condition. I cannot see a way how you can join with posts and friends and get the result that you need. A SQL expert may know this, but for me it's just too difficult.
If I were you I would break the problem down in 2 parts:
Select the user's own posts
Select the user's friend's posts
For example, you can do this by using 2 different conditions and do the join with the friends table in a sub query (I have not tested this!):
select *
from posts p
where
p.currentuserid = 'user1id'
or
p.postid in
(
select p2.postid
from posts p2
join friend f on p2.currentuserid = f.friendid
where p2.time >= f.friend_since and f.myid='user1id'
)
Another way to do it is to use a union (also not tested..):
select *
from posts p
where
p.currentuserid = 'user1id'
union
select p2.*
from posts p2
join friend f on p2.currentuserid = f.friendid
where p2.time >= f.friend_since and f.myid='user1id'
I think, the easiest solution is to use GROUP BY statement on column posts.userId to remove duplicate entries. However it is not optimized way to solve the problem.
The reason you're getting the posts of all of user1's friends is you're not qualifying which friend's posts the query should return.
Add a f.friendid = 'user2id' (or whatever the column name is) in there before the WHERE clause.
You really should give some idea of what the schema looks like so we don't have to make so many assumptions. I'm assuming the primary key of user is id, and friends has a userid as well as a friendid field. I'm also assuming posts.currentuserid is the id of the user who created the post. If not, replace it with posts.userid or whatever the correct field is.
The reason your query doesn't work right is that you need at least 2 joins. When creating a query, it's easiest to start with what you have and work up to what you want, one join at a time. Here's the query to get the posts that a particular user can read:
SELECT p.*
FROM user u
JOIN friends f ON u.id = f.userid
JOIN posts p ON ((u.id = p.currentuserid) OR (f.friendid = p.currentuserid AND p.time >= f.friend_since))
WHERE u.id = ?
ORDER BY p.postid DESC LIMIT 20
The second join is where the meat is. It specifies that in order to read a post it (a) has to be written by you or (b) has to be written by a friend of yours AFTER you friended them.
If you want to also get the name of the user who created the post (assuming user.name holds the user name) you need a 3rd join:
SELECT pu.name as 'Posted By', p.*
FROM user u
JOIN friends f ON u.id = f.userid
JOIN posts p ON ((u.id = p.currentuserid) OR (f.friendid = p.currentuserid AND p.time >= f.friend_since))
JOIN user pu ON p.currentuserid = pu.id
WHERE u.id = ?
ORDER BY p.postid DESC LIMIT 20