【问题标题】:Eliminate similar cycles in directed graph消除有向图中的相似循环
【发布时间】:2020-05-22 09:32:07
【问题描述】:

我有以下数据:

表:

CREATE TABLE tblLoop
(
    person1 varchar(20),
    person2 varchar(20),
    ColDate date,
);

INSERT INTO tblLoop VALUES('A','B','2020-01-01'),('A','C','2020-01-01'),('A','D','2020-01-01'),
                          ('B','E','2020-01-02'),('B','F','2020-01-02'),
                          ('D','G','2020-01-03'),('D','H','2020-01-03'),
                          ('F','i','2020-01-04'),
                          ('G','J','2020-01-05'),
                          ('i','A','2020-01-06'),
                          ('J','D','2020-01-07'),
                          ('X','Y','2020-01-08'),('X','Z','2020-01-08'),
                          ('Z','X','2020-01-09'),
                          ('Y','W','2020-01-09');   

记录看起来像:

要求:我需要找到形成循环的人。对于给定数据中的示例,我们发现了 3 个循环:

循环 1:A 连接到 B 连接到 F 连接到 i 连接到 A

循环 2:A 连接到 D 连接到 G 连接到 J 连接到 D

循环 3:X 连接到 Z 连接到 X

预期结果:

LoopFound
--------------------
A->B->F->i->A
A->D->G->J->D
X->Z->X

我的尝试:

;WITH CTE AS 
(
      SELECT Person1, Person2, 
             CONVERT(VARCHAR(MAX), (','+ Person1+ ','+ Person2+ ',')) AS nodes, 1 AS lev, 
             (CASE WHEN Person1 = Person2 THEN 1 ELSE 0 END) AS has_cycle
      FROM tblLoop e
      UNION ALL
      SELECT cte.Person1, e.Person2,
             CONVERT(VARCHAR(MAX), (cte.nodes+ e.Person2+ ',')), lev + 1,
             (CASE WHEN cte.nodes LIKE ('%,'+ e.Person2+ ',%') THEN 1 ELSE 0 END) AS has_cycle
      FROM CTE 
      JOIN tblLoop e ON e.Person1 = cte.Person2
      WHERE cte.has_cycle = 0 
     )
SELECT *
FROM CTE
WHERE has_cycle = 1;

注意:从上述查询中获取多个循环组合。

【问题讨论】:

  • 我建议,如果可能的话,在你的表中添加一个额外的标志,表明 A 和 X 是根人。在您的 SELECT 语句中,您只能过滤这些人的组合。

标签: sql-server tsql sql-server-2012


【解决方案1】:

根据 Kevin 的评论,答案在于包含一个标志来指示给定节点是一个有效的起点,并且可以包含在查询中:

在这种情况下,“A”和“X”作为起点,因此我们将标记所有记录的始发者是“A”或“X”:

CREATE TABLE #tblLoop
(
    person1 varchar(20),
    person2 varchar(20),
    ColDate date,
isRoot INT
);
INSERT INTO #tblLoop VALUES('A','B','2020-01-01',1),
                          ('A','C','2020-01-01',1),
                          ('A','D','2020-01-01',1),
                          ('B','E','2020-01-02',0),
                          ('B','F','2020-01-02',0),
                          ('D','G','2020-01-03',0),
                          ('D','H','2020-01-03',0),
                          ('F','i','2020-01-04',0),
                          ('G','J','2020-01-05',0),
                          ('i','A','2020-01-06',0),
                          ('J','D','2020-01-07',0),
                          ('X','Y','2020-01-08',1),
                          ('X','Z','2020-01-08',1),
                          ('Z','X','2020-01-09',0),
                          ('Y','W','2020-01-09',0);   

那么下面的查询可以修改如下:

;WITH CTE AS 
(
      SELECT Person1, Person2, isRoot,
             CONVERT(VARCHAR(MAX), (','+ Person1+ ','+ Person2+ ',')) AS nodes, 1 AS lev, 
             (CASE WHEN Person1 = Person2 THEN 1 ELSE 0 END) AS has_cycle
      FROM #tblLoop e
      UNION ALL
      SELECT cte.Person1, e.Person2, cte.isRoot,
             CONVERT(VARCHAR(MAX), (cte.nodes+ e.Person2+ ',')), lev + 1,
             (CASE WHEN cte.nodes LIKE ('%,'+ e.Person2+ ',%') THEN 1 ELSE 0 END) AS has_cycle
      FROM CTE 
      JOIN #tblLoop e ON e.Person1 = cte.Person2
      WHERE cte.has_cycle = 0 
     )
SELECT *
FROM CTE
WHERE has_cycle = 1
AND isRoot = 1

感谢 Kevin 的想法,这只是它的工作实现。

【讨论】:

  • 除了设置flag之外还有什么办法吗?
  • 我尝试使用查询;WITH CTE AS ( SELECT t.* FROM tblLoop t WHERE person1 IN (SELECT person2 FROM tblLoop t2 WHERE t.ColDate <= t2.ColDate ) OR person2 IN (SELECT person1 FROM tblLoop t3 WHERE t.ColDate <= t3.ColDate ) ) SELECT DISTINCT person1 FROM CTE WHERE person1 NOT IN (SELECT person2 FROM CTE); 找到那些起始节点,让我知道你的看法。
【解决方案2】:

我尝试了以下两个步骤:

第 1 步:在此步骤中,找到图形的起始节点。

--Create table to store start nodes
IF OBJECT_ID('dbo.Temp_tblLoop', 'U') IS NOT NULL 
BEGIN
  DROP TABLE dbo.Temp_tblLoop; 
END

--Query to find start nodes.
;WITH CTE AS
(
    SELECT t.* 
    FROM tblLoop t
    WHERE person1 IN (SELECT person2 FROM tblLoop t2 WHERE t.ColDate<= t2.ColDate) OR
          person2 IN (SELECT person1 FROM tblLoop t3 WHERE t.ColDate<= t3.ColDate)
)
SELECT DISTINCT person1 INTO Temp_tblLoop
FROM CTE 
WHERE person1 NOT IN (SELECT person2 FROM CTE);

第 2 步:找出图中的循环(排除相似循环)

;WITH CTE AS 
(
      SELECT Person1, Person2, 
             CONVERT(VARCHAR(MAX), (Person1+ '->'+ Person2)) AS nodes, 1 AS lev, 
             (CASE WHEN Person1 = Person2 THEN 1 ELSE 0 END) AS has_cycle
      FROM tblLoop e WHERE person1 IN (SELECT person1 FROM Temp_tblLoop)
      UNION ALL
      SELECT cte.Person1, e.Person2,
             CONVERT(VARCHAR(MAX), (cte.nodes+'->'+ e.Person2)), lev + 1,
             (CASE WHEN CHARINDEX(e.Person2, cte.nodes) != 0 THEN 1 ELSE 0 END) AS has_cycle
      FROM CTE 
      JOIN tblLoop e ON e.Person1 = cte.Person2
      WHERE cte.has_cycle = 0 
     )
SELECT Person1 AS Start, Person2 AS [End],
       nodes AS Links,
       lev AS Levels     
FROM CTE
WHERE has_cycle = 1;    

如果需要改进,请告诉我您的评论。

【讨论】:

    猜你喜欢
    • 2020-05-22
    • 2015-07-19
    • 1970-01-01
    • 2019-03-28
    • 2019-01-10
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多