MySQL查询共同的朋友

时间:2012-04-06 11:55:00

标签: mysql sql mutual-friendship

  

可能重复:
  MYSQL select mutual friends

我有一张友谊表,友谊只存储在一行。所以没有重复的条目。

id  Person1    Person2  status
1         1          2  friend
2         1          3  friend
3         2          3  friend
4         3          4  friend

什么MySQL查询(加入,内部联接)将帮助我找到人#1和人#3之间的共同(共同)朋友?此示例中的输入为{1,3},输出应为{2},因为Person#2是bot#1和#3的朋友。

11 个答案:

答案 0 :(得分:8)

嗯,到目前为止唯一可能工作的查询是Simon的......但这真是一种过度杀伤 - 这种复杂讨厌的查询(2个子查询有2个联盟!)这么简单,你需要放置一笔赏金吗? :-)如果你有1000多个用户,那么查询将会很慢 - 因为地狱 - 它是二次的,并且由于子查询中的联合,几乎不会使用任何索引!

我建议再次重新考虑设计并允许2个重复的行来建立友谊:

id  Person1    Person2  status
1         1          2  friend
2         2          1  friend
3         1          3  friend
4         3          1  friend

您可能认为效率低下,但简化后会允许将查询重写为简单连接:

select f1.Person2 as common_friend
from friends as f1 join friends as f2
    using (Person2)
where f1.Person1 = '$id1' and f2.Person1 = '$id2' 
    and f1.status = 'friend' and f2.status = 'friend'

这将是快死的! (不要忘记为Person1,2添加索引。)I've advised a similar simplification (rewriting subqueries to joins) in other very nasty data structure and it has speeded up the query从永恒到闪电战瞬间!

所以看起来可能是一个很大的开销(一个友谊的两行)实际上是一个很大的优化: - )

此外,它将使“查找所有X的朋友”等查询变得更加容易。并且不需要花更多的奖励: - )

答案 1 :(得分:2)

set search_path='tmp';

DROP TABLE friendship CASCADE;
CREATE TABLE friendship
        ( id integer not null PRIMARY KEY
        , person1 integer not null
        , person2 integer not null
        , status varchar
        , CONSTRAINT pk1 UNIQUE (status,person1,person2)
        , CONSTRAINT pk2 UNIQUE (status,person2,person1)
        , CONSTRAINT neq CHECK (person1 <> person2)
        );

INSERT INTO friendship(id,person1,person2,status) VALUES
 (1,1,2,'friend' ) ,(2,1,3,'friend' ) ,(3,2,3,'friend' ) ,(4,3,4,'friend' )
        ;

        -- -----------------------------------------
        -- For implementations that don't have CTEs, 
        -- a view can be used to emulate a CTE.
        -- -----------------------------------------
CREATE VIEW flip AS (
        SELECT person1 AS one
                , person2 AS two
        FROM friendship WHERE status = 'friend'
        UNION
        SELECT person2 AS one
                , person1 AS two
        FROM friendship WHERE status = 'friend'
        );

SELECT DISTINCT
        f1.two AS common
FROM flip f1
JOIN flip f2 ON f1.two = f2.two
WHERE f1.one = 1
AND f2.one = 3
        ;

DROP VIEW flip;

        -- ------------------------------
        -- The same query with a real CTE
        -- ------------------------------
with flip AS (
        SELECT person1 AS one
                , person2 AS two
        FROM friendship WHERE status = 'friend'
        UNION
        SELECT person2 AS one
                , person1 AS two
        FROM friendship WHERE status = 'friend'
        )
SELECT DISTINCT
        f1.two AS common
FROM flip f1
JOIN flip f2 ON f1.two = f2.two
WHERE f1.one = 1
AND f2.one = 3
        ;

结果:

SET
DROP TABLE
NOTICE:  CREATE TABLE / PRIMARY KEY will create implicit index "friendship_pkey" for table "friendship"
NOTICE:  CREATE TABLE / UNIQUE will create implicit index "pk1" for table "friendship"
NOTICE:  CREATE TABLE / UNIQUE will create implicit index "pk2" for table "friendship"
CREATE TABLE
INSERT 0 4
CREATE VIEW
 common 
--------
      2
(1 row)

DROP VIEW
 common 
--------
      2
(1 row)

答案 2 :(得分:2)

还有一个答案。

select 
    (case when f1.person1 = 1 then f1.person2 else f1.person1 end) as fid
