How create SQL query that validates friends of friends - sql

I have two tables.
USER user_id password
FRIEND_LIST user_id friend_id
If user 1 is friend of user 2 then in friend_list there will be 2 records:
1 2
2 1
Thats how I'm controlling friend list.
My question is how can I create an efficient query that validates if a user is a friend of a friend.
For example user 1 has in his friend list user 2. and user 3 has in his friend list user 2. So user 2 is a common friend of both 1 and 3.
Here is how friend_list table looks like:
1 2
2 1
3 2
2 3
No I want to know if user 1 has a friend that has as friend user 3.
The pseudocode is as follows:
validate(){
valid = false
list = get all friends from user 1 and store them in 'list'.
for each friend in list {
list2 = get all friends from friend
for each friend2 in list2 {
if friend2.user_id = 3 }
valid = true
break; //stop here because we have found that 3 is a friend of a friend of 1
}
}
}
return valid
}
This is how it would look like in a programming language. Now I want to validate the same but just with an SQL query.
I tried this but I dont know if this is a good way to validate that.
select *
from friend_list fl1
inner join friend_list fl2 on fl1.user_id = fl2.user_id
inner join friend_list fl3 on fl2.friend_id = fl3.user_id
where fl1.user_id = 1 and fl3.friend_id = 3
Thanks in advance.
Thank you very much for your support. This is the first time I use this forum
and helped me a lot.
I used the EXISTS code you posted like this.
SELECT EXISTS (
SELECT
*
FROM
friend_list AS users
INNER JOIN
friend_list AS friends
ON users.friend_id = friends.user_id
WHERE
users.user_id = 1
AND friends.friend_id = 3
) AS result

As you're looking to find out if there are Any instances of "a and b have a friend in common" you're better of using the EXISTS keyword.
The benefit of this over using COUNT or DISTINT is that the optimiser knows a couple of things:
1. The actual data doesn't matter
2. It can stop searching after the first hit
For example...
IF EXISTS (
SELECT
*
FROM
friend_list AS [user]
INNER JOIN
friend_list AS [friend]
ON [user].friend_id = [friend].user_id
WHERE
[user].user_id = #user_id
AND [friend].friend_id = #friend_of_friend_id
)
BEGIN
RETURN 1
-- Or whatever code you want to execute
END
ELSE
BEGIN
RETURN 0
-- Or whatever code you want to execute
END
Although this doesn't have "TOP 1" and uses "*", it neither actually returns multiple fields or rows. It literally just searches for the existance of a match and stops when it finds one.

The query you already wrote is along the right lines, I think you need one less join to the friend-list table:
select distinct fl1.user_id, fl2.friend_id
from friend_list fl1
inner join friend_list fl2 on fl1.friend_id = fl2.user_id
where fl1.user_id = 1 and fl2.friend_id = 3
So '1' (fl1.user_id) is friends with 'x' (fl1.friend_id and fl2.user_id) who is friends with '3' (fl2.friend_id).
Because you have two complementary entries in friend-list for each pair of friends, the query is nice and simple. It would be a bit tougher if each pair of friends only got one row in friend-list ....
(edit: realised too many joins were happening...)
(edit: added a distinct into the select after comment conversation)

Related

Sub-query works but would a join or other alternative be better?

