10

重複の可能性:
MYSQL は相互の友人を選択します

友情のテーブルがあります。友情は1行にのみ保存されます。したがって、重複エントリはありません。

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

人物 1 と人物 3 の間の共通の (相互の) 友人を見つけるのに役立つ MySQL クエリ (結合、内部結合) はどれですか? この例の入力は {1,3} であり、出力は {2} である必要があります。これは、人物 #2 がボット #1 および #3 と友達であるためです。

4

11 に答える 11

9

まあ、今までうまくいくかもしれない唯一のクエリはサイモンのものです...しかし、それは本当にやり過ぎです-賞金を配置する必要があるほど単純なことに対して、そのような複雑で厄介なクエリ(2つのユニオンを持つ2つのサブクエリ!)? :-) そして、1000 人以上のユーザーがいる場合、クエリは非常に遅くなります - 覚えておいてください、これは 2 次であり、サブクエリのユニオンにより、ほとんどインデックスが使用されません!

デザインをもう一度考え直して、友情のために 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 のインデックスを追加することを忘れないでください。)私は他の非常に厄介なデータ構造で同様の単純化 (サブクエリを結合に書き換える) をアドバイスしました

したがって、大きなオーバーヘッド (1 つの友情に対して 2 行) のように見えたかもしれないものは、実際には大きな最適化です :-)

また、「X のすべての友達を見つける」などのクエリがはるかに簡単になります。そして、これ以上賞金を費やす必要はありません:-)

于 2012-04-16T16:45:28.260 に答える
2

もう1つの答え。

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'
于 2012-04-18T18:47:38.987 に答える
2

このクエリは、フレンドシップ テーブルにセルフ フレンドリングがなく、フレンドシップ テーブルに重複がないという前提で機能します。

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
于 2012-04-18T14:29:59.273 に答える
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)
于 2012-04-18T15:47:27.500 に答える
0

内部クエリは、最初の人のFRIEND IDのみを排他的に取得し、それを単一の列「FriendID」に標準化します。見つかったレコードの最初の位置に人物ID=1がある場合、2番目の位置を取得します...2番目の位置にある人物ID= 1の場合、最初の位置を取得します。

これが行われると、友人の単一のリストが人1の誰であるかがわかります...完了。さて、再び友情テーブルに参加しますが、最初に人1の友達の1人として資格を与えられた人だけが...資格が得られたら、2番目のテーブルの他の人が人3であることを確認します。あなたはの共通点を探しています。

OR条件を利用するには、person1とperson2の両方にインデックスを作成してください。

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
于 2012-04-17T23:24:06.730 に答える
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 に置き換えることができます。

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

どの代替案がより優れたパフォーマンスを発揮するかを知りたいです。

于 2012-04-16T16:09:18.917 に答える
0

これはかなり単純にこれによって達成されると思います

SELECT * FROM friends

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

あなたが人1と2の間の相互を見つけようとしているとすれば

于 2012-04-06T12:18:02.200 に答える
0

このクエリは、1 と 3 の両方に共通しているため、結果として「22」を返します。個別の 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'))

于 2012-04-19T05:34:39.720 に答える
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;
于 2012-04-16T18:13:09.310 に答える
-1

これはあなたの現在の質問に答えるはずですが、私はこのようにすることはお勧めしません。この状況では、私は常に関係の2つのコピーを各方向に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'
于 2012-04-06T13:00:46.193 に答える
-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
于 2012-04-19T14:49:59.067 に答える