2015-02-08 16 views
3

我有兩張桌子。 用戶表:SQL查詢,我怎樣才能找到一對有最常見朋友的朋友?

USERS(ID,NAME) 

朋友關係:

FRIEND(ID1,ID2) 

,我想找到對擁有最共同的朋友,而這兩個用戶不是朋友的用戶。

最後,我想打印這兩個用戶的名字對。 一個例子是這樣的:

用戶表:

(1,Jimmy) 
(2,Sam) 
(3,Alices) 
(4,Tom) 

朋友表:

(1,2) 
(1,3) 
(4,2) 
(4,3) 

隨着用戶1和4具有相互2,3朋友。用戶2和3具有共同的朋友1,4。兩個2對朋友有共同的朋友2.所以我們想打印自己的名字作爲結果的數量:

Jimmy,Tom 
Sam,Alices 

我怎樣才能做到這一點在一個查詢?

+0

_most共享的朋友,雖然他們不是朋友_我不明白這一點。誰不是朋友?你能澄清這一點,也許增加一些樣本輸入/輸出數據? – jpw 2015-02-08 20:09:38

+0

@jpw我認爲OP是在擁有最多共同朋友的人之後,然而他們本身並不是彼此的朋友。 – Mez 2015-02-08 20:14:43

+0

看看這個鏈接:http://www.sqlteam.com/forums/topic.asp?TOPIC_ID=72097 – Mez 2015-02-08 20:16:31

回答

1

我正在使用SQL Server來測試它,因爲我手邊只有SQL Server,但它應該直接轉換爲Oracle語法。

我已經使用SQL Fiddle將它轉換爲Oracle,但我以前從未見過Oracle。查看底部的最終查詢。

的樣本數據

DECLARE @USERS TABLE (ID int, NAME nvarchar(255)); 

DECLARE @FRIEND TABLE (ID1 int, ID2 int); 

INSERT INTO @USERS (ID, NAME) VALUES (1, 'Jimmy'); 
INSERT INTO @USERS (ID, NAME) VALUES (2, 'Sam'); 
INSERT INTO @USERS (ID, NAME) VALUES (3, 'Alice'); 
INSERT INTO @USERS (ID, NAME) VALUES (4, 'Tom'); 

INSERT INTO @FRIEND (ID1, ID2) VALUES (1,2); 
INSERT INTO @FRIEND (ID1, ID2) VALUES (1,3); 
INSERT INTO @FRIEND (ID1, ID2) VALUES (4,2); 
INSERT INTO @FRIEND (ID1, ID2) VALUES (4,3); 

用戶的雙

我們需要對用戶的。這由CROSS JOIN完成。 CROSS JOIN會返回兩倍的行數,因爲我們需要(1,2) and (2,1),但我們只需要其中的一個,所以我們將添加用戶ID的過濾器。

WITH 
CTE_Pairs 
AS 
(
    SELECT 
     U1.ID AS ID1 
     ,U2.ID AS ID2 
    FROM 
     @USERS AS U1 
     CROSS JOIN @USERS AS U2 
    WHERE 
     U1.ID > U2.ID 
) 
SELECT * 
FROM CTE_Pairs; 

結果集:

ID1 ID2 
2  1 
3  1 
4  1 
3  2 
4  2 
4  3 

對,不是朋友

一旦我們擁有所有對我們應該刪除那些朋友已經對。表FRIEND可以列出一對爲(1,2)(2,1),所以我們應該檢查兩種可能性。我們將使用EXCEPT來「減去」這些行。

.... 
,CTE_PairsNonFriends 
AS 
(
    SELECT ID1, ID2 
    FROM CTE_Pairs 

    EXCEPT 

    SELECT ID1, ID2 
    FROM @FRIEND 

    EXCEPT 

    SELECT ID2, ID1 
    FROM @FRIEND 
) 
SELECT * 
FROM CTE_PairsNonFriends; 

結果集:

所選用戶

ID1 ID2 
3  2 
4  1 

朋友,我們有對的最終名單。對於每個用戶,我們需要獲得他的直接朋友列表。簡單的join就夠了。再次表friend可以有(1,2)(2,1),所以我們需要做兩次。我們首先爲用戶ID1執行此操作,然後分別爲用戶ID2執行此操作。

