Sql query to find users with same skills - sql

I have three tables
UserInfo (U-id,U-name)
Skill(S-id,S-Name)
and a bridge table between them (cause they have many to many relationship)
UserSkill(U-id,S-id)
I want to write a query to find users with same skills
for example this is a sample data in UserSkill table
U-id S-id
1 1
1 2
1 7
2 1
2 6
so the result would be like this
UserName1 UserName2 SkillName
A B Java
ad this is my query
{select ui.UserName,ui2.UserName,SkillName
from
UserSkill us1 inner join UserSkill us2
on us1.SkillID = us2.SkillID and us1.UserID <> us2.UserID
inner join UsersINFO UI
on ui.UserID = us1.UserID
inner join UsersINFO ui2
on ui2.UserID = us2.UserID
inner join Skill s
on s.SkillID = us2.SkillID}
I want to know whether anyone knows a better way to write the query

You just need to add Where ui.UserName<ui2.UserName at the end
select ui.UserName,ui2.UserName,SkillName
from
UserSkill us1 inner join UserSkill us2
on us1.SkillID = us2.SkillID and us1.UserID <> us2.UserID
inner join UsersINFO UI
on ui.UserID = us1.UserID
inner join UsersINFO ui2
on ui2.UserID = us2.UserID
inner join Skill s
on s.SkillID = us2.SkillID
Where ui.UserName<ui2.UserName
Output
UserName UserName SkillName
A B Java
Live Demo
http://sqlfiddle.com/#!18/64540/1

The result from your query is just messy. It reports A, B, java and B, A, java.
declare #J table (uid int, sid int, primary key (uid, sid));
insert into #J values
(1, 1)
, (1, 2)
, (1, 7)
, (2, 1)
, (2, 6)
, (3, 1)
, (3, 2);
declare #N table (id int primary key, name varchar(10));
insert into #N values
(1, 'bob')
, (2, 'ted')
, (3, 'mac');
select j1.sid, n1.name, n2.name
from #J j1
join #J j2
on j2.sid = j1.sid
and j2.uid <> j1.uid
join #N n1
on n1.id = j1.uid
join #N n2
on n2.id = j2.uid
order by j1.sid, j1.uid, j2.uid;
sid name name
----------- ---------- ----------
1 bob ted
1 bob mac
1 ted bob
1 ted mac
1 mac bob
1 mac ted
2 bob mac
2 mac bob
Even if you replace <> with > it is still messy when you have more than 2 with the same skill.
select j1.sid, n1.name, n2.name
from #J j1
join #J j2
on j2.sid = j1.sid
and j2.uid > j1.uid
join #N n1
on n1.id = j1.uid
join #N n2
on n2.id = j2.uid
order by j1.sid, j1.uid, j2.uid;
sid name name
----------- ---------- ----------
1 bob ted
1 bob mac
1 ted mac
2 bob mac
I suggest you NOT report as pairs
select *
from ( select j1.sid, n1.name
, count(*) over (partition by j1.sid) as cnt
from #J j1
join #N n1
on n1.id = j1.uid
) t
where t.cnt > 1
order by t.sid, t.name;
sid name cnt
----------- ---------- -----------
1 bob 3
1 mac 3
1 ted 3
2 bob 2
2 mac 2

I suggest not to have in pairs like above below is a sol :
Here in below soln.. I am joining data and just grouping users by skill id and skillname.
SELECT
o.skillname,
LISTAGG(o.username, ',') WITHIN GROUP(
ORDER BY
o.username ASC
) user_common_skill
FROM
(
SELECT
t.username,
t.userid,
t.skillid,
s.skillname
FROM
(
SELECT
ui.username,
ui.userid,
us.skillid
FROM
userskill us
INNER JOIN usersinfo ui ON us.userid = ui.userid
) t
LEFT JOIN skill s ON t.skillid = s.skillid
) o
GROUP BY
o.skillid,
o.skillname
Results of above query

The following returns all pairs of users that have a skill in common, along with that skill:
select ui.name, us2.name, s.s_name
from userskill us join
userskill us2
on us.sid = us2.sid join
skills s
on s.sid = us.sid join
userinfo ui
on ui.uid = us.uid join
userinfo ui2
on ui2.uid = us2.uid
where us.uid <> us2.uid;

