【问题标题】:Retrieving a tree of references with SQL Server使用 SQL Server 检索引用树
【发布时间】:2016-02-05 05:53:07
【问题描述】:

我有一个管理文件的数据库 - 一些文件包含/引用其他文件,我的目标是设计一个可以为我提供给定文档的整个“树”的查询。

例如,结构可能如下所示:

  • 文件 1
    • 文件 2
    • 文件 3
    • 文件 4
    • 文件 5
      • 文件 6
      • 文件 7
      • 文件 8
        • 文件 9
        • 文件 10

等等,其中文件 1 有效地包含了它后面的所有文件

这些在我的数据库中的两个表之间进行细分 - 我们称它们为“文件”表和“参考”表

“文件”表包含有关文件本身的信息 - 文件 ID、文件名等。

“参考”表使用文件的 FileID 显示上述结构的关系。我的问题是,例如,文件 1 未引用文件 6 - 它仅被文件 5 引用。

例如:

[ParentFileID]  [ChildFileID]

1               2
1               3
1               4
1               5
5               6
5               7
5               8
8               9
8               10

理想情况下,我希望能够检查我传入的任何给定 FileID 在整个结构中的位置

有什么想法吗?我一直在阅读 CTE,感觉某种递归公用表表达式是我所追求的,尽管我能找到的每个示例都使用一个表并涉及 NULL 来追踪顶级元素。

【问题讨论】:

标签: sql sql-server


【解决方案1】:

是的,可以使用递归 CTE 来完成。

USE tempdb
GO
CREATE TABLE files
(
    [file_id] int PRIMARY KEY,
    [file_name] varchar(128) NOT NULL
);
INSERT INTO files VALUES
(1, 'File 1'),
(2, 'File 2'),
(3, 'File 3'),
(4, 'File 4'),
(5, 'File 5'),
(6, 'File 6'),
(7, 'File 7'),
(8, 'File 8'),
(9, 'File 9'),
(10, 'File 10');


CREATE TABLE [references]
(
    parent_file_id int NOT NULL,
    child_file_id int NOT NULL,
    PRIMARY KEY (child_file_id) 
);
INSERT INTO [references] VALUES
(1, 2),
(1, 3),
(1, 4),
(1, 5),
(5, 6),
(5, 7),
(5, 8),
(8, 9),
(8, 10);
GO

CREATE FUNCTION dbo.get_file_with_path(@file_id int)
RETURNS TABLE
AS
RETURN WITH h
AS
(
    SELECT 
        f.file_id, f.file_id as child_file_id, 
        f.file_name, 0 as reverse_level, 
        CAST( '/' + f.file_name as varchar(8000)) as path
    FROM 
        dbo.files f

    WHERE
        f.file_id = @file_id

    UNION ALL

    SELECT 
        h.file_id, r.parent_file_id as child_file_id,
        h.file_name, h.reverse_level + 1 as reverse_level,
        CAST('/' + f.file_name + h.path as varchar(8000)) as path
    FROM
        h 
        INNER JOIN  [references] r
            ON h.child_file_id = r.child_file_id
        INNER JOIN dbo.files f
            ON f.file_id = r.parent_file_id
)
SELECT TOP(1) h.file_id, h.file_name, h.path 
FROM h 
ORDER BY h.reverse_level DESC;
GO

SELECT *
FROM dbo.get_file_with_path(1)
UNION ALL
SELECT *
FROM dbo.get_file_with_path(3)
UNION ALL
SELECT *
FROM dbo.get_file_with_path(6)
UNION ALL
SELECT *
FROM dbo.get_file_with_path(10)

输出:

| file_id | file_name |             path              |
|---------|-----------|-------------------------------|
|       1 | File 1    | /File 1                       |
|       3 | File 3    | /File 1/File 3                |
|       6 | File 6    | /File 1/File 5/File 6         |
|      10 | File 10   | /File 1/File 5/File 8/File 10 |

当你说 position

时,我假设你的意思是 path

编辑:

在cmets中回答问题,还可以创建一个表值函数,返回给定节点下的子树:

CREATE FUNCTION dbo.get_file_subtree_excluding_self(@file_id int)
RETURNS TABLE
AS RETURN
WITH h AS
(
    SELECT r.parent_file_id, r.child_file_id
    FROM [references] r
    WHERE r.parent_file_id = @file_id

    UNION ALL

    SELECT r.parent_file_id, r.child_file_id
    FROM
        h INNER JOIN [references] r
            ON h.child_file_id = r.parent_file_id

)
SELECT h.child_file_id as [file_id]
FROM h
GO

SELECT * FROM dbo.get_file_subtree_excluding_self(5)

输出:

+---------+
| file_id |
+---------+
|       6 |
|       7 |
|       8 |
|       9 |
|      10 |
+---------+

表格references 描述了一个图表。由于主键,一个节点只能有一个父节点,但没有什么能阻止循环。例如,考虑以下数据:

+-------+--------+
| child | parent |
+-------+--------+
|     1 |      2 |
|     2 |      3 |
|     3 |      1 |
+-------+--------+

如您所见,此图上有循环。

【讨论】:

  • 这非常棒。还有一种方法可以发送一个 ID 并获取其中文件的所有 ID 吗?即如果我发送文件 5 的 ID,它将返回 6、7、8、9 和 10?
  • 无论我将限制设置为多少(并允许无限循环无限期运行),编辑中的函数似乎总是在语句完成之前达到最大递归限制
  • 我现在注意到有些确实有效,所以我假设数据库中存在某种循环引用问题
  • 是的,有循环引用。注意引用表的DDL,它有child_file_id作为主键,这样可以防止循环引用。没有那个主键,你就没有树,你有一个图表。
  • 没有那个主键,一个文件可以有多个父级。这个结构是一个图,它可能是一个 DAG(有向无环图)或者它可能是一个循环图,即可能发生循环引用。
猜你喜欢
  • 2017-10-24
  • 2011-05-10
  • 1970-01-01
  • 1970-01-01
  • 2011-03-28
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多