.... 
,CTE_FriendsOfUser1 
AS 
(
    SELECT 
     CTE_PairsNonFriends.ID1 AS IDUser1 
     ,F1.ID2 AS FriendOfUser1 
    FROM 
     CTE_PairsNonFriends 
     INNER JOIN @FRIEND AS F1 ON F1.ID1 = CTE_PairsNonFriends.ID1 

    UNION -- sic! not ALL 

    SELECT 
     CTE_PairsNonFriends.ID1 AS IDUser1 
     ,F1.ID1 AS FriendOfUser1 
    FROM 
     CTE_PairsNonFriends 
     INNER JOIN @FRIEND AS F1 ON F1.ID2 = CTE_PairsNonFriends.ID1 
) 
,CTE_FriendsOfUser2 
AS 
(
    SELECT 
     CTE_PairsNonFriends.ID2 AS IDUser2 
     ,F1.ID2 AS FriendOfUser2 
    FROM 
     CTE_PairsNonFriends 
     INNER JOIN @FRIEND AS F1 ON F1.ID1 = CTE_PairsNonFriends.ID2 

    UNION -- sic! not ALL 

    SELECT 
     CTE_PairsNonFriends.ID2 AS IDUser2 
     ,F1.ID1 AS FriendOfUser2 
    FROM 
     CTE_PairsNonFriends 
     INNER JOIN @FRIEND AS F1 ON F1.ID2 = CTE_PairsNonFriends.ID2 
) 

結果集:

SELECT * FROM CTE_FriendsOfUser1 

IDUser1 FriendOfUser1 
4   2 
4   3 
3   1 
3   4 


SELECT * FROM CTE_FriendsOfUser2 

IDUser2 FriendOfUser2 
1   2 
1   3 
2   1 
2   4 

共同的朋友

join USER1與他們的好友列表上的用戶2找出他們共同的朋友。與用戶名

.... 
,CTE_MutualFriends 
AS 
(
    SELECT * 
    FROM 
     CTE_FriendsOfUser1 
     INNER JOIN CTE_FriendsOfUser2 ON CTE_FriendsOfUser2.FriendOfUser2 = CTE_FriendsOfUser1.FriendOfUser1 
    WHERE 
     CTE_FriendsOfUser1.IDUser1 <> CTE_FriendsOfUser2.IDUser2 
) 

計數共同的朋友

,CTE_FriendCount 
AS 
(
    SELECT 
     IDUser1 
     ,IDUser2 
     ,COUNT(*) AS FriendCount 
    FROM CTE_MutualFriends 
    GROUP BY IDUser1, IDUser2 
) 

最終全場查詢

結果排序的好友數。您只能返回第一行(或前幾行)返回共同朋友數最多的用戶。其實,它應該由TOP與領帶。

