MySQL: 3 table join query? - mysql

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

Related

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

query finding non associated records based on a user

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.

SQL query, which one is more sufficient?

I have a database with three tables:
post
user_id
friendship
user_id
friend_id
status
I want to select all posts whose author (user_id) is current_user's friend
I am wondering which one of the following statement is more efficient? (1 is current user's id, 2 is the code for being friend)
1)
SELECT * FROM posts
INNER JOIN friendships AS fs
ON fs.user_id=1 AND fs.friend_id=posts.user_id AND fs.status=2;
2) query twice
SELECT id FROM friendships
WHERE user_id = 1 AND status = 2
# Assuming get (1,2,3)
SELECT * FROM posts WHERE user_id IN (1,2,3)
Can anyone tell me which is faster? Because I do not have enough data, I can not test....
Neither. Join your tables in the same order you would access them as a human:
SELECT p.*
FROM friendships f
JOIN posts p ON p.user_id = f.friend_id
WHERE f.user_id = 1
AND f.status = 2
Here an index will be used based on the WHERE clause to find rows in friendships, which will then be used to access posts via an index.
Make sure there are indexes on posts.user_id and friendships.user_id.

MySQL JOIN ON the one of two columns that doesn't match variable

Let's jump right into it: I've got two simple tables set up in my MySQL database, a users table and a matches table. The users table holds, well, users. The matches table is meant to establish many-to-many connections between users and contains just two userID's.
What is want to query is a list of names of all matched users for the user with userID 1 but I can't wrap my head around it. The problem is that the userID (in this case 1) could be in either one field and I don't have a clue in which one.
Just to clarify; I mean something like this (please don't mind the weird pseudo-code):
SELECT users.name
FROM matches
INNER JOIN users
ON userId = (userId1 OR userId2 DEPENDS ON WHERE)
WHERE userId1 = '1'
OR userId2 = '1';
Could you please tell me if this is possible with MySQL and if so, what I should look for/if you would be so kind, give a simple example.
Thanks a lot.
The user of or in a join condition often prevents MySQL from using an index. The use of union or union all makes the query rather cumbersome. You can do what you want with left outer join:
SELECT coalesce(u1.name, u2.name) as name
FROM matches m LEFT JOIN
users u1
ON u.userId = m.userId1 AND m.userId2 = '1' LEFT JOIN
users u2
ON u.userId = m.userId2 AND m.userId1 = '1'
WHERE '1' in (m.userId1, m.userId2);
This should take advantage of indexes on users for looking up the values. If you want distinct names, then add the distinct keyword.
Try:
SELECT DISTINCT u.name
FROM matches m
INNER JOIN users u
ON (u.userId = m.userId1 AND m.userId2 = '1')
OR (u.userId = m.userId2 AND m.userId1 = '1')
Added DISTINCT to avoid duplicate rows.
See this fiddle.
Here's one way to do it that avoids excessive JOIN logic (to make sure SQL can use indexes on users.userId, matches.userId1, matches.userId2)
SELECT u.`name`
FROM
matches AS m
JOIN users AS u
ON m.userId1=u.userId
AND m.userId2='1'
UNION
SELECT u.`name`
FROM
matches AS m
JOIN users AS u
ON m.userId2=u.userId
AND m.userId1='1'
Something like this:
Select UserId1, UserId2
From Matches
Where UserId1 = 1
Union
Select UserId2, UserId1
From Matches
Where UserId2 = 1
Notice the order of the UserIds have been changed in the Select clause. This will give you a single list of matches with you searched user '1' in a single column and all their matches in the other.
This approach will require you then link in your users table as follows:
Select searchmatches.UserId1, searchmatches.UserId2, leftuser.Name, rightuser.name
From (
Select UserId1, UserId2
From Matches
Where UserId1 = 1
Union
Select UserId2, UserId1
From Matches
Where UserId2 = 1
) searchmatches
inner join users leftuser userMatches.UserId1 = leftuser.UserId
inner join users rightuser userMatches.UserId2 = rightuser.UserId
Hope that Helps! If you want you can remove one of the inner joins to the users table as you know who the left user is as you searched on them!

Count Number of UnViewed Posts

I have a db structure like:
posts
id
title
content
users
id
....
post_reads
post_id
user_id
How can I count the number of posts for which a particular user with an id say, x does not have a read record.
My SQL query currently looks like:
SELECT COUNT(posts.id) AS c
FROM `posts`
LEFT JOIN `post_reads` ON (`posts`.`id` = `post_reads`.`post_id`)
LEFT JOIN `users` ON (post_reads.user_id = `users`.`id` AND post_reads.user_id = x)
WHERE users.id IS NULL
AND post_reads.user_id IS NULL
I know I'm doing something wrong, although I'm not sure what that is.
This should to the trick
SELECT COUNT(posts.id) AS c
FROM posts
LEFT JOIN post_reads ON posts.id = post_reads.post_id AND post_reads.user_id = x
LEFT JOIN users ON post_reads.user_id = users.id
WHERE users.id IS NULL
Note that if you're not interested in doing anything with table users you can shorten this query to:
SELECT COUNT(posts.id) AS c
FROM posts
LEFT JOIN post_reads ON posts.id = post_reads.post_id AND post_reads.user_id = x
WHERE post_reads.user_id IS NULL
The first join you were doing is really an inner join, because it will never 'misfire'.
The second join will sometimes misfire, because you have the extra condition in there.
Therefore using the post_reads.some_id is null will never be true.
In order for that to work you'd have to repeat the AND post_reads.user_id = x in that join condition as well, but putting it in twice is silly and not needed, once will do.
PS don't forget to replace the 'x' with something more useful :-)
I tried this a few ways just using JOINS/WHERE, but they tend to miss certain cases (i.e. you can exclude posts joined to a read record for the given user, but the posts' ids will still be returned if they also join to read records for other users).
The simplest way may be something like this:
SELECT COUNT(DISTINCT id)
FROM posts
WHERE id NOT IN (SELECT DISTINCT post_id FROM post_reads WHERE user_id = #x)
Also, note that I don't believe you need to surround identifiers in backticks unless they are MySQL keywords.