文件系统中目录路径的分层/树数据库

Posted

技术标签:

【中文标题】文件系统中目录路径的分层/树数据库【英文标题】:Hierarchical/tree database for directories path in filesystem 【发布时间】:2011-10-11 18:22:37 【问题描述】:

我想将目录(存在于磁盘上)存储到数据库中,维护它们的层次结构/树结构。

这是一张图:

(根) / \ 目录 2 目录 3 / \ \ 目录 4 目录 5 目录 6 / 目录7

我正在使用 SQLite 数据库。

请给我建议:

    将上述结构存储在 SQLite 数据库中的 SQL 查询,以及

    当我选择一个查询时检索目录的完整路径。

    即假设我选择Dir7,那么我应该得到像ROOT/Dir2/Dir4/Dir7这样的完整路径

【问题讨论】:

闭包表可能是一个不错的选择。看看What is the most efficient/elegant way to parse a flat table into a tree?,也看看Bill Karwin's幻灯片Models for hierarchical data 该主题再次出现在 SQLite mainling 列表中,Eduardo Morras(重新)指出了这个 SQLite vtable 扩展来处理 SQLite 存储库本身的层次结构:1)sqlite.org/src/artifact/…2)@987654325 @ 【参考方案1】:

这是 SQLite 的快速关闭表示例。我没有包含将项目插入现有树的语句。相反,我刚刚手动创建了语句。您可以在Models for hierarchical data 幻灯片中找到插入和删除语句。

为了在插入目录 ID 时保持理智,我重命名了目录以匹配它们的 ID:

        (ROOT)
      /        \ 
    Dir2        Dir3
    /    \           \
  Dir4   Dir5        Dir6
  /          
Dir7

创建表格

CREATE TABLE `filesystem` (
  `id` INTEGER,
  `dirname` TEXT,
  PRIMARY KEY (`id`)
);

CREATE TABLE `tree_path` (
  `ancestor` INTEGER,
  `descendant` INTEGER,
  PRIMARY KEY (`ancestor`, `descendant`)
);

将目录插入filesystem 表中

INSERT INTO filesystem (id, dirname) VALUES (1, 'ROOT');
INSERT INTO filesystem (id, dirname) VALUES (2, 'Dir2');
INSERT INTO filesystem (id, dirname) VALUES (3, 'Dir3');
INSERT INTO filesystem (id, dirname) VALUES (4, 'Dir4');
INSERT INTO filesystem (id, dirname) VALUES (5, 'Dir5');
INSERT INTO filesystem (id, dirname) VALUES (6, 'Dir6');
INSERT INTO filesystem (id, dirname) VALUES (7, 'Dir7');

创建闭包表路径

INSERT INTO tree_path (ancestor, descendant) VALUES (1, 1);
INSERT INTO tree_path (ancestor, descendant) VALUES (1, 2);
INSERT INTO tree_path (ancestor, descendant) VALUES (1, 3);
INSERT INTO tree_path (ancestor, descendant) VALUES (1, 4);
INSERT INTO tree_path (ancestor, descendant) VALUES (1, 5);
INSERT INTO tree_path (ancestor, descendant) VALUES (1, 6);
INSERT INTO tree_path (ancestor, descendant) VALUES (1, 7);
INSERT INTO tree_path (ancestor, descendant) VALUES (2, 2);
INSERT INTO tree_path (ancestor, descendant) VALUES (2, 4);
INSERT INTO tree_path (ancestor, descendant) VALUES (2, 5);
INSERT INTO tree_path (ancestor, descendant) VALUES (2, 7);
INSERT INTO tree_path (ancestor, descendant) VALUES (3, 3);
INSERT INTO tree_path (ancestor, descendant) VALUES (3, 6);
INSERT INTO tree_path (ancestor, descendant) VALUES (4, 4);
INSERT INTO tree_path (ancestor, descendant) VALUES (4, 7);
INSERT INTO tree_path (ancestor, descendant) VALUES (5, 5);
INSERT INTO tree_path (ancestor, descendant) VALUES (6, 6);
INSERT INTO tree_path (ancestor, descendant) VALUES (7, 7);

运行一些查询

# (ROOT) and subdirectories
SELECT f.id, f.dirname FROM filesystem f
  JOIN tree_path t
    ON t.descendant = f.id
 WHERE t.ancestor = 1;

+----+---------+
| id | dirname |
+----+---------+
|  1 | ROOT    |
|  2 | Dir2    |
|  3 | Dir3    |
|  4 | Dir4    |
|  5 | Dir5    |
|  6 | Dir6    |
|  7 | Dir7    |
+----+---------+


# Dir3 and subdirectories
SELECT f.id, f.dirname
  FROM filesystem f
  JOIN tree_path t
    ON t.descendant = f.id
 WHERE t.ancestor = 3;

+----+---------+
| id | dirname |
+----+---------+
|  3 | Dir3    |
|  6 | Dir6    |
+----+---------+