from friends f1
where f1.person1 = 1 or f1.person2 = 1
and f1.status = 'friend'

intersect

select 
    (case when f1.person1 = 3 then f1.person2 else f1.person1 end) as fid
from friends f1
where f1.person1 = 3 or f1.person2 = 3
and f1.status = 'friend'

答案 3 :(得分:1)

如果这些条件不符合使其工作所需的小调整,则此查询的假设是友谊表中没有自助友好且没有重复项。

SELECT fid FROM 
(
    --FIRST PERSON (X) FRIENDLIST
    SELECT 
        (CASE WHEN Person1 = X THEN Person2 ELSE Person1 END) AS fid
    FROM Friendships WHERE (Person1 = X OR Person2 = X) AND status = "friend"
    UNION ALL --DO NOT REMOVE DUPLICATES WITH ALL JOIN
    --SECOND PERSON (Y) FRIENDLIST
    SELECT 
        (CASE WHEN Person1 = Y THEN Person2 ELSE Person1 END) AS fid
    FROM Friendships WHERE (Person1 = Y OR Person2 = Y) AND status = "friend"
) FLIST
GROUP BY fid
HAVING COUNT(*) = 2

答案 4 :(得分:0)

我询问编号较低的用户是否总是Person1,但我最后写了一个不关心这是否属实的查询。

set @firstParty = 1, @secondParty = 3

select friends_of_first.friend
from (
    select Person2 as friend from friends where Person1 = @firstParty
    union 
    select Person1 as friend from friends where Person2 = @firstParty
    ) as friends_of_first
join (
    select Person2 as friend from friends where Person1 = @secondParty
    union 
    select Person1 as friend from friends where Person2 = @secondParty
    ) as friends_of_second
on friends_of_first.friend = friends_of_second.friend

用于查找用户朋友的子查询可以替换为使用的@Nirmal-thInk beYond:

select case when f1.person1 = @firstParty then  f1.person2 else f1.person1 end 
from friend f1 where f1.person1 = @firstParty or f1.person2 = @firstParty

我很想知道哪种替代方案表现得更好。

答案 5 :(得分:0)

如果各种回复或评论之一已经提出过这一点,我很抱歉,但是如何:

select Person2 mutual_friend from 
  (select Person1, Person2 from friends 
      where Person1 in (1,3) union 
   select Person2, Person1 from friends 
      where Person2 in (1,3)
  ) t 
  group by Person2 having count(*) > 1;

答案 6 :(得分:0)

内部查询专门只获取第一个人的FRIEND ID,并将其标准化为单个列“FriendID”。如果找到的记录在第一个位置有人ID = 1,则抓住第二个...如果第二个位置的人ID = 1,则抓住第一个。

通过这样做,我们知道朋友的单一名单是谁1 ...完成。现在,再次加入友谊表,但仅限于那些第一人被认定为人1的朋友之一...一旦合格,那么确保第二张桌上的另一个人是那个人3你正在寻找共性。

确保在person2上的索引和person2上的另一个索引以利用OR条件。

select
      JustPerson1Friends.FriendID
   from
      ( select
              if( f.Person1 = 1, f.Person2, f.Person1 ) as FriendID
           from
              Friendships f
           where
                   (    f.Person1 = 1
                     OR f.Person2 = 1 )
               AND f.status = "friend" ) JustPerson1Friends
      JOIN Friendships f2
         on  (   JustPerson1Friends.FriendID = f2.Person1
              OR JustPerson1Friends.FriendID = f2.Person2 )
         AND f2.status = "friend"
         AND ( f2.Person1 = 3 OR f2.person2 = 3 )

另一个选项是将“3”作为常用标记预先标记到结果集中,这样我们就不需要在以后明确限定3。此外,通过使用MySQL变量,易于编写脚本并实现为参数。在内部查询之后,对友谊进行DOUBLE左连接以明确地测试可以在X / Y或Y / X组合中找到人的两种组合。所以最后的where子句就是说只要在EITHER LEFT-JOIN条件中找到一条记录,它就是一个共同的朋友并包含在结果集中。