WITH 
CTE_Pairs 
AS 
(
    SELECT 
     U1.ID AS ID1 
     ,U2.ID AS ID2 
    FROM 
     @USERS AS U1 
     CROSS JOIN @USERS AS U2 
    WHERE 
     U1.ID > U2.ID 
) 
,CTE_PairsNonFriends 
AS 
(
    SELECT ID1, ID2 
    FROM CTE_Pairs 

    EXCEPT 

    SELECT ID1, ID2 
    FROM @FRIEND 

    EXCEPT 

    SELECT ID2, ID1 
    FROM @FRIEND 
) 
,CTE_FriendsOfUser1 
AS 
(
    SELECT 
     CTE_PairsNonFriends.ID1 AS IDUser1 
     ,F1.ID2 AS FriendOfUser1 
    FROM 
     CTE_PairsNonFriends 
     INNER JOIN @FRIEND AS F1 ON F1.ID1 = CTE_PairsNonFriends.ID1 

    UNION -- sic! not ALL 

    SELECT 
     CTE_PairsNonFriends.ID1 AS IDUser1 
     ,F1.ID1 AS FriendOfUser1 
    FROM 
     CTE_PairsNonFriends 
     INNER JOIN @FRIEND AS F1 ON F1.ID2 = CTE_PairsNonFriends.ID1 
) 
,CTE_FriendsOfUser2 
AS 
(
    SELECT 
     CTE_PairsNonFriends.ID2 AS IDUser2 
     ,F1.ID2 AS FriendOfUser2 
    FROM 
     CTE_PairsNonFriends 
     INNER JOIN @FRIEND AS F1 ON F1.ID1 = CTE_PairsNonFriends.ID2 

    UNION -- sic! not ALL 

    SELECT 
     CTE_PairsNonFriends.ID2 AS IDUser2 
     ,F1.ID1 AS FriendOfUser2 
    FROM 
     CTE_PairsNonFriends 
     INNER JOIN @FRIEND AS F1 ON F1.ID2 = CTE_PairsNonFriends.ID2 
) 
,CTE_MutualFriendsRaw 
AS 
(
    SELECT 
     CTE_FriendsOfUser1.FriendOfUser1 AS MutualFriend 
     ,IDUser1 
     ,IDUser2 
    FROM 
     CTE_FriendsOfUser1 
     INNER JOIN CTE_FriendsOfUser2 ON CTE_FriendsOfUser2.FriendOfUser2 = CTE_FriendsOfUser1.FriendOfUser1 
    WHERE 
     CTE_FriendsOfUser1.IDUser1 <> CTE_FriendsOfUser2.IDUser2 
) 
,CTE_MutualFriends 
AS 
(
    SELECT DISTINCT 
     MutualFriend 
     ,CASE WHEN IDUser1 < IDUser2 THEN IDUser1 ELSE IDUser2 END AS IDUser1 
     ,CASE WHEN IDUser1 < IDUser2 THEN IDUser2 ELSE IDUser1 END AS IDUser2 
    FROM 
     CTE_MutualFriendsRaw 
) 
,CTE_FriendCount 
AS 
(
    SELECT 
     IDUser1 
     ,IDUser2 
     ,COUNT(*) AS FriendCount 
    FROM CTE_MutualFriends 
    GROUP BY IDUser1, IDUser2 
) 
SELECT 
    CTE_FriendCount.IDUser1 
    ,CTE_FriendCount.IDUser2 
    ,CTE_FriendCount.FriendCount 
    ,U1.NAME AS Name1 
    ,U2.NAME AS Name2 
FROM 
    CTE_FriendCount 
    INNER JOIN @USERS AS U1 ON U1.ID = CTE_FriendCount.IDUser1 
    INNER JOIN @USERS AS U2 ON U2.ID = CTE_FriendCount.IDUser2 
ORDER BY FriendCount DESC 
; 

結果集:

IDUser1 IDUser2 FriendCount Name1 Name2 
4   1   2    Tom  Jimmy 
3   2   2    Alice Sam 

可能有CTE_MutualFriends一個問題。同樣的問題,一對可以列爲(1,2)(2,1)。例如,您可以將(a,b)NN和配對MM。嚴格地說,應該有另一個步驟來尋找這樣的對並將它們結合在一起。我不確定是否使用當前查詢這樣的對可能或不可以。

There is原始版本CTE_MutualFriends存在問題,所以我添加了額外的步驟以消除查詢的最終完整版本中的重複項。給定的示例數據太小,並且很容易具有所有可能的變體,因此版本版本提供了正確的結果。如果我們在示例數據中添加更多條目,我們會看到需要額外的步驟。

甲骨文語法版本

經過與http://sqlfiddle.com/#!4/48e1f/21/0