Related

Not able to generate SQL Join query for Postgres

I have a table structure like this and I am using Postgres
ContactPhoneRelation
id
ContactId
PhoneId
1
123
999
2
123
998
I have another table GroupTable
id
groupId
ContactId
PhoneId
1
1
123
999
2
2
123
999
3
2
123
998
I am trying to fetch the data from ContactPhoneRelation which does not exist in groupId 1 and ContactId is 123, So I want to query such that where groupId is 1 and ContactId is 123 and ContactId phoneId both does not exists in groupId 1
So in return, it should give me this result
id
contactId
PhoneId
2
123
998
If I query for groupId 2, It should give me 0 rows in return.
I tried this query but it gave me the opposite data.
select * from ContactPhoneRelation cp
left join GroupTable gt on gt.ContactId = cp.ContactId
where cp.contactId = '123' and gt.groupId = 1
In all honesty, I don't understand what you are trying to achive. But maybe this is because of language issues.
For better understanding I added an example on db<>fiddle: Link
Edit (29.09.2021; 16:49):
SELECT
c.*
FROM
(
SELECT
b.groupID
,a.ContactID
,a.PhoneID
FROM
(SELECT DISTINCT
ContactId
,PhoneID
FROM
ContactPhoneRelation
) a
FULL JOIN
(SELECT DISTINCT
groupID
FROM
GroupTable
) b ON (1=1)
ORDER BY groupID, ContactID, phoneID
) c
LEFT OUTER JOIN GroupTable d ON (
c.groupID = d.groupID
AND c.ContactID = d.ContactID
AND c.PhoneID = d.PhoneID
)
WHERE
d.groupID IS NULL

Inner join on the same row twice

I have the following tables:
games:
id | tournament | player1 | player 2
1 | 1 | 1 | 2
2 | 1 | 3 | 4
players
id | name
1 | Johnson
2 | Smith
tournaments
id | name
1 | Tournament 1
Now I want to extract all information in the table 'games'.
I've used:
SELECT t.name, g.player1, g.player2
FROM tournaments AS t
INNER JOIN games AS g ON g.tournament = t.id
This works for extracting the information for the tournament - but I'm looking for the same for the players as well. The only solution I could find was after my loop to do another SQL to extract the information from the player:
SELECT * FROM players where id = player1variable
Is this the best solution? Is it possible to include this information in the first SQL?
You can do this by JOINing to the players table twice, once for each player:
SELECT t.name, p1.*, p2.*
FROM tournaments AS t
INNER JOIN games AS g ON g.tournament = t.id
INNER JOIN players AS p1 ON p1.id = g.player1
INNER JOIN players AS p2 ON p2.id = g.player2
Output:
name id name id name
Tournament 1 1 Johnson 2 Smith
Demo on dbfiddle
Note that this will give you columns with the same name, which may cause issues in your application. You can work around this by using column aliases:
SELECT t.name, p1.name AS player1, p2.name AS player2
FROM tournaments AS t
INNER JOIN games AS g ON g.tournament = t.id
INNER JOIN players AS p1 ON p1.id = g.player1
INNER JOIN players AS p2 ON p2.id = g.player2
Output:
name player1 player2
Tournament 1 Johnson Smith
Demo on dbfiddle
Use SubQuery.
SELECT t.name
, g.player1
, (select m.name from players m where m.id = g.player1) as player1_name
, g.player2
, (select m.name from players m where m.id = g.player2) as player2_name
FROM tournaments AS t
INNER JOIN games AS g ON g.tournament = t.id

Inner join - more than one table

