2

我正在尝试根据以下问题插入所有关系。我已经得到了从 A 到 C 的所有关系(根据下面的问题)。但就我而言,我也得到了“C朋友与A”的记录。据我了解问题陈述“没有重复的友谊”,我必须将友谊插入 A 到 C,反之亦然。要么我理解错了问题,要么我无法得到想要的结果。
所以,当我尝试将我得到的所有值插入到表中时,我的结果是错误的。有些人的朋友超出预期,数值为 2。

表的结构如下:

Friend ( ID1, ID2 )

ID1 的学生与 ID2 的学生是朋友。友谊是相互的,所以如果 (123, 456) 在 Friend 表中,那么 (456, 123) 也是。

(没有主键)我试图解决的情况是:

“对于 A 和 B 的朋友,B 和 C 的朋友的所有情况,为 A 和 C 对添加新的友谊。不要添加重复的友谊、已经存在的友谊或与自己的友谊。”

我已经尝试解决这个问题 2 天了。请帮忙。

提前致谢。

----我的SQL查询-----

select B.ID1 as ID1,B.ID3 as ID2
from (select A.ID1 as ID1,A.ID2 as ID2,A.ID3 as ID3,F3.ID2 as ID4
from (select F1.ID1 as ID1,F1.ID2 as ID2,F2.ID2 as ID3
from Friend F1 join Friend F2
on F1.ID2=F2.ID1
where F1.ID1<>F2.ID2) A join Friend F3
on A.ID3=F3.ID1) B
where B.ID1<>B.ID4
group by  B.ID1,B.ID3
4

2 回答 2

2

让我们假设(对于示例)朋友表包含这些行。

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从两个或多个共享的朋友那里获得友谊,例如br。两者a-b + b-ca-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)
于 2013-08-23T18:58:34.693 回答
1

对我来说,上述每个答案看起来都很麻烦。我认为有一种更简单的方法来表示查询。如上述答案所述,让我们假设 Friend 表如下所示。

ID1 ID2
--- ---
 抗体
 交流
 巴
 公元前
 BD
 CB

如果我们想要朋友互易的情况,我们会寻找“a”是“b”的朋友并且“b”是“a”的朋友的情况。这样的查询应该产生以下结果。

ID1 ID2
--- ---
 抗体
 巴
 公元前
 CB

下面的 sql 命令会很好地切入正题并识别正确的元组。

选择 *
来自朋友
其中 (ID2, ID1) in (select * From Friend)

这会产生以下内容。

ID1 ID2
--- ---
 抗体
 巴
 公元前
 CB

如果我想列出一次,消除重复项,只需添加一个“和”选项,该选项将删除一个潜在的元组,例如 ID2 小于 ID1。完整的 SQL 语句如下所示。

select * from Friend where (ID2, ID1) in (select * From Friend) and ID2 < ID1;

并且重复被消除。

ID1 ID2
--- ---
 抗体
 公元前
于 2015-09-17T20:44:37.320 回答