I am trying to select rows from one table where the id referenced in those rows matches the unique id from another table that relates to it like so:
SELECT *
FROM booklet_tickets
WHERE bookletId = (SELECT id
FROM booklets
WHERE bookletNum = 2000
AND seasonId = 9
AND bookletTypeId = 3)
With the bookletNum/seasonId/bookletTypeId being filled in by a user form and inserted into the query.
This works and returns what I want but seems messy. Is a join better to use in this type of scenario?
If there is even a possibility for your subquery to return multiple value you should use in instead:
SELECT *
FROM booklet_tickets
WHERE bookletId in (SELECT id
FROM booklets
WHERE bookletNum = 2000
AND seasonId = 9
AND bookletTypeId = 3)
But I would prefer exists over in :
SELECT *
FROM booklet_tickets bt
WHERE EXISTS (SELECT 1
FROM booklets b
WHERE bookletNum = 2000
AND seasonId = 9
AND bookletTypeId = 3
AND b.id = bt.bookletId)
It is not possible to give a "Yes it's better" or "no it's not" answer for this type of scenario.
My personal rule of thumb if number of rows in a table is less than 1 million, I do not care optimising "SELECT WHERE IN" types of queries as SQL Server Query Optimizer is smart enough to pick an appropriate plan for the query.
In reality however you often need more values from a joined table in the final resultset so a JOIN with a filter WHERE clause might make more sense, such as:
SELECT BT.*, B.SeasonId
FROM booklet_tickes BT
INNER JOIN booklets B ON BT.bookletId = B.id
WHERE B.bookletNum = 2000
AND B.seasonId = 9
AND B.bookletTypeId = 3
To me it comes down to a question of style rather than anything else, write your code so that it'll be easier for you to understand it months later. So pick a certain style and then stick to it :)
The question however is old as the time itself :)
SQL JOIN vs IN performance?

inner join combined with a count

In our databases we have crewmembers and non-crewmembers. Each crewmeber and non-crewmember can have friends.
for example a crewmember is befriended with 4 crewmembers and 2 non-crewmembers.
What I would like to know is the avarage number of non-crewmembers our crewmembers with AirlineCode=LH have.
We have two tables involved:
AppUser: where the user data are stored.
Here we have the Cells
AppUser.IsCrew (0 = non-crewmembers and 1 = crewmember)
AppUser.AirlineCode (sting of some sort)
Follower: Friends list stored
Follower.FromUserId
Follower.ToUserId
There we store that AppUser.Id = 1 has 5 friends and follows Follower.ToUserId 3,4,5 and so on
Problem here is and I don't even know how to start:
Query which checks
a) All AppUser.IsCrew=1 and has Airline.Code=LH
b) Check in Follower table how many users they follow but only the ToUserId who are in AppUser.IsCrew have = 0
And somehow return a average number
Thanks a lot for the help
First, you'll need to count how many non-crewmembers friends each crewmember person has. Make sure you also count all crewmembers even if they don't have any friend.
Then it's a matter of averaging those counts. The query should look like:
with x as (
select
c.id, count(n.id) as nc_total
from AppUser c
left join Follower f on f.FromUserId = c.id
left join AppUser n on f.ToUserId = n.id and n.isCrew = 0
where c.isCrew = 1
and c.AirLineCode = 'LH'
group by c.id
)
select avg(nc_total) from x

SQL query to find whoever has a skill what other skill do they have?

