views:

166

answers:

2

Hi

I have this problem. Given a users table that consists of users' username in a social network and friends table that contain a user's name and a user's friendname like below...

username friendname

John        Thomas
Chris       James

... I'm trying to write an SQL statement that will if a user is in my network. In other words is that user a friend or friend of friends?

I've been dancing around this problem and could only come up with this query:

SELECT f2.username, f2.friendname 
FROM friends f2 
WHERE f2.username IN (
      SELECT f1.friendname 
      FROM friends f1 
      WHERE f1.username = 'Thomas') 
AND f2.friendname <> 'user1' 
AND f2.friendname = 'user2';    

It basically check if a user if is a friend of my friend i.e. just return null if false.

Trying to figure out how I can expand to go through all my network of friend. I mean not just friend of my friend.

+4  A: 
SELECT  *
FROM    (
        SELECT  username
        FROM    friends
        START WITH
                username = 'myname'
        CONNECT BY
                friendname = PRIOR username
                AND level <= 3
        )
WHERE   username = 'friendname'
        AND rownum = 1

Update the level as necessary: you may search for the third layer friends etc.

If the friendship relationship is symmetric, you should make the following query:

WITH    q AS
        (
        SELECT  username, friendname
        FROM    friends
        UNION ALL
        SELECT  friendname, username
        FROM    friends
        ),
        f AS
        (
        SELECT  friendname, level
        FROM    q
        START WITH
                username = 'Thomas'
        CONNECT BY NOCYCLE
                username = PRIOR friendname
        )
SELECT  *
FROM    f
WHERE   friendname = 'Jo'
        AND rownum = 1

This query can be made much faster if you denormalize your table: store two records per friendship, like this:

CREATE TABLE dual_friends (orestes NOT NULL, pylades NOT NULL, CONSTRAINT pk_dualfriends_op PRIMARY KEY (orestes, pylades)) ORGANIZATION INDEX
AS
SELECT  username, friendname
FROM    friends
UNION ALL
SELECT  friendname, username
        FROM    friends

Then you can just replace the CTE above with the dual_friends:

WITH    f AS
        (
        SELECT  pylades, level
        FROM    dual_friends
        START WITH
                orestes  = 'Thomas'
        CONNECT BY NOCYCLE
                orestes = PRIOR pylades
                AND level <= 3
        )
SELECT  *
FROM    f
WHERE   pylades = 'Jo'
        AND rownum = 1

, which will use the index and be much more efficient, especially if you limit the level to some reasonable value.

Quassnoi
However oracle has Hierarchial queries using connect by so will do the level searching for you
Mark
That sql does seem to work for me. ok, imagine given this tableusername friendnameThomas AliceAlice BobBob JoRunning that sql with myname = Thomas and friendname='Jo' give nothing indicating Jo is not in Thomas network, which is incorrect
core_pro
@user: Is your friendship relationship symmetric? That is, if `Alice` is `Thomas`'s friend, is `Thomas` the `Alice`'s friend as well? I believe he is, but it is better to specify it.
Quassnoi
yes it is. i'm not if that changes much
core_pro
@Quassnoi, do u want to try your query on this sample tableusername friendname Thomas AliceAlice BobBob Joit seems to be returning nothing, not sure if it is me
core_pro
@Quassnoi,I actually managed to get it working using some of your previous suggestions and tweaking plus googling. But this new one beats it though, looks like i still ve a lot to learn. Thanks for everything
core_pro
+1  A: 

You can use connect by

klausbyskov