# Dir5 and parent directories
SELECT f.id, f.dirname
  FROM filesystem f
  JOIN tree_path t
    ON t.ancestor = f.id
 WHERE t.descendant = 5;

+----+---------+
| id | dirname |
+----+---------+
|  1 | ROOT    |
|  2 | Dir2    |
|  5 | Dir5    |
+----+---------+

# Dir7 and parent directories
SELECT f.id, f.dirname
  FROM filesystem f
  JOIN tree_path t
    ON t.ancestor = f.id
 WHERE t.descendant = 7;

+----+---------+
| id | dirname |
+----+---------+
|  1 | ROOT    |
|  2 | Dir2    |
|  4 | Dir4    |
|  7 | Dir7    |
+----+---------+

SELECT f.id, f.dirname
  FROM filesystem f
  JOIN tree_path t
    ON t.ancestor = f.id
 WHERE t.descendant = (
SELECT id
  FROM filesystem
 WHERE dirname LIKE '%7%'
);

+----+---------+
| id | dirname |
+----+---------+
|  1 | ROOT    |
|  2 | Dir2    |
|  4 | Dir4    |
|  7 | Dir7    |
+----+---------+

【讨论】:

@JOHN:对不起,我从来没有用过 orientDB,所以无法评论。 @JOHN:我不确定你的意思 - 请你举个例子吗? 我首先阅读了这些查询并一直想知道,“它是如何工作的?”我意识到您的tree_path 表是目录图的传递闭包 的邻接表表示,回答了以下问题:“这条路径是这条路径的可达后代吗?”和“这条路是这条路的可到达祖先吗?”不错。 @seh:是的,这是一个非常好的技术。虽然它比简单的邻接列表需要更多的条目,但选择数据的查询要简单得多。不过我不能把功劳。如果您想了解更多信息,请查看我在问题下方的 cmets 中发布的 Bill Karwin 链接。请注意,您还可以添加路径长度,这样可以更轻松地查找直接祖先/后代。 您的tree_path 表不应该包含“嵌套级别”或“深度”字段以使其成为闭合表吗?【参考方案2】:

我认为你应该阅读一个名为 Modified Preorder Tree Traversal 的方法:http://www.sitepoint.com/hierarchical-data-database/

该链接讨论了将分层数据存储到关系数据库中的两种方法:邻接表模型和改进的前序树遍历算法。

Modified Preorder Tree Traversal方法的主要思想是用指针注释所有节点以辅助导航和子树选择:

【讨论】:

【参考方案3】:

您将分层数据表示为一系列节点,每个节点都有一个 ID 和一个父 ID。 您可以将您的存储在一个名为 DIRTAB 的表中,其中包含 2 个 ID 列和一个用于单个目录名称的文本:

ID -- as a primary key  
PARENT_ID -- refers to the ID of the parent row in DIRTAB  
DIRNAME -- the text of the name eg Dir5  

SQLite 缺少 Oracle 必须处理分层数据的 CONNECT BY 子句,但我认为如果您准备接受一些丑陋的 SQL,您可以近似分层:

SELECT (CASE WHEN p5.DIRNAME IS NOT NULL THEN p5.DIRNAME || '/' ELSE '' END) ||
       (CASE WHEN p4.DIRNAME IS NOT NULL THEN p4.DIRNAME || '/' ELSE '' END) ||
       (CASE WHEN p3.DIRNAME IS NOT NULL THEN p3.DIRNAME || '/' ELSE '' END) ||
       (CASE WHEN p2.DIRNAME IS NOT NULL THEN p2.DIRNAME || '/' ELSE '' END) ||
       (CASE WHEN p1.DIRNAME IS NOT NULL THEN p1.DIRNAME || '/' ELSE '' END) ||
       p0.DIRNAME as FULLPATH
FROM DIRTAB p0
     LEFT OUTER JOIN DIRTAB p1 ON p1.ID = p0.PARENT_ID
     LEFT OUTER JOIN DIRTAB p2 ON p2.ID = p1.PARENT_ID
     LEFT OUTER JOIN DIRTAB p3 ON p3.ID = p2.PARENT_ID
     LEFT OUTER JOIN DIRTAB p4 ON p4.ID = p3.PARENT_ID
     LEFT OUTER JOIN DIRTAB p5 ON p5.ID = p4.PARENT_ID
WHERE p0.DIRNAME = 'Dir6'  

这里的问题是您必须预测目录结构的最大深度并扩展 SQL 语句以应对。我已经完成了 6 个级别作为示例。 另外我假设 SQLite 连接空字符串没有问题。 (有些DB把它们当作null,把整个表达式结果都转换成null)

【讨论】:

以上是关于文件系统中目录路径的分层/树数据库的主要内容,如果未能解决你的问题,请参考以下文章

Linux基础——文件系统与目录结构

Linux入门基础认知2

linux运维基础文件管理

linux系统软硬链接与文件管理几个基本命令使用

从路径列表中表示文件系统(文件/目录)的 Java 树

零基础领跑教科书式博文:DAY2---Linux文件管理