enter image description here
I have two tables :
UserInfo
Skill
and the join table between them called UserSkill as you can see at the
right part of the diagram.
I want to know whoever knows or is skillful in Java, what else he is skillful at. I mean for example I know java, Go, PHP, python and user number 2 knows java and python and CSS. So the answer to the question: whoever knows java what else he knows would be GO, PHP, Python and CSS.
It's like recommendation systems for example whoever but this product what else do they bought? Like what we have in amazon ..
What would be the best query for this ?
Thank you
More information:
UserInfo
U-id U-name
1 A
2 B
3 C
SkillInfo
S-id S-Name
1 Java
2 GO
3 PHP
4 Python
5 CSS
UserSkill:
U-id S-id
1 1
1 2
1 3
1 4
2 1
2 4
2 5
In SQL Server 2017 and Azure SQL DB you can use the new graph database capabilities and the new MATCH clause to answer queries like this, eg
SELECT FORMATMESSAGE ( 'User %s has skill %s and other skill %s.', [user1].[U-name], mainSkill.[S-name], otherSkill.[S-name] ) result
FROM
dbo.users user1,
dbo.hasSkill hasSkill1, dbo.skills mainSkill, dbo.hasSkill hasSkill2, dbo.skills otherSkill
WHERE mainSkill.[S-name] = 'CSS'
AND otherSkill.[S-name] != mainSkill.[S-name]
AND MATCH ( mainSkill<-(hasSkill1)-user1-(hasSkill2)->otherSkill);
My results:
Obviously you can answer the same queries with a relational approach, it's just a different way of doing things. Full script available here.
To make this more dynamic, replace the hard coded 'java' with a variable that you can pass to filter by any skill type, possibly make a stored procedure so you can pass the variable,
Edited column names as I didn't look at the image you provided:
--Outer query selects all skills of users which are not java and user has skill of java,
--the inner query selects all user ids where the user has a skill of java
SELECT sk.[SkillName], ui.[UserName], ui.[UserId]
FROM [dbo].[Skill] AS sk
INNER JOIN [dbo].[UserSkill] AS us
ON us.[SkillId] = sk.[SkillId]
INNER JOIN [dbo].[UserInfo] AS ui
ON ui.[UserId] = us.[UserId]
WHERE sk.[Skill] <> 'java' AND ui.[UserId] IN (
SELECT [UserId]
FROM [dbo].[UserInfo] ui
INNER JOIN [dbo].[UserSkill] us
ON us.[UserId] = ui.[UserId]
INNER JOIN [dbo].[Skill] sk
ON sk.[SkillId] = us.[SkillId]
WHERE sk.[SkillName] = 'java')
This is what I have found
--Formatted query
select
o.UserName, k.SkillName
from
UserSkill S
inner join
UserSkill SS on s.UserID = ss.UserID
and s.SkillID = 1
and ss.SkillID <> 1
inner join
Skill k on k.SkillID = ss.SkillID
inner join
UsersINFO O on o.UserID = ss.UserID

A task about friend of friend of .... on Oracle DB

There are two tables:
Table of peoples (m_id, m_name);
Table of links (m_id, f_id), where both fields link to m_id from first table
I need an Oracle database query that prints the word "Possible" if everyone is linked to everyone by not more than:
through 3 friends
through N friends
otherwise prints "Impossible"
Help me with this task if it's possible, or at least show me where to look for the answer, I mean what I have to read before, and what's necessary for solving this task.
I am not sure i got you question correct but i guess you require something like this.
select p.m_id,count(l.f_id),'Possible' col
from people p,
links l
where p.m_id = l.m_id
group by p.m_id
having count(l.f_id) >= 3
union
select p.m_id,count(l.f_id),'Impossible' col
from people p,
links l
where p.m_id = l.m_id
group by p.m_id
having count(l.f_id) < 3

using a select query from 2 views

there is a sql query problem in which i should find out which users have passed all of the tests.
here are the tables:
ex: nameoftable(nameofcolumns)
user(userid,password)
test(test#,creditpertrue,negperfalse,total,neededcredit)
{creditpertrue=points given per a right answer and negperfalse=points taken per a wrong answer
and total=is the total points of a test and neededcredit is the points to pass the test}
question(test#,q#,truechoice#)
user_test(userid,test#)
{this table shows that which user have taken which test(a user can only take a test once)}
user_test_question(uesrid,test#,q#,userchoice#)
{this table shows the choice of every user in every test and question# which may be wrong or right}
now the question is this:
find out the userid of users that have passed all of the tests.
a solution that came to my mind was this:
create 2 views like this:
view1:userid,test#,numberofrightchoices
and
view2:userid,test#,numberofwrongchoices
and then use a select on these 2views and use numberofrightchoices*creditpertrue-numberofwrongchoices*negperfalse>=neededcredit
is it possible?
I think that the query that you need to get a list of users who have passed all the tests would be this:
select userid,
from user_test
where userid not in (
Select
userid
from
(select t0.test#,t2.uesrid, neededcredit,
Sum(case when t1.truechoice# = userchoice# then t0.creditpertrue else -negperfalse end) as User_Test_Points
from test t0 left join
question t1 on (t0.test# = t1.test#) left join
user_test_question t2 on (t1.q# = t2.q#)
where t2.uesrid is not null
Group by t0.test#,t2.uesrid,neededcredit )
Where Test_Results.User_Test_Points < Test_Results.neededcredit )