Simplify MySQL query - mysql

I have a MySQL query that I used to use to return mutual friends between two users, but now that I am recoding my website, I am trying to simplify this code, or at least make it better.
So here's my code below to check mutual friends:
SELECT a.friendID
FROM
(SELECT CASE WHEN userID = $id
THEN userID2
ELSE userID
END AS friendID
FROM friends
WHERE (userID = $id OR userID2 = $id)
AND state='1'
) a
JOIN
(SELECT CASE WHEN userID = $session
THEN userID2
ELSE userID
END AS friendID
FROM friends
WHERE (userID = $session OR userID2 = $session)
AND state='1'
) b
ON b.friendID = a.friendID
My table is set up like this:
userID -- userID2 -- state
1 ------- 2 ------- 1
2 ------- 3 ------- 1
1 ------- 3 ------- 0
(sorry, I don't know how to do the pretty database structure design, so if someone could edit that for me...)
but for the above, when user 1 is on user 3's profile, since user 1 and user 2 are friends, and user 2 and user 3 are friends, but user 1 and user 3 are not, it should return user 2 as a mutual friend. (state 1 means friendship accepted, state 0 means friendship pending, so only if state is 1 should it be counted as a friend)
Also note that userID and userID2 can be in any order, it depends on who requests who as a friend, so like the above query does, I need to also have the "friendID" returned, as the above query does right.

SELECT t.f FROM friends JOIN (
SELECT IF(userID = $id, userID2, userID) f
FROM friends
WHERE state AND $id IN (userID, userID2)
) t ON t.f IN (userID, userID2)
WHERE state AND $session IN (userID, userID2)
See it on sqlfiddle.

Not necessarily the fastest but simple to read:
Create View ActiveFriends As
Select
UserId, UserID2
From
friends
Where
State = '1'
Union All
Select
UserId2, UserID
From
friends
Where
State = '1'
Select
f1.userID2 as MutualFriendId
From
ActiveFriends f1
Inner Join
ActiveFriends f2
On f1.UserID2 = f2.UserID
Where
f1.UserID = $session And
f2.UserID2 = $id
http://sqlfiddle.com/#!2/5748f/1/0

You could do :
SELECT userID2
FROM friends_table
WHERE userID IN ( $id, $session )
AND state = 1
GROUP BY userID2
HAVING COUNT(userID2) >= 2
friends_table :
user_id | friend_id | state
-----------------------------
1 | 7 | 1
2 | 3 | 0
7 | 1 | 1
User 1 and 7 are friends, user 2 wants to be friends with user 3, but user 3 hasn't responded yet.

at the first glance I would say that using intersect is a good solution
so this query should do the trick for you
SELECT userID2
FROM friends
WHERE userID = $fromID
INTERSECT
SELECT userID2
FROM friends
WHERE userID = $theOtherID
EDIT:
another less clear solution would be
SELECT userID2
FROM friends INNER JOIN friends f2
USING (userID2)
WHERE friends.UserID = $fromID AND f2.userID = $theOtherID

Related

MySQL join not producing expected results with 'in' function

I have a game_players table like this (other columns omitted for brevity):
game_id user_id
1 1
1 3
2 1
2 2
2 4
My intention is to show the user that's logged in only the games they're involved in (e.g. user 2 should only see game 2).
The "where 2 in(select game_players.user_id from game_players)" bit doesn't appear to be working, I get a list of all the games - including the ones user 2 isn't involved in.
select games.game_id as 'game_id',
games.date_game_started as 'date_started',
users.username as 'username',
users.permanent_id as 'permanent_id',
game_players.user_id as 'user_id'
from games
inner join game_players on games.game_id = game_players.game_id
inner join users on game_players.user_id = users.user_id
where 2 in(select game_players.user_id from game_players)
and games.game_active = 1
and game_players.current_turn = 1
group by(games.game_id)
order by field(game_players.user_id, 2) desc,
games.date_game_started asc
Given my test data, I get this result set:
game_id date_started username permanent_id user_id
1 2021-12-15 13:33:17 userc userc 3
2 2021-12-15 13:35:20 Admin admin 1
I should only be getting the second row, because user 2 is only involved in game 2.
I'll admit that my SQL is a bit rusty, please can you help?
I simplified/broke it down and found the answer. I think... so far so good on my testing of it.
Needed to change:
where 2 in(select game_players.user_id from game_players)
to...
where game_players.game_id in(select game_id from game_players where user_id = 2)

How to get following users post feed that has visibility condition?

I am trying to make an SQL query on postgresql database that should give me posts feed of my posts and posts of users that I follow and my friends ( which is mutual follow ) I have this table structure
users table
id username
1 me
2 user2
3 user3
4 user4
relationships table
id follower_id following_id
1 1 2 // me following user2
2 2 1 // user2 also following me so we are friends
3 3 1
posts table
id user_id post visibility
1 2 post1 friends
2 2 post2 public
3 2 post3 public
4 1 post4 public
5 3 post5 public
select p.*
from posts p
where (
visibility = 'friends' and
user_id in (select following_id from relationships r1 where r1.follower_id = 1) and
user_id in (select follower_id from relationships r2 where r2.following_id = 1)
) or (
visibility = 'public' and
user_id in (select following_id from relationships r3 where follower_id = 1)
)
and this is the query I made which is giving a result but for me it is not an efficient query I need a better query to get the result as
user id 1's feed should be
id user_id post visibility
1 2 post1 friends
2 2 post2 public
3 2 post3 public
4 1 post4 public
Look next query:
select p.*
from posts p
join (
-- get followers and friends
select distinct relationships.*, coalesce(friends.follower_id, 0) as friend_id
from relationships
-- join self for check is follower friend
left join relationships friends on
relationships.following_id = friends.follower_id and
relationships.follower_id = friends.following_id
where relationships.follower_id = 1
) followers on (
visibility = 'friends' and followers.friend_id = p.user_id or
visibility = 'public' and followers.following_id = p.user_id
);
Try this query on SQLize.online

MySQL select rand and exclude users with condition

I need to select random user_id from "user" table, and completely exclude any user_id if current user have any "ongoing" battles with him battles.status
Query:
SELECT user.id
FROM user
LEFT JOIN battles b ON b.uid = user.id AND b.status <> 'ongoing'
WHERE user.id <> 1
ORDER BY RAND( )
LIMIT 1
But the query is not sufficient, because a user can have multiple battles with specific other users, one of them "ongoing" and the others "finished",
My query should select users from the "finished" row.
Tables structure:
user table:
id name
1 John
2 Sarah
3 Jack
4 Andy
5 Rio
battles table:
id uid uid2 status
1 1 2 finished
2 1 2 ongoing
3 2 3 ongoing
4 1 4 finished
5 3 5 finished
If "my" id = "1",
I want to completely exclude any user I have ongoing battle with him, like "2" in the above case and accept all other ids (i.e.3,4 and 5)
You probably want something along the lines of this:
SELECT foe.*
-- Select yourself and join all other users to find potential foes
FROM `user` AS me
INNER JOIN `user` AS foe
ON (me.id <> foe.id)
-- Here we select the active user
WHERE me.`id` = 1
-- Now we exclude foes we have ongoing battles with
-- (your id could be in either uid or uid2)
AND foe.`id` NOT IN (
SELECT `uid` FROM `battles`
WHERE `uid2` = me.`id` AND `status` = 'ongoing'
UNION ALL
SELECT `uid2` FROM `battles`
WHERE `uid` = me.`id` AND `status` = 'ongoing'
);
This will return a list of users which you do not currently have ongoing battles with. You can customise this to return just one of them using LIMIT and random ordering like in your example.

Select the opposite row of a WHERE clause in a joined jquery result

I have a database with users and games. 1 game can have multiple users so I made a linking table called users_games. The crux is that a game can always only have 2 players since it is a board game. I know which player I am, i have my user_id and my email, but I would like to gain a result that gives me a list of all games I am in WITH the user_id and email of the other fellow. So a query that looks to all games I am in and give the other row, with the name of the player.
My tables:
games
id (int)
board (varchar) representation of the board
users
id (int)
email (varchar)
password (varchar MD5)
users_games
id (int)
user_id (int)
game_id (int)
For clarification this query
SELECT *
FROM `tic_users_games` AS ug
LEFT JOIN tic_users AS u
ON ug.user_id = u.id
RIGHT JOIN tic_games AS g
ON ug.game_id = g.id
And result
id user_id game_id id email password id board created updated
1 1 1 1 ME#gmail.com d56b699830e77ba53855679cb1d252da 1 0|0|0|0|0|0|0|0|0 2012-04-02 16:56:06 2012-04-02 16:56:06
2 2 1 2 FOE1#gmail.com d56b699830e77ba53855679cb1d252da 1 0|0|0|0|0|0|0|0|0 2012-04-02 16:56:06 2012-04-02 16:56:06
3 3 2 2 FOE2#gmail.com d56b699830e77ba53855679cb1d252da 2 0|0|0|0|0|0|0|0|0 2012-04-02 16:56:06 2012-04-02 16:56:06
4 1 2 1 ME#gmail.com d56b699830e77ba53855679cb1d252da 2 0|0|0|0|0|0|0|0|0 2012-04-02 16:56:06 2012-04-02 16:56:06
See: In the above case I just want 2 rows: game_id 1 and 2, with FOE1#... and FOE2#...
Thanks
This should do it for you. The syntax might not be exact for mysql but you should get there. Basically get all users that have games in the user_games table with the same game_ID as the games I am in:
SELECT
User_Games.Game_ID,
Users.ID,
Users.Email
FROM
Users
LEFT JOIN User_Games ON Users.ID = User_Games.User_Id
WHERE
Users.User_ID <> #yourUserID
AND EXISTS
(SELECT
NULL
FROM
User_Games AS MyUserGames
WHERE
User_Games.Game_ID = MyUserGames.Game_ID
AND MyUserGames.User_ID = #yourUserID)
Try this (where #userid represents the user you are searching games for, in this case, 1):
select *
from tic_users_games ug1
left join tic_users_games ug2 on ug1.game_id = ug2.game_id
left join tic_users u on ug2.user_id = u.id
right join tic_games g on ug2.game_id = g.id
where ug1.user_id = #userid
and ug2.user_id <> #userid
Demo: http://www.sqlfiddle.com/#!2/7b0f6/2

"friends of friends" like sql query

I am building an application where users can connect with each other (something like friends in any social network).
I am saving that connections in a table with the following structure:
id_user1 | id_user2 | is_accepted | is_blocked | created_at
The connections between users are bi-directional, so when two users are connected there is only one record in the table. Doesnt matter if the user_id is in id_user1 or id_user2 collumn.
Now i need to write a sql query to get "friends of friends" of a certain user that are not already friends of the user.
Also the user must be accepted and not blocked.
In resume, here are the steps i need to execute.
Find all the users id associated with the user I want (id_user1 = current_user or id_user2 = current_user and is_accepted and !blocked)
foreach of the returned user_ids --> get all the associated users( ignore associations with current user) (make sure it is accepted and !blocked also).
How can I do such query?.
Thanks for your help.
For the reasons others have mentioned, and because I've seen it work better in other systems, I'd go with a row for each direction.
primary_user_id | related_user_id | is_accepted | is_blocked | created_at
You can also then create a clustered index on the user_id which should more than offset the overhead of doubling the number of rows.
Your first query would then translate into something like this:
SELECT f1.related_user_id
FROM friends f1
WHERE f1.primary_user_id = #current_user
AND f1.is_accepted = 1 AND f1.is_blocked = 0
AND EXISTS (
SELECT *
FROM friends f2
WHERE f1.related_user_id = f2.primary_user_id
AND f2.related_user_id = #current_user
AND f2.is_accepted = 1 AND f2.is_blocked = 0
Not sure if you can do table functions in MySql. If so then wrap this up into a function to make your second query simpler.
SELECT CASE f2.id_user1 WHEN CASE f1.id_user1 WHEN $user THEN f1.id_user2 ELSE f1.id_user1 END THEN f2.id_user2 ELSE f2.id_user1 END
FROM friends f1
JOIN friends f2
ON f2.id_user1 = CASE f1.id_user1 WHEN $user THEN f1.id_user2 ELSE f1.id_user1 END
OR f2.id_user2 = CASE f1.id_user1 WHEN $user THEN f1.id_user2 ELSE f1.id_user1 END
WHERE (f1.id_user1 = $user OR f1.id_user = $user)
AND f1.is_accepted = 1
AND f2.is_accepted = 1
AND f1.is_blocked = 0
AND f2.is_blocked = 0
AND NOT (f1.id_user1, f1.id_user2) = (f2.id_user1, f2.id_user2)
Note that it is better to store the users least first, greatest second. In this case the query would be more simple.
When working with the one-record-per-friendship table directly, all queries will be bloated and error-prone, because you will have to write 'id_user1 = ... or id_user2 = ...' often. I'd create a view
CREATE VIEW bidifreinds (id_user1, id_user2, is_accepted, is_blocked, created_at) AS
SELECT id_user1, id_user2, is_accepted, is_blocked, created_at FROM friends
UNION
SELECT id_user2, id_user1, is_accepted, is_blocked, created_at FROM friends
This will make life much easier.
Then you can write
SELECT f1.id_user1, f2.id_user2
FROM friends f1, friends f2
WHERE f2.id_user1 = f1.id_user2
AND f1.is_accepted
AND NOT f1.is_blocked
AND f2.is_accepted
AND NOT f2.is_blocked
And I hope you're not usind MySQL, because MySQL is very slow at querying over views.
select id_user1 from friends where is_accepted = 1 and is_blocked = 0 and id_user2 in
(select id_user1 from friends where is_accepted = 1 and is_blocked = 0 and id_user2 = :a_user:
union
select id_user2 from friends where is_accepted = 1 and is_blocked = 0 and id_user1 = :a_user:)
union
select id_user2 from friends where is_accepted = 1 and is_blocked = 0 and id_user1 in
(select id_user1 from friends where is_accepted = 1 and is_blocked = 0 and id_user2 = :a_user:
union
select id_user2 from friends where is_accepted = 1 and is_blocked = 0 and id_user1 = :a_user:)
You can add a whereClause to weed out :a_user: from the resultset.