MySQL: Finding users with similar interests - sql

I have 2 tables in my SQL db:
Users: id | email | religion | politics
Interests: id | user_id | interest_name
Given a user1 id, what is the best way to find a second user with at least 1 matching interest? Also, note that the religion/politics in the Users table should also be used for this match.
Any help appreciated,
- Andy

select * from users
where id in (
select id from interests where interest_name in
( select interest_name from interests where id = :current_user_id ))

(PostgreSQL)
I won't argue with your decision to elevate religion and politics above ordinary interests. (That doesn't mean it's a good idea; it just means I won't argue with you about it.)
create table users (
user_id integer primary key,
email_addr varchar(35) not null,
current_religion varchar(35) not null default 'None',
current_politics varchar(35) not null default 'None'
);
insert into users values
(1, 'user#userdomain.com', 'Muslim', 'Liberal'),
(2, 'user#differentdomain.com', 'Muslim', 'Conservative'),
(3, 'user#yadn.com', 'Christian', 'Liberal');
create table interests (
user_id integer not null references users (user_id),
user_interest varchar(20) not null,
primary key (user_id, user_interest));
insert into interests values
(1, 'Walks on the beach'),
(1, 'Women'),
(1, 'Polar bears'),
(2, 'Walks on the beach'),
(2, 'Women'),
(2, 'Little Big Man'),
(3, 'Running on the beach'),
(3, 'Coffee'),
(3, 'Polar bears');
-- Given one user id (1), find a different user with at least
-- one matching interest. You can do this without referring
-- to the users table at all.
select t1.user_id, t1.user_interest, t2.user_id
from interests t1
inner join interests t2 on (t2.user_interest = t1.user_interest)
where t1.user_id = 1 and t2.user_id <> 1;
Returns
1 Walks on the beach 2
1 Women 2
1 Polar bears 3
To also match on, say, religion, you can do essentially the same thing with the table "users".
select t1.user_id, t1.current_religion as interest, t2.user_id
from users t1
inner join users t2 on (t1.current_religion = t2.current_religion)
where t1.user_id = 1 and t2.user_id <> 1
Returns
1 Muslim 2
You can exploit the similar structure to bring religious interests and ordinary interests together using UNION.
select t1.user_id, t1.current_religion as interest, t2.user_id
from users t1
inner join users t2 on (t1.current_religion = t2.current_religion)
where t1.user_id = 1 and t2.user_id <> 1
union
select t1.*, t2.user_id
from interests t1
inner join interests t2 on (t2.user_interest = t1.user_interest)
where t1.user_id = 1 and t2.user_id <> 1;
Returns
1 Walks on the beach 2
1 Women 2
1 Polar bears 3
1 Muslim 2

Related

SQL to Select only full groups of data

