【问题标题】:SQLite: Create Directory Structure Table from A List Of PathsSQLite:从路径列表创建目录结构表
【发布时间】:2021-01-18 05:17:58
【问题描述】:

我想创建一个 目录结构 表,如 question 中所述,其中:

目录 =“主键”ID 字段,通常一个整数
Directory_Parent = "Foreign Key" id 字段,它指向同一张表中另一个 Directory 的 id
Value = 包含目录的字符串/文件夹名称

给定树/水果/苹果/

Directory | Directory_Parent | Value
0           null               Root
1           0                  Tree
2           1                  Fruit
3           2                  Apples

已在主键 0 处创建了一个 Root 文件夹,其父项为空。


我的路径是从 CSV 导入的,目前在一个有 2 列的表中:

 FileID  Path
      1  videos/gopro/father/mov001.mp4
      2  videos/gopro/father/mov002.mp4
      3  pictures/family/father/Oldman.jpg
      4  pictures/family/father/Oldman2.jpg
      5  documents/legal/father/estate/will.doc
      6  documents/legal/father/estate/will2.doc
      7  documents/legal/father/estate/newyork/albany/will.doc
      8  video/gopro/father/newyork/albany/holiday/christmas/2002/mov001.mp4
      9  pictures/family/father/newyork/albany/holiday/christmas/2002/july/Oldman.jpg
      10 pictures/family/father/newyork/albany/holiday/christmas/2002/june/Oldman2.jpg

此表包含 100 万个文件条目。
如前所述,解析这些数据并将文件夹结构移动到新表中的快速优化方法是什么?

在此demo 中,文件夹由“/”分隔并移至新列,如果有帮助的话。

【问题讨论】:

  • 我不认为它会在纯 sql 中工作,如果它工作,它不会被优化。我认为最好用脚本语言来做,这样你就可以利用缓存生成的文件夹的 id。您将需要一个字典来将文件夹映射到 O(1) 中的 id。
  • 您希望文件名也在表中还是仅在目录中?此外,您标记了 MySql 和 SQLite。在我看来,一个适用于两个数据库的解决方案是不可能的。
  • @forpas 理想情况下,文件名被隔离到一个不同的表中,该表仅由 FileID Int 主键、父文件夹 ID(外键)和字符串文件名组成。这个问题的表应该只包含目录数据,所以它很小并且可以构建一个目录树(就像你在资源管理器窗口中看到的那样)。当您单击特定文件夹时,该目录中的文件将从数据库中加载/显示(包含 100 万个文件的表)

标签: database sqlite csv directory structure


【解决方案1】:

SQL 缺乏编程语言的灵活性和工具,这将为这个问题提供快速和优化的解决方案。

此外,在字符串操作方面,SQLite 是数据库中最差的,因为它不支持像 SQL Server 的 STRING_SPLIT() 或 MySql 的 SUBSTRING_INDEX() 这样非常有用的函数。

不过这个问题很有趣,我试了一下。

我用这个语句创建表dir_struct

CREATE TABLE dir_struct (
  Directory INTEGER PRIMARY KEY, 
  Directory_Parent INTEGER REFERENCES dir_struct(Directory), 
  Value TEXT
);

然后我插入'root' 行:

INSERT INTO dir_struct (Directory, Directory_Parent, Value) VALUES (0, null, 'root');

另外,我将OFF 外键强制执行为:

PRAGMA foreign_keys = OFF;

虽然默认是关闭的,以防万一。

首先,您需要一个递归 CTE,它将路径拆分到各个目录(很像您上一个问题的答案)。
然后在第二个 CTE 中,通过条件聚合,每个目录进入自己的列(最多 10 个目录)。
3d CTE 删除重复项,第 4 个 CTE 使用 ROW_NUMBER() 窗口函数为目录分配唯一 ID。
最后通过第 4 次 CTE 的结果的自连接,将行插入到表中:

