(例として) フレンド テーブルにこれらの行が含まれていると仮定します。
ID1 ID2
--- ---
a b
a c
b a
b c
b d
c b
まず、次のようなクエリを使用して、フレンド テーブルから「完全なフレンド」タプルを特定することから始めます。
SELECT fa.ID1
, fa.ID2
FROM friend fa
JOIN friend fb
ON fb.ID1 = fa.ID2
AND fb.ID2 = fa.ID1
fa.ID1 fa.ID2
------ ------
a b
b a
b c
c b
この結果は、a が b と友達であり、b が c と友達であることを示しています。(a,c)との行は、逆のor (b,d)がないため省略されています。(c,a)(d,b)
当分の間、このセットを " ft" (フレンド タプル) と呼びます。これで、そのセット (ft) に対するクエリを記述して、"a->b->c" および "c->b->a" のすべてのフレンド ペアを取得できます。
SELECT fx.ID1
, fy.ID2
FROM ft fx
JOIN ft fy
ON fy.ID1 = fx.ID2
AND fy.ID2 <> fx.ID1
fx.ID1 fy.ID2
------ ------
a c
c a
ただし、友人テーブルに既に存在する行を複製しないようにする必要があるため、NOT IN または NOT EXISTS 述語を使用するか、結合防止パターンを使用して、友人テーブルにある行を削除できます。フレンド テーブルに既にある行と一致します。
SELECT fx.ID1
, fy.ID2
FROM ft fx
JOIN ft fy
ON fy.ID1 = fx.ID2
AND fy.ID2 <> fx.ID1
-- eliminate rows that match
LEFT
JOIN friend fe
ON fe.ID1 = fx.ID1
AND fe.ID2 = fy.ID2
WHERE fe.ID1 IS NULL
fx.ID1 fy.ID2
------ ------
c a
ここで、への参照をft、セットを生成するクエリ (インライン ビューとして) に置き換えることができます。
SELECT fx.ID1
, fy.ID2
FROM ( SELECT fa.ID1
, fa.ID2
FROM friend fa
JOIN friend fb
ON fb.ID1 = fa.ID2
AND fb.ID2 = fa.ID1
) fx
JOIN ( SELECT fc.ID1
, fc.ID2
FROM friend fc
JOIN friend fd
ON fd.ID1 = fc.ID2
AND fd.ID2 = fc.ID1
) fy
ON fy.ID1 = fx.ID2
AND fy.ID2 <> fx.ID1
-- eliminate rows that match
LEFT
JOIN friend fe
ON fe.ID1 = fx.ID1
AND fe.ID2 = fy.ID2
WHERE fe.ID1 IS NULL
GROUP
BY fx.ID1
, fy.ID2
((ID1、ID2)が一意であることが保証されている限り、このクエリは重複を生成しないと考えています。そして、このクエリは指定された一致のみを生成し、余分な一致は生成しないと考えています. 確認のためにいくつかの追加のテスト ケースがあります. クエリが重複を生成する場合は、クエリに aGROUP BY fx.ID1, fy.ID2を追加するとそれらが削除されます.)
最後に、これらの行をフレンド テーブルに入れるには、クエリの前に次を付けます。
INSERT INTO friend (ID1,ID2)
アップデート
返してほしい結果は、「友情」がどのように表現されるかによって異なります。
私は、「友達」のペアがfriend2 つのタプルの存在によって表に表されていると想定していました:(a,b)と (b,a) の両方が存在する必要があります。(友情は、「a の友人 b」と「b の友人 a」で形成されます)。
行の 1 つだけが存在する場合、それは本当の友情ではなく、中途半端な友情です。
いくつかのテストケースを実行しました。それらを介して作業するのはちょっと面倒です。ORDER BY を追加して行を決定論的な順序に戻し、SELECT リストに列を追加して「パス」(共有フレンド) を検証することで、クエリを拡張しました。WHERE 句をコメントアウトしたので、すべての潜在的な友達を見ることができました。
GROUP BY重複を排除するために a を追加する必要があることがわかりました。a-c友情は、2 人以上の共有された友人 (例:bと) から派生させることができますr。a-b + b-cとの両方がa-r + r-c得られa-cます。
これは私がテストした最後のクエリです。GROUP BY が追加されていることを除けば、基本的に前のものと同じです。
SELECT fx.ID1
, fy.ID2
-- , fx.ID1>fy.ID2 AS d
-- , fx.ID1 AS x1
-- , fx.ID2 As x2
-- , fy.ID1 AS y1
-- , fy.ID2 As y2
-- , fe.ID1 AS e1
-- , fe.ID2 AS e2
FROM ( SELECT fa.ID1
, fa.ID2
, fa.ID1>fa.ID2 AS d
FROM friend fa
JOIN friend fb
ON fb.ID1 = fa.ID2
AND fb.ID2 = fa.ID1
-- ORDER
-- BY LEAST(fa.ID1,fa.ID2)
-- , GREATEST(fa.ID1,fa.ID2)
-- , fa.ID1>fa.ID2
) fx
JOIN ( SELECT fc.ID1
, fc.ID2
FROM friend fc
JOIN friend fd
ON fd.ID1 = fc.ID2
AND fd.ID2 = fc.ID1
-- ORDER
-- BY LEAST(fc.ID1,fc.ID2)
-- , GREATEST(fc.ID1,fc.ID2)
-- , fc.ID1>fc.ID2
) fy
ON fy.ID1 = fx.ID2
AND fy.ID2 <> fx.ID1
-- eliminate rows that match existing row
LEFT
JOIN friend fe
ON fe.ID1 = fx.ID1
AND fe.ID2 = fy.ID2
WHERE fe.ID1 IS NULL
GROUP
BY fx.ID1
, fy.ID2
ORDER
BY LEAST(fx.ID1,fy.ID2)
, GREATEST(fx.ID1,fy.ID2)
, fx.ID1>fy.ID2
完全な友情関係がただ 1 つのタプルの存在によって表される場合、"(a,b)" は "(b,a)" を意味するため、クエリを変更する必要があります。
fxandのインライン ビュー クエリはfy、「欠落している」逆タプルを返すように拡張する必要があります... (a,b) がフレンド テーブルにある場合、クエリは (a,b) と (b,a) の両方を返す必要があります。 )。SELECT リストの列の順序を逆にして、2 つの同一のクエリ間で UNION ALL 操作を実行することで、これを実現できます。(ここでは、UNION ALL の代わりに UNION を実際に使用して、重複を排除することができます。) fxandのインライン ビュー クエリは次のfyようになります。
SELECT fa.ID1, fa.ID2 FROM ...
UNION ALL
SELECT fa.ID2, fa.ID1 FROM ...
フレンド テーブル内の一致する行を除外するチェックも変更する必要があります (既存の (a,b) または ( b,a) 行)
ON ( fe.ID1 = fx.ID1 AND fe.ID2 = fy.ID2 )
OR ( fe.ID1 = fy.ID2 AND fe.ID2 = fx.ID1 )
また、「余分な」逆タプルを削除するには、SELECT リストと GROUP BY を変更する必要があります。ORDER BY のような式を使用できます
SELECT LEAST(fx.ID1,fy.ID2) AS ID1
, GREATEST(fx.ID1,fy.ID2) AS ID2
...
GROUP
BY LEAST(fx.ID1,fy.ID2)
, GREATEST(fx.ID1,fy.ID2)