Let's say I have three sample tables for groups of people as shown below.
Table users:
id
name
available
1
John
true
2
Nick
true
3
Sam
false
Table groups:
id
name
1
study
2
games
Table group_users:
group_id
user_id
role
1
1
teach
1
2
stdnt
1
3
stdnt
2
1
tank
2
2
heal
And I need to show to a user all groups that he participates in and also available right now, which means all users in that group have users.available = true.
I tried something like:
SELECT `groups`.*, `users`.* , `group_users`.*
FROM `groups`
LEFT JOIN `group_users` ON `groups`.`id` = `group_users`.`group_id`
LEFT JOIN `users` ON `users`.`id` = `group_users`.`user_id`
WHERE `users`.`available` = true AND `users`.`id` = 1
But it just shows groups and part of their users, that are available. And I need to have ONLY the groups that have all their users available.
If I were to find all available groups as User 1 - I should get only group 2 and it's users. How to do this the right way?
Tables DDL:
CREATE TABLE users (
id int PRIMARY KEY,
name varchar(256) NOT NULL,
available bool
);
CREATE TABLE teams (
id int PRIMARY KEY,
name varchar(256) NOT NULL
);
CREATE TABLE team_users (
team_id int NOT NULL,
user_id int NOT NULL,
role varchar(64)
);
INSERT INTO users VALUES
(1, 'John', true ),
(2, 'Nick', true ),
(3, 'Sam' , false);
INSERT INTO teams VALUES
(1, 'study'),
(2, 'games');
INSERT INTO team_users VALUES
(1, 1, 'teach'),
(1, 2, 'stdnt'),
(1, 3, 'stdnt'),
(2, 1, 'tank' ),
(2, 2, 'heal' );
mySQL select version() output:
10.8.3-MariaDB-1:10.8.3+maria~jammy
Check do you need in this:
WITH cte AS (
SELECT users.name username,
teams.id teamid,
teams.name teamname,
SUM(NOT users.available) OVER (PARTITION BY teams.id) non_availabe_present,
SUM(users.name = #user_name) OVER (PARTITION BY teams.id) needed_user_present
FROM team_users
JOIN users ON team_users.user_id = users.id
JOIN teams ON team_users.team_id = teams.id
)
SELECT username, teamid, teamname
FROM cte
WHERE needed_user_present
AND NOT non_availabe_present;
https://dbfiddle.uk/?rdbms=mysql_8.0&fiddle=605cf10d147fd904fb2d4a6cd5968302
PS. I use user name as a criteria, you may edit and use user's identifier, of course.
Join the tables and aggregate with the conditions in the HAVING clause:
SELECT t.id, t.name
FROM teams t
INNER JOIN team_users tu ON t.id = tu.team_id
INNER JOIN users u ON u.id = tu.user_id
GROUP BY t.id
HAVING MIN(u.available) AND SUM(u.id = 1);
The HAVING clause is a simplification of:
HAVING MIN(u.available) = true AND SUM(u.id = 1) > 0
See the demo.
first you need to find those group which users is available. then find the all the group details of those group which is not related to those group which user is available.
SELECT * FROM team_users a
JOIN teams b ON a.team_id=b.id
JOIN users c ON a.user_id=c.id
WHERE NOT EXISTS
(
SELECT 1 FROM team_users tu
JOIN users u ON tu.user_id=u.id AND u.available =1
WHERE tu.team_id=a.Team_id
)

Marking users as not active if they don't have required records

I have a simple table model. There are some required "Actions" that every user has to have completed.
User
-Id
-Name
-IsActive
Actions
-Id
-Name
UserActions
-UserID
-ActionID
I want to find all Users who don't have all the Action records in the UserAcitons table. If they don't have all actions records in UserActions, I want to mark IsActive as false.
There could be 20K users so this should be effecient to process.
Looking for the best way to do this without having a cursor.
Assuming rows in UserActions are unique, you can count the actions in the two tables and compare them:
update u
set isactive = (case when num_actions > total_actions then 1 else 0 end)
from users u left join
(select ua.userid, count(*) as num_actions
from useractions ua
group by ua.userid
) ua
on ua.userid = u.id cross join
(select count(*) as total_actions
from actions a
) a;
SQL Server doesn't support booleans, so this uses 0 for false and 1 for true.
You forgot to tell us some details, for example if there are duplicated actions, if ids are UNIQUE, etc.
for very simple scenario I managed to create the below example:
create table [User]
(
Id int not null primary key,
Name varchar(50) not null,
IsActive bit not null
)
create table [Actions]
(
Id int not null primary key,
Name varchar(50) not null
)
GO
create table [UserActions]
(
UserId int not null,
ActionId int not null,
foreign key (UserId) REFERENCES [User](Id),
foreign key (ActionId) REFERENCES [Actions](Id)
)
GO
insert into [User] values
(1, 'Alice', 1),(2, 'Bob', 1),(3, 'Caroline', 1)
insert into [Actions] values
(1, 'eat'),(2,'drink'),(3,'sleep')
insert into [UserActions] values
(1,1),(1,2),(1,3),
(2,1),(2,2),
(3,1),(3,2),(3,1)
GO
update us
set us.IsActive = 0
from [User] us
join
(
select ua.UserId, COUNT(distinct ua.ActionId) as ActionCount
from [UserActions] ua
group by ua.UserId
) as uac on uac.UserId = us.Id
where uac.ActionCount < (select count(*) from [Actions])
select * from [User] us
Provides de results below
Id Name IsActive
----------- ---------- --------
1 Alice 1
2 Bob 0
3 Caroline 0

Get room members, room's owner and admin at the same time in one query with grouped by id (unique) on PostgreSQL 12

I want to get room's member list, room's owner member in case of he doesn't exists in other table and admin member at the same time. Currently i fetch them individually.
CREATE TABLE public.room_members (
id bigint NOT NULL,
member_id bigint,
room_id bigint,
group_id bigint
);
CREATE TABLE public.rooms (
id bigint NOT NULL,
member_id bigint,
group_id bigint,
name varchar(128)
);
CREATE TABLE public.members (
id bigint NOT NULL,
group_id bigint,
username varchar(128),
is_admin bool default false
);
CREATE TABLE public.groups (
id bigint NOT NULL,
name varchar(128)
);
-- My Group created
INSERT INTO "groups" (id, name) VALUES (1, 'My Group');
-- Create users for this group. We have 4 users/members
INSERT INTO "members" (id, group_id, username, is_admin) VALUES (1, 1, 'Pratha', true);
INSERT INTO "members" (id, group_id, username) VALUES (2, 1, 'John');
INSERT INTO "members" (id, group_id, username) VALUES (3, 1, 'Mike');
INSERT INTO "members" (id, group_id, username) VALUES (4, 1, 'April');
-- April creates a room and he is owner of this room
INSERT INTO "rooms" (id, group_id, member_id, name) VALUES (1, 1, 4, 'My Room'); -- 4 is April
-- April also adds Mike to the room members. But she does not add herself. As she is owner.
INSERT INTO "room_members" (id, group_id, room_id, member_id) VALUES (1, 1, 1, 3); -- 3 is Mike
What I want is:
room_members list of 'My Room' (Which is only Mike at the moment)
My Room's owner in case of he didn't add himself to room_members table. Because he is the owner of that room (Which is April)
Plus, admin member (Which is Pratha)
And this should be unique. For example, if user add himself to room_members and also owner then it should fetch member one time only.
What I tried so far?
select * from members
left outer join room_members cm on cm.member_id = members.id
left outer join rooms c on c.id = cm.room_id
where c.name = 'My Room' or members.id = 1
I couldn't use group by here either. Also i don't need the all fields. Just room_members table fields only.
See here: https://rextester.com/XWDS42043
Expected output for room_members:
+-------------+------------+------------+
| member_id | group_id | username |
+-------------+------------+------------+
| 1 | 1 | Pratha |
+-------------+------------+------------+
| 3 | 1 | Mike |
+-------------+------------+------------+
| 4 | 1 | April |
+-------------+------------+------------+
Pratha: Because he is ADMIN
Mike: Because he is member of My Room. MEMBER
April: Because she created that room. OWNER
room_members can be many. I just added only Mike but it can have multiple members including admins and owners.
You can address this with UNION:
-- list the admin(s) of the room group
select m.id, m.group_id, m.username
from rooms r
inner join members m on m.group_id = r.group_id and m.is_admin = true
where r.name = 'My Room'
union
-- list the members of the room
select m.id, m.group_id, m.username
from rooms r
inner join room_members rm on r.id = rm.room_id
inner join members m on rm.member_id = m.id
where r.name = 'My Room'
union
-- recover the room owner
select m.id, m.group_id, m.username
from rooms r
inner join members m on r.member_id = m.id
where r.name = 'My Room'
UNION eliminates duplicates accross queries, so if a user is both member and/or group admin and/or owner of the room, they will only appear once.
In your fiddle, this query returns:
id group_id username
1 4 1 April
2 3 1 Mike
3 1 1 Pratha

Self join many-to-many relationship

From sample data below, assuming Julie (1) has friends Adam, David, John (2, 3, 4).
Adam (2) has friends Julie, David, John (1, 3, 4).
ID Name
1 Julie
2 Adam
3 David
4 John
5 Sam
This make a self join and many-to-many relationship within ONE table.
In addition to the above problem, say Julie (1) added Sam (5) as friends, technically and practically speaking, Sam (5) is now friend of Julie (1) as well. This make things more complicated because the relationship bi-directional.
So I'm wondering:
How do I design the database?
How do I make a query that will return all friends of every users?
Thank you!
Example Data:
PEOPLE
PERS_ID PERS_NAME
1 Julie
2 Adam
3 David
4 John
5 Sam
FRIENDSHIPS
PERS_ID FRIEND_ID
1 2
1 3
1 4
2 3
2 4
Query:
select people.pers_id as person,
people.pers_name as person_name,
peoplef.pers_id as friend_id,
peoplef.pers_name as friend_name
from people
join friendships
on people.pers_id = friendships.pers_id
or people.pers_id = friendships.friend_id
join people peoplef
on (peoplef.pers_id = friendships.pers_id and
peoplef.pers_id <> people.pers_id)
or (peoplef.pers_id = friendships.friend_id and
peoplef.pers_id <> people.pers_id)
order by 2, 4
SQL Fiddle demo: http://sqlfiddle.com/#!2/97b41/6/0
This will work regardless of whether or not you record both directions on the friendships table.
Pretty much agree with the others. You need a link table. I'll give a bit more detail.. some examples of keys and indexes and the query you wanted (bi-directional).
CREATE TABLE dbo.tblUser
(
ID int identity(0,1),
name varchar(20)
CONSTRAINT PK_tblUser PRIMARY KEY (ID)
)
-- Many to many link table with FKs
CREATE TABLE dbo.tblFriend
(
ID1 int not null constraint FK_tblUser_ID1 foreign key references dbo.tblUser(ID),
ID2 int not null constraint FK_tblUser_ID2 foreign key references dbo.tblUser(ID)
CONSTRAINT PK_tblFriend PRIMARY KEY (ID1, ID2)
)
-- Add index (So you can get an index seek if using ID2)
CREATE INDEX IX_tblFriend_ID2 ON dbo.tblFriend (ID2)
-- Test data
INSERT INTO dbo.tblUser(name)
VALUES ('Julie'),('Adam'),('David'),('John'),('Sam');
Insert INTO dbo.tblFriend (ID1, ID2)
values(0, 1),(2, 0)
-- Get bi-directional friend to friend relationships
SELECT U1.Name as 'User1', U2.Name as 'User2' FROM dbo.tblFriend F
INNER JOIN dbo.tblUser U1 ON U1.ID = F.ID1
INNER JOIN dbo.tblUser U2 ON U2.ID = F.ID2
UNION
SELECT U2.Name as 'User1', U1.Name as 'User2' FROM dbo.tblFriend F
INNER JOIN dbo.tblUser U1 ON U1.ID = F.ID1
INNER JOIN dbo.tblUser U2 ON U2.ID = F.ID2
ORDER BY User1, User2
One approach could be that you create second table that stores the person and friend ids. In this scenario, consider the following tables.
CREATE TABLE User
(
id int auto_increment primary key,
name varchar(20)
);
CREATE TABLE Friend
(
user_id int ,
friend_id int
);
INSERT INTO User
(name)
VALUES
('Julie'),
('Adam'),
('David'),
('John'),
('Sam');
Insert INTO Friend
(user_id, friend_id)
values(1, 5),
(3, 1);
Now the Friend table will store the user_id and his/her friend_id. For getting the list of friends for a particular user, you can search the id matching in either of these two columns. Below are sample queries.
-- Get Friends of Julie
select 1 AS user_id, IF(user_id = 1, friend_id, user_id) AS friend_id
FROM Friend
WHERE user_id=1 OR friend_id=1;
-- Get Friends of David
select 3 AS user_id, IF(user_id = 3, friend_id, user_id) AS friend_id
FROM Friend
WHERE user_id=3 OR friend_id=3
I hope you get idea with this and can play around.
I tried whatever you written in your query:
declare #table table
(
id int,
name varchar(40)
)
insert into #table values
(1, 'Julie'),
(2, 'Adam'),
(3, 'David'),
(4, 'John'),
(5, 'Sam')
select
t1.name ,
t2.name as friend
from #table t1, #table t2 where t1.id <> t2.id
and t1.id in (1,2) and t2.id <> 5
order by t1.id

SQL problem - select accross multiple tables (user groups)

I have a db schema which looks something like this:
create table user (id int, name varchar(32));
create table group (id int, name varchar(32));
create table group_member (group_id int, user_id int, flag int);
I want to write a query that allows me to so the following:
Given a valid user id (UID), fetch the ids of all users that are in the same group as the specified user id (UID) AND have group_member.flag=3.
Rather than just have the SQL. I want to learn how to think like a Db programmer. As a coder, SQL is my weakest link (since I am far more comfortable with imperative languages than declarative ones) - but I want to change that.
Anyway here are the steps I have identified as necessary to break down the task. I would be grateful if some SQL guru can demonstrate the simple SQL statements - i.e. atomic SQL statements, one for each of the identified subtasks below, and then finally, how I can combine those statements to make the ONE statement that implements the required functionality.
Here goes (assume specified user_id [UID] = 1):
//Subtask #1.
Fetch list of all groups of which I am a member
Select group.id from user inner join group_member where user.id=group_member.user_id and user.id=1
//Subtask #2
Fetch a list of all members who are members of the groups I am a member of (i.e. groups in subtask #1)
Not sure about this ...
select user.id from user, group_member gm1, group_member gm2, ... [Stuck]
//Subtask #3
Get list of users that satisfy criteria group_member.flag=3
Select user.id from user inner join group_member where user.id=group_member.user_id and user.id=1 and group_member.flag=3
Once I have the SQL for subtask2, I'd then like to see how the complete SQL statement is built from these subtasks (you dont have to use the SQL in the subtask, it just a way of explaining the steps involved - also, my SQL may be incorrect/inefficient, if so, please feel free to correct it, and point out what was wrong with it).
Thanks
Query 1 - Select all groups I am a member of.
You don't need a join here unless you also want the groups' names. Just check the group_member table.
SELECT group_id
FROM group_member
WHERE user_id = 1
Result:
1
3
Query 2: Select all users in one of the same groups as me.
You can self-join the group_member table to find all the users that are in the same group as each other and then add a where clause to only find all those that are in the same group as yourself. Add DISTINCT to make sure you don't get people twice.
SELECT DISTINCT T2.user_id
FROM group_member AS T1
JOIN group_member AS T2
ON T1.group_id = T2.group_id
WHERE T1.user_id = 1
AND T2.user_id <> 1 -- Remove myself
Result:
2
3
5
Query 3: Users who have flag 3 in any group.
You just need to check the group_member table. Again, add DISTINCT if you only want to see each user once.
SELECT DISTINCT user_id
FROM group_member
WHERE group_member.flag=3
Result:
2
3
4
Final query: Users in the same group as me who have flag 3.
This is almost the same as query two, just add an extra WHERE condition.
SELECT DISTINCT T2.user_id
FROM group_member AS T1
JOIN group_member AS T2
ON T1.group_id = T2.group_id
WHERE T1.user_id = 1
AND T2.user_id <> 1 -- Remove myself
AND T2.flag = 3
Result:
2
3
Test data:
create table user (id int, name varchar(32));
create table `group` (id int, name varchar(32));
create table group_member (group_id int, user_id int, flag int);
insert into user (id, name) VALUES (1, 'user1'), (2, 'user2'), (3, 'user3'), (4, 'user4'), (5, 'user5');
insert into `group` (id, name) VALUES (1, 'group1'),(2, 'group2'), (3, 'group3');
insert into group_member (group_id, user_id, flag) VALUES (1, 1, 0), (1, 2, 3), (1, 3, 3), (2, 3, 3), (2, 4, 3), (2, 5, 0), (3, 1, 0), (3, 5, 0);
This should find all people in the same group as a certain user, with the specified flag.
SELECT DISTINCT g2.user_id
FROM group_member AS g INNER JOIN group_member AS g2
ON g.group_id = g2.group_id
WHERE g.user_id = <the userid you want to find>
AND g2.flag = 3
To approach the problem, I did the following:
We need to compare two group_members, as we want to know which ones are in the same group, so we'll need to join group_member with itself. (On group_id, as we want the ones in the same group.)
I want to make sure that one of them is the user I want to compare it to, and then the other one must have the correct flag.
Once this is done, I simply need to pull out the user_id from the one I compare to my original user. Since I figure a user might be in two groups another user is in, it might also be wise to add a DISTINCT to ensure that we only get each user_id out of it once.
The beauty of SQL is that you can join these three selects together with one efficient join. In this case I used the WHERE version of a join because I find it easier to understand. But you might also look at the syntax for LEFT JOIN and INNER JOIN because they give you a lot of expressivity.
SELECT * FROM user, group_member, group
WHERE user.id = group_member.user_id
&& group_member.group_id = group.id
&& group_member.flag = 3