select
      JustPerson1Friends.FriendID
   from
      ( select
              @WantPerson2 as FindInCommonWith,
              if( f.Person1 = @WantPerson1, f.Person2, f.Person1 ) as FriendID
           from
              ( select @WantPerson1 := 1,
                       @WantPerson2 := 3 ) sqlvars
              Friendships f,
              (
           where
                   (    f.Person1 = @WantPerson1
                     OR f.Person2 = @WantPerson2 )
               AND f.status = "friend" ) JustPerson1Friends

      LEFT JOIN Friendships f2
         on JustPerson1Friends.FindInCommonWith = f2.Person1
         AND JustPerson1Friends.FriendID = f2.Person2
         AND f2.status = "friend"

      LEFT JOIN Friendships f3
         on JustPerson1Friends.FindInCommonWith = f2.Person2
         AND JustPerson1Friends.FriendID = f2.Person1
         AND f2.status = "friend"
   where
         f2.Person1 > 0
      OR f3.Person1 > 0

答案 7 :(得分:0)

此查询返回'22'作为结果,因为它对于1和3都是常见的 您可能必须过滤掉不同的PERSON1 / PERSON2 如果我可以优化此查询,我会更新它


SELECT DISTINCT (REPLACE(TRANSLATE((WM_CONCAT(DISTINCT F.PERSON1) || ',' ||
                                           WM_CONCAT(DISTINCT F.PERSON2)),
                                           '1,3',
                                           ' '),
                                 ' ',
                                 '')) AS COMMON_FRIEND
          FROM FRIENDSHIP F
         WHERE UPPER(F.STATUS) = 'FRIEND'
         AND ((SELECT DISTINCT WM_CONCAT(F1.PERSON1)
                   FROM FRIENDSHIP F1
                  WHERE F1.PERSON2 = '3') LIKE ('%' || F.PERSON1 || '%') OR
               (SELECT DISTINCT WM_CONCAT(F1.PERSON2)
                   FROM FRIENDSHIP F1
                  WHERE F1.PERSON1 = '3') LIKE ('%' || F.PERSON2 || '%'))
           AND ((SELECT DISTINCT WM_CONCAT(F1.PERSON1)
                   FROM FRIENDSHIP F1
                  WHERE F1.PERSON2 = '1') LIKE ('%' || F.PERSON1 || '%') OR
               (SELECT DISTINCT WM_CONCAT(F1.PERSON2)
                   FROM FRIENDSHIP F1
                  WHERE F1.PERSON1 = '1') LIKE ('%' || F.PERSON2 || '%'))
           AND NOT ((F.PERSON1 = '1' AND F.PERSON2 = '3') OR
                (F.PERSON1 = '3' AND F.PERSON2 = '1'))

答案 8 :(得分:-1)

我认为这可以通过这个

来实现
SELECT * FROM friends

WHERE
     (Person1 = '1' or Person2 = '1') && 
     (Person1 = '2' or Person2 = '2') &&
     status = 'friend'

鉴于你正试图找到1号和2号人之间的相互作用

答案 9 :(得分:-1)

这应该回答你当前的问题,虽然我建议不要这样做。在这种情况下,我总是选择存储两个关系副本,每个方向一个。

SELECT IF(f1.person1 IN ($id1, $id3), f1.person2, f1.person1) AS mutual_friend
FROM friends f1
INNER JOIN friends f2
    ON (f1.person1 = $id1 AND f2.person1 = $id3 AND f1.person2 = f2.person2)
    OR (f1.person1 = $id1 AND f2.person2 = $id3 AND f1.person2 = f2.person1)
    OR (f1.person2 = $id1 AND f2.person1 = $id3 AND f1.person1 = f2.person2)
    OR (f1.person2 = $id1 AND f2.person2 = $id3 AND f1.person1 = f2.person1)
WHERE f1.status = 'friend' AND f2.status = 'friend'

答案 10 :(得分:-1)

id  Person1    Person2  status
1         1          2  friend
2         1          3  friend
3         2          3  friend
4         3          4  friend


  SELECT
    DISTINCT
    F1.Person
  FROM
    --Friends of 1
    (
    SELECT F.Person1 Person FROM People F WHERE F.Person2 = 1 AND F.status = 'friend'
    UNION
    SELECT F.Person2 Person FROM People F WHERE F.Person1 = 1 AND F.status = 'friend'
    ) F1
    INNER JOIN
    (
    --Friends of 3
    SELECT F.Person1 Person FROM People F WHERE F.Person2 = 3 AND F.status = 'friend'
    UNION
    SELECT F.Person2 Person FROM People F WHERE F.Person1 = 3 AND F.status = 'friend'
    ) F2 ON
      F2.Person = F1.Person

输出:

Person
2