WITH 
  split AS (
    SELECT 0 idx,
           FileDataID,
           SUBSTR(SUBSTR(Path, 1), 1, INSTR(SUBSTR(Path, 1), '/') - 1) item,
           SUBSTR(SUBSTR(Path, 1), INSTR(SUBSTR(Path, 1), '/') + 1) value
    FROM listfile
    UNION ALL
    SELECT idx + 1,
           FileDataID,
           SUBSTR(value, 1, INSTR(value, '/') - 1),
           SUBSTR(value, INSTR(value, '/') + 1)
    FROM split
    WHERE value LIKE '%_/_%' 
  ),
  cols AS (
    SELECT DISTINCT
           MAX(CASE WHEN idx = 0 THEN item END) path0,
           MAX(CASE WHEN idx = 1 THEN item END) path1,
           MAX(CASE WHEN idx = 2 THEN item END) path2,
           MAX(CASE WHEN idx = 3 THEN item END) path3,
           MAX(CASE WHEN idx = 4 THEN item END) path4,
           MAX(CASE WHEN idx = 5 THEN item END) path5,
           MAX(CASE WHEN idx = 6 THEN item END) path6,
           MAX(CASE WHEN idx = 7 THEN item END) path7,
           MAX(CASE WHEN idx = 8 THEN item END) path8,
           MAX(CASE WHEN idx = 9 THEN item END) path9
    FROM split
    GROUP BY FileDataID
  ),
  paths AS (
    SELECT path0, path1, path2, path3, path4, path5, path6, path7, path8, path9 FROM cols UNION
    SELECT path0, path1, path2, path3, path4, path5, path6, path7, path8, null FROM cols UNION
    SELECT path0, path1, path2, path3, path4, path5, path6, path7, null, null FROM cols UNION
    SELECT path0, path1, path2, path3, path4, path5, path6, null, null, null FROM cols UNION
    SELECT path0, path1, path2, path3, path4, path5, null, null, null, null FROM cols UNION
    SELECT path0, path1, path2, path3, path4, null, null, null, null, null FROM cols UNION
    SELECT path0, path1, path2, path3, null, null, null, null, null, null FROM cols UNION
    SELECT path0, path1, path2, null, null, null, null, null, null, null FROM cols UNION
    SELECT path0, path1, null, null, null, null, null, null, null, null FROM cols UNION
    SELECT path0, null, null, null, null, null, null, null, null, null FROM cols
  ), 
  ids AS (
    SELECT *, 
           ROW_NUMBER() OVER (ORDER BY path0, path1, path2, path3, path4, path5, path6, path7, path8, path9) nr,
           COALESCE(path9, path8, path7, path6, path5, path4, path3, path2, path1, path0) last_child,
           path0 || COALESCE('/' || path1, '') ||
                    COALESCE('/' || path2, '') ||
                    COALESCE('/' || path3, '') ||
                    COALESCE('/' || path4, '') ||
                    COALESCE('/' || path5, '') ||
                    COALESCE('/' || path6, '') ||
                    COALESCE('/' || path7, '') ||
                    COALESCE('/' || path8, '') ||
                    COALESCE('/' || path9, '') full_path
    FROM paths       
  )
INSERT INTO dir_struct(Directory, Directory_Parent, Value)
SELECT i1.nr, COALESCE(i2.nr, 0), i1.last_child
FROM ids i1 LEFT JOIN ids i2
ON i1.full_path = i2.full_path || '/' || i1.last_child

在包含 187365 行的测试数据集中,插入行的时间(平均)为 9.5-10 分钟,对于较大的数据集而言,这将更长。

请参阅demo

更有趣的是,更简单的代码,性能更差(但你也可以测试一下):

WITH 
  split AS (
    SELECT Path,
           0 parent_len,
           SUBSTR(SUBSTR(Path, 1), 1, INSTR(SUBSTR(Path, 1), '/') - 1) item,
           SUBSTR(SUBSTR(Path, 1), INSTR(SUBSTR(Path, 1), '/') + 1) value
    FROM listfile
    UNION ALL
    SELECT Path,
           parent_len + LENGTH(item) + 1, 
           SUBSTR(value, 1, INSTR(value, '/') - 1),
           SUBSTR(value, INSTR(value, '/') + 1)
    FROM split
    WHERE value LIKE '%_/_%' 
  ), 
  row_numbers AS (
    SELECT parent_path, item, 
           ROW_NUMBER() OVER (ORDER BY parent_path, item) rn
    FROM (SELECT DISTINCT SUBSTR(Path, 1, parent_len) parent_path, item FROM split)       
  )
INSERT INTO dir_struct(Directory, Directory_Parent, Value)  
SELECT r1.rn, COALESCE(r2.rn, 0) rn_parent, r1.item 
FROM row_numbers r1 LEFT JOIN row_numbers r2
ON r1.parent_path = r2.parent_path || r2.item || '/'

此查询分配给目录的 ID 与第一个解决方案分配的 ID 不同,但它们是正确且唯一的。

这运行(平均)14-15 分钟。
请参阅demo

结论是,如果这是一次性的事情,也许你可以使用它,但我不推荐它作为这个要求的解决方案。

【讨论】:

  • 这超出了 stackoverflow 答案的预期。我学到了很多东西,我很感激你在这里分享了你的时间、知识和专业知识。非常感谢!
  • 这在 10 分钟内完成了 100 万个文件。 forpas 是 SQL 大师!!
  • @forpas 你是如何以分钟为单位记录时间的?你能分享你的测试数据集来复制你的实验吗?
  • @ggordon 我使用 DB Browser for SQLite,它在完成后以毫秒为单位报告每个查询的时间。我的测试数据集包含我硬盘中的所有目录。不,我不能分享它。
  • @forpas 谢谢。我将使用此工具复制不同的数据集。我试图更好地理解为什么您认为更简单的第二种解决方案具有更长的运行时间。是不是因为第一个解决方案中的“最多 10 个目录的限制”?
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2022-06-15
  • 2015-12-06
  • 2019-06-08
  • 2014-03-07
  • 2018-03-16
  • 2022-01-23
相关资源
最近更新 更多