WITH 
CTE_Pairs 
AS 
(
    SELECT 
     U1.ID ID1 
     ,U2.ID ID2 
    FROM 
     USERS U1 
     CROSS JOIN USERS U2 
    WHERE 
     U1.ID > U2.ID 
) 
,CTE_PairsNonFriends 
AS 
(
    SELECT ID1, ID2 
    FROM CTE_Pairs 

    MINUS 

    SELECT ID1, ID2 
    FROM FRIEND 

    MINUS 

    SELECT ID2, ID1 
    FROM FRIEND 
) 
,CTE_FriendsOfUser1 
AS 
(
    SELECT 
     CTE_PairsNonFriends.ID1 IDUser1 
     ,F1.ID2 FriendOfUser1 
    FROM 
     CTE_PairsNonFriends 
     INNER JOIN FRIEND F1 ON F1.ID1 = CTE_PairsNonFriends.ID1 

    UNION 

    SELECT 
     CTE_PairsNonFriends.ID1 IDUser1 
     ,F1.ID1 FriendOfUser1 
    FROM 
     CTE_PairsNonFriends 
     INNER JOIN FRIEND F1 ON F1.ID2 = CTE_PairsNonFriends.ID1 
) 
,CTE_FriendsOfUser2 
AS 
(
    SELECT 
     CTE_PairsNonFriends.ID2 IDUser2 
     ,F1.ID2 FriendOfUser2 
    FROM 
     CTE_PairsNonFriends 
     INNER JOIN FRIEND F1 ON F1.ID1 = CTE_PairsNonFriends.ID2 

    UNION 

    SELECT 
     CTE_PairsNonFriends.ID2 IDUser2 
     ,F1.ID1 FriendOfUser2 
    FROM 
     CTE_PairsNonFriends 
     INNER JOIN FRIEND F1 ON F1.ID2 = CTE_PairsNonFriends.ID2 
) 
,CTE_MutualFriendsRaw 
AS 
(
    SELECT 
     CTE_FriendsOfUser1.FriendOfUser1 MutualFriend 
     ,IDUser1 
     ,IDUser2 
    FROM 
     CTE_FriendsOfUser1 
     INNER JOIN CTE_FriendsOfUser2 ON CTE_FriendsOfUser2.FriendOfUser2 = CTE_FriendsOfUser1.FriendOfUser1 
    WHERE 
     CTE_FriendsOfUser1.IDUser1 <> CTE_FriendsOfUser2.IDUser2 
) 
,CTE_MutualFriends 
AS 
(
    SELECT DISTINCT 
     MutualFriend 
     ,CASE WHEN IDUser1 < IDUser2 THEN IDUser1 ELSE IDUser2 END IDUser1 
     ,CASE WHEN IDUser1 < IDUser2 THEN IDUser2 ELSE IDUser1 END IDUser2 
    FROM 
     CTE_MutualFriendsRaw 
) 
,CTE_FriendCount 
AS 
(
    SELECT 
     IDUser1 
     ,IDUser2 
     ,COUNT(*) FriendCount 
    FROM CTE_MutualFriends 
    GROUP BY IDUser1, IDUser2 
) 
SELECT 
    CTE_FriendCount.IDUser1 
    ,CTE_FriendCount.IDUser2 
    ,CTE_FriendCount.FriendCount 
    ,U1.NAME Name1 
    ,U2.NAME Name2 
FROM 
    CTE_FriendCount 
    INNER JOIN USERS U1 ON U1.ID = CTE_FriendCount.IDUser1 
    INNER JOIN USERS U2 ON U2.ID = CTE_FriendCount.IDUser2 
ORDER BY FriendCount DESC 
; 
+0

僅供參考,您可以使用SQL Fiddle(sqlfiddle.com)在其他DBMS中測試。 – 2015-02-09 00:00:18

+0

@DavidFaber,我使用sql fiddle將查詢轉換爲Oracle語法。正如我預料的那樣,這非常簡單。 – 2015-02-09 00:33:21

0

我想你想是這樣的:

WITH uf AS (
    SELECT id1 AS user_id, id2 AS friend_id FROM friends 
    UNION ALL 
    SELECT id2 AS user_id, id1 AS friend_id FROM friends 
), xf AS (
    SELECT user_id1, user_id2, friend_cnt FROM (
     SELECT uf1.user_id AS user_id1, uf2.user_id AS user_id2 
      , COUNT(*) AS friend_cnt 
      , RANK() OVER (ORDER BY COUNT(*) DESC) AS rn 
      FROM uf uf1 INNER JOIN uf uf2 
      ON uf1.friend_id = uf2.friend_id 
      AND uf1.user_id < uf2.user_id 
     GROUP BY uf1.user_id, uf2.user_id 
    ) WHERE rn = 1 
) 
SELECT xf.friend_cnt, u1.username || ',' || u2.username 
    FROM xf INNER JOIN users u1 
    ON xf.user_id1 = u1.user_id 
INNER JOIN users u2 
    ON xf.user_id2 = u2.user_id; 

在我收到的用戶與他們的朋友第一CTE;在第二次我得到用戶與朋友的共同點,然後通過計數排名;在主要查詢中,我只是獲取用戶名並將它們連接起來。

Please see SQL Fiddle demo here.請注意,儘管吉米和湯姆各有四個朋友在演示中,friend_cnt值爲3,因爲這是他們共同的朋友的數量(我加了幾個朋友到你的樣本數據)。

相關問題