让我们假设(对于示例)朋友表包含这些行。
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)
和行被省略,(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) 是唯一的,这个查询就不会生成任何重复项。我认为这个查询只会生成指定的匹配项,而不是任何额外的匹配项. 一些额外的测试用例是为了确认。如果查询确实产生了任何重复,那么GROUP BY fx.ID1, fy.ID2
在查询中添加 a 将消除它们。)
最后,要将这些行放入朋友表中,请在查询之前添加:
INSERT INTO friend (ID1,ID2)
更新
我们想要返回的结果实际上取决于如何表示“友谊”。
我假设“朋友”对在friend
表中由两个元组的存在表示:(a,b)
和 (b,a) 都必须存在。(当“a 朋友 b”和“b 朋友 a”时形成友谊)。
如果只有一行存在,那不是真正的友谊,只是半途而废的友谊。
我运行了几个测试用例。通过它们工作有点乏味。我通过添加 ORDER BY 来扩展查询,以便以确定的顺序返回行,并在 SELECT 列表中添加其他列,以验证“路径”(共享的朋友)。我注释掉了 WHERE 子句,所以我可以看到所有潜在的朋友。
我确实发现我需要添加一个GROUP BY
来消除重复项。我们可以a-c
从两个或多个共享的朋友那里获得友谊,例如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
如果只存在一个元组“(a,b)”表示完整的友谊意味着“(b,a)”,则需要更改查询。
fx
和的内联视图查询fy
需要扩展以返回“丢失的”逆元组...如果 (a,b) 在朋友表中,我们的查询需要返回 (a,b) 和 (b,a )。我们将通过在两个相同查询之间执行 UNION ALL 操作来实现这一点,只是颠倒了 SELECT 列表中列的顺序。fx
(在这里,我们实际上可以使用 UNION 而不是 UNION ALL 来消除任何重复。) and的内联视图查询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)