I'm trying to write a code that will join together information from 3 tables and compare it with a "big table" and according to the comparison update a field in a test table. Look at the example:
Big Table:
Employee_Id status
2322 5
222 3
545 6
4532 2
Table 1:
S_Id status
2322 7
Table 2:
S_Id status
222 3
Table 3:
S_Id status
545 6
4532 3
Test Table:
TE_Id IsNotGood
2322 1
4532 1
222 0
545 0
The id "2322" got 1 because the status in table 1 is not like the status in the big table and same for 4532.
I started writing this:
update Test Table
set isNotGood = 0
with ids as (
select distinct Employee_Id from (
select Employee_Id,Status from BigTable as B
) as T
inner join table1 as W on W.S_ID = T.Employee_Id
inner join table2 as K on K.S_ID = T.Employee_Id
inner join table3 as R on R.S_ID = T.Employee_Id
)
I would be happy for your tips to finish this query.
Thank you very much!
You won't be able to use an inner join unless the employee_id is in all the tables.
UPDATE Test
SET isNotGood = 1
WHERE te_id in (select employee_id
from [Big Table] bt
left join table1 as W on W.s_id = bt.employee_Id
left join table2 as K on K.s_id = bt.employee_Id
left join table3 as R on R.s_id = bt.employee_Id
where (bt.status <> W.status and W.status is not null) or (bt.status <> K.status and k.status is not null) or (bt.status <> R.status and R.status is not null);

Complex sql server query

I need some help with a SQL query I'm working on. Here is a simplified version of the data I'm working with. I have 3 tables:
Contacts:
- ContactID
- ContactName
Submissions:
- SubmissionID
- ContactID
- SubmissionTypeID
SubmissionTypes:
- SubmissionTypeID
- SubmissionType
I need to return all of the Contacts (joined to Submissions on ContactID) where there are SubmissionTypeIDs that match up with a list of SubmissionTypeIDs. The tricky part is that I only want results where a Contact has a Submission record with a SubmissionTypeID that matches each of the values in the list. So, for instance, if I had this data:
Contacts
----------------
1 | Jim Johnson
2 | Sally Anderson
SubmissionTypes
----------------------
1 | Contact Form
2 | Request Form
3 | Generic Form
Submissions
----------------------
1 | 1 | 1
2 | 1 | 2
3 | 2 | 1
If my SubmissionTypeID values are 1 and 2, I'd want to get the following results:
Jim Johnson | Contact Form
Jim Johnson | Request Form
I wouldn't want to see Sally Anderson because she doesn't have a record in Submissions for both values.
I'm guessing there are a few ways to do this. I'm excited to see your ideas.
Thank you!
Here's a convoluted way using double negation.
declare #list table (SubmissionTypeID int not null primary key);
insert into #list values (1), (2); -- values to search for.
with c as (
select
c.ContactID,
c.ContactName
from
Contacts c
where
not exists (
select
'x'
from
#list l
where
not exists (
select
'x'
from
Submissions s
where
s.ContactID = c.ContactID and
s.SubmissionTypeID = l.SubmissionTypeID
)
)
)
select
c.ContactName,
t.SubmissionType
from
c
inner join
Submissions s
on c.ContactId = s.ContactId
inner join
SubmissionTypes t
on s.SubmissionTypeID = t.SubmissionTypeID
inner join
#list l
on t.SubmissionTypeID = l.SubmissionTypeID;
Example SQLFiddle
One way is with INTERSECT:
select c.contactname, t.submissiontype
from contacts c
join submissions s
on c.contactid = s.contactid
join submissiontypes t
on s.submissiontypeid = t.submissiontypeid
join (select c.contactid
from contacts c
join submissions s
on c.contactid = s.contactid
where s.submissiontypeid = 1
intersect
select c.contactid
from contacts c
join submissions s
on c.contactid = s.contactid
where s.submissiontypeid = 2) v
on c.contactid = v.contactid
where s.submissiontypeid in (1, 2)
Fiddle: http://sqlfiddle.com/#!6/9ee4e/2/0
You can also COUNT where equal to 2 (you have 2 values you're checking for):
select c.contactname, t.submissiontype
from contacts c
join submissions s
on c.contactid = s.contactid
join submissiontypes t
on s.submissiontypeid = t.submissiontypeid
join (select c.contactid
from contacts c
join submissions s
on c.contactid = s.contactid
where s.submissiontypeid in (1, 2)
group by c.contactid
having count(distinct s.submissiontypeid) = 2) v
on c.contactid = v.contactid
where s.submissiontypeid in (1, 2)
Fiddle: http://sqlfiddle.com/#!6/9ee4e/1/0
Try this.. It works fine to me
DECLARE #list TABLE (SubmissionTypeID int not null primary key);
INSERT INTO #list VALUES(1); -- values to search for.
SELECT C.ContactName, ST.SubmissionTypeName
FROM
(
SELECT *, ROW_NUMBER() OVER(PARTITION BY S.ContactID ORDER BY S.ContactID) AS RCount
FROM
Submission S
WHERE EXISTS
(SELECT 1 FROM #list l WHERE l.SubmissionTypeID = S.SubmissionTypeID)) AS Result
INNER JOIN Submission S1 ON S1.ContactID = Result.ContactID
INNER JOIN Contacts C ON C.ContactID = Result.ContactID
INNER JOIN SubmissionTypes ST ON ST.SubmissionTypeID = S1.SubmissionTypeID
WHERE RCOunt = (SELECT COUNT(DISTINCT SubmissionTypeID) FROM #list)
AND EXISTS
(SELECT 1 FROM #list l WHERE l.SubmissionTypeID = ST.SubmissionTypeID)
;

Using 'AND' in a many-to-many relationship

I have a Users table and a Groups table. Users can be in multiple groups via a 'UserInGroup' table and Groups can have a 'GroupTypeId'.
[User]
--------------
Id | Name
1 | Bob
2 | James
[UserInGroup]
-----------------
UserId | GroupId
1 1
1 2
[Group]
Id | Name | TypeId
------------------------
1 | Directors | 1
2 | IT | 1
3 | London | 2
I want to create a query to return for example users that are in both 'Directors' AND 'London' (rather than 'Directors' OR 'London'). However, I only want to AND groups of a different 'Type', I want to OR groups of the same type. I could do with having a separate table per group type but I can't as they are created dynamically.
Ideally I want to be able to query users who are in 'Directors' OR 'IT' AND 'London'.
What is the most efficient way of doing this?
This problem is commonly known as Relational Division.
SELECT a.Name
FROM [user] a
INNER JOIN UserInGroup b
ON a.ID = b.UserID
INNER JOIN [Group] c
ON b.groupID = c.TypeId
WHERE c.Name IN ('Directors','London')
GROUP BY a.Name
HAVING COUNT(*) = 2
SQLFiddle Demo
SQL of Relational Division
But if a UNIQUE constraint was not enforce on GROUP for every USER, DISTINCT keywords is needed to filter out unique groups:
SELECT a.Name
FROM [user] a
INNER JOIN UserInGroup b
ON a.ID = b.UserID
INNER JOIN [Group] c
ON b.groupID = c.TypeId
WHERE c.Name IN ('Directors','London')
GROUP BY a.Name
HAVING COUNT(DISTINCT c.Name) = 2
OUTPUT from both queries
╔══════╗
║ NAME ║
╠══════╣
║ Bob ║
╚══════╝
I arrived at the following solution (with help from J W and this article):
SELECT
u.Name UserName
FROM [User] u
INNER JOIN [UserInGroup] uig
ON uig.UserId = u.Id
INNER JOIN [Group] g
ON g.Id = uig.GroupId
WHERE
g.Id IN (1,2,3) -- these are the passed in groupids
GROUP BY
u.Name
having count(distinct g.TypeId)
= (select count(distinct g1.TypeId)
from [group] g1 where g1.Id IN (1,2,3))
This allows me to group the relational division by a discriminator field. An alternative would be this:
SELECT a.Name
FROM [User] a
INNER JOIN
(
SELECT b.UserID
FROM UserInGroup b
INNER JOIN [Group] c
ON b.groupID = c.Id
WHERE c.Name IN ('Directors','IT')
GROUP BY b.UserID
HAVING COUNT(DISTINCT c.Name) >= 1
) b ON a.ID = b.UserID
INNER JOIN
(
SELECT DISTINCT b.UserID
FROM UserInGroup b
INNER JOIN [Group] c
ON b.groupID = c.Id
WHERE c.Name = 'London'
) c ON a.ID = c.UserID
With an extra join for each GroupTypeId. Execution plans look similar, so I went with the first option.