在SQL Server中有效查询有向/无向图边的表

时间:2011-01-26 21:00:14

标签: sql sql-server sql-server-2008 graph-theory

我有一个SQL服务器表,其中每一行代表图形网络中的一个边。 FromNodeID和ToNodeID是节点表的外键,架构是这样的:

CREATE TABLE #Edges (
  EdgeID int identity (1,1),
  FromNodeID int,
  ToNodeID int
  );

INSERT INTO #Edges (FromNodeID, ToNodeID) VALUES
  (1,2),
  (1,3),
  (1,4),
  (2,3),
  (3,5),
  (4,5),
  (5,6);

现在,如果我认为每个边都是定向的(即单向),那么很容易计算出我可以直接从任何节点获得的所有节点。我将为FromNodeID列添加一个索引,然后运行如下查询:

SELECT ToNodeID FROM #Edges WHERE FromNodeID = 3

结果:5

但是如果我想将每个边视为单向的,那么构建我的表/查询的最佳方法是什么。即从节点3开始,我想得到结果:

结果:1​​,2,5

我能想到的最简单的方法是在ToNodeID列中添加一个额外的索引,然后运行如下查询:

SELECT ToNodeID FROM #Edges WHERE FromNodeID = 3 
UNION SELECT FromNodeID FROM #Edges WHERE ToNodeID = 3;

但这显然涉及组合来自两个查询的结果集并且看起来效率不高 - 是否有更好的方法在单个查询中编写它? (请注意,我不想再将反转边插入表中 - 我需要能够在运行时将边处理为有向或无向)。

感谢您的任何建议!

3 个答案:

答案 0 :(得分:4)

  

但这显然涉及组合来自两个查询的结果集,并且看起来效率不高 - 有没有更好的方法在单个查询中编写它?

这很有效率。

你可以这样做:

SELECT  CASE 3 WHEN FromNodeId THEN ToNodeId ELSE FromNodeId END
FROM    Edges
WHERE   3 IN (FromNodeId, ToNodeId)

但这基本上是相同的(这些索引会UNION)。

这是一个要测试的脚本:

CREATE TABLE #Edges
        (
        EdgeID INT IDENTITY (1,1) PRIMARY KEY,
        FromNodeID int NOT NULL,
        ToNodeID int NOT NULL
        )
CREATE INDEX ix_edges_from ON #Edges (FromNodeID, ToNodeId)
CREATE INDEX ix_edges_to ON #Edges (ToNodeID, FromNodeId)
;
WITH    q (rn) AS
        (
        SELECT  1
        UNION ALL
        SELECT  rn + 1
        FROM    q
        WHERE   rn < 1000
        )
INSERT
INTO    #Edges (FromNodeId, ToNodeId)
SELECT  q1.rn, q2.rn
FROM    q q1
CROSS JOIN
        q q2
WHERE   (q1.rn + q2.rn) % 37 = 0
OPTION (MAXRECURSION 0)

对于UNION

SELECT  ToNodeId
FROM    #Edges
WHERE   FromNodeId = 3
UNION
SELECT  FromNodeId
FROM    #Edges
WHERE   ToNodeId = 3


  |--Stream Aggregate(GROUP BY:([Union1006]))
       |--Merge Join(Concatenation)
            |--Index Seek(OBJECT:([tempdb].[dbo].[#Edges]), SEEK:([tempdb].[dbo].[#Edges].[FromNodeID]=(3)) ORDERED FORWARD)
            |--Index Seek(OBJECT:([tempdb].[dbo].[#Edges]), SEEK:([tempdb].[dbo].[#Edges].[ToNodeID]=(3)) ORDERED FORWARD)

对于IN

  |--Compute Scalar(DEFINE:([Expr1003]=CASE WHEN (3)=[tempdb].[dbo].[#Edges].[FromNodeID] THEN [tempdb].[dbo].[#Edges].[ToNodeID] ELSE [tempdb].[dbo].[#Edges].[FromNodeID] END))
       |--Sort(DISTINCT ORDER BY:([tempdb].[dbo].[#Edges].[EdgeID] ASC))
            |--Concatenation
                 |--Index Seek(OBJECT:([tempdb].[dbo].[#Edges]), SEEK:([tempdb].[dbo].[#Edges].[FromNodeID]=(3)) ORDERED FORWARD)
                 |--Index Seek(OBJECT:([tempdb].[dbo].[#Edges]), SEEK:([tempdb].[dbo].[#Edges].[ToNodeID]=(3)) ORDERED FORWARD)

正如您所看到的,计划基本相同:它们都从相应的索引中获取值并连接结果。

UNION查询实际上更有效率,因为它使用Merge Join来连接结果,并且记录来自自然排序的合并连接,因此{{1}不需要排序。

答案 1 :(得分:1)

您必须直接从SQL Server处理图表吗?如果您真的关心性能,则应该使用一种专门用于表示和处理图形的数据结构。如果我使用通用数据库后端来查阅图表,那么我用图表完成的大部分工作(以及我做得很多)都是不可行的。

我所使用的最有效的表示之一在我编写的一本编译器书的附录中有所描述:工程编译器,由Keith Cooper和Linda Torczon编写。

答案 2 :(得分:0)

我可以想到三个选项:仅在表格中进行,仅在查询中进行,或创建view。对于表,创建强制对称闭包的triggers(例如,当插入(a,b)时,也插入(b,a);当更新(a,b)到(c,d)时,删除旧的对称保持(b,a)对,然后插入(d,c))。请注意,这可能不起作用,因为一些RDBMS(我不确定SQL Server是否是一个)不允许对触发的触发器的表进行插入/更新。

在查询中,

SELECT CASE FromNodeID WHEN 3 THEN ToNodeId ELSE FromNodeId END
  FROM #Edges 
    WHERE FromNodeID=3 OR ToNodeID=3

对于视图,创建一个原始表的对称闭包。我认为你仍然需要使用UNION,但它可以简化查询编写。

CREATE VIEW undirected_edges (FirstEdgeID,SecondEdgeId)
  AS (SELECT FromNodeID, ToNodeID FROM #Edges)
  UNION DISTINCT
    (SELECT ToNodeID, FromNodeID FROM #Edges)