Database SQLite:从路径列表创建目录结构表

Database SQLite:从路径列表创建目录结构表,database,sqlite,csv,directory,structure,Database,Sqlite,Csv,Directory,Structure,我想创建一个目录结构表,如下所述: Directory=主键id字段,通常为整数 Directory\u Parent=外键id字段,指向同一表中另一个目录的id Value=包含目录/文件夹名称的字符串 给定的树/水果/苹果/ 已在主键0处创建父级为空的根文件夹 我的路径正在从CSV导入,当前位于一个包含两列的表中: FileID Path 1 videos/gopro/father/mov001.mp4 2 videos/gopro/father/mov00

我想创建一个目录结构表,如下所述: Directory=主键id字段,通常为整数 Directory\u Parent=外键id字段,指向同一表中另一个目录的id Value=包含目录/文件夹名称的字符串 给定的树/水果/苹果/

已在主键0处创建父级为空的根文件夹

我的路径正在从CSV导入,当前位于一个包含两列的表中:

 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万个文件条目。 如上所述,解析此数据并将文件夹结构移动到新表中的快速优化方法是什么? 在这种情况下,文件夹以/分隔,并移动到一个新列中(如果有帮助的话)。

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

此外,在字符串操作方面,SQLite是最差的数据库,因为它不支持SQL Server或MySql之类的功能,这将非常有用

尽管如此,这个问题还是很有趣,我试了一下

我使用以下语句创建表dir_struct:

CREATE TABLE dir_struct (
  Directory INTEGER PRIMARY KEY, 
  Directory_Parent INTEGER REFERENCES dir_struct(Directory), 
  Value TEXT
);
我插入“根”行:

此外,我还通过以下方式关闭外键强制:

PRAGMA foreign_keys = OFF;
虽然默认情况下它是关闭的,以防万一

首先,您需要一个递归CTE,该CTE将路径拆分为各个目录,这与前面问题的答案非常相似。 然后,在第二个CTE中,通过条件聚合,每个目录都位于其自己的列中,最多限制为10个目录。 3d CTE删除重复项,第四个CTE使用行号窗口功能为目录分配唯一ID。 最后,通过第四次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分钟,这对于较大的数据集来说要长得多

更有趣的是,代码越简单,性能越差,但您也可以对其进行测试:

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分钟。 看


结论是,如果这是一次性的,也许您可以使用它,但我不建议将其作为解决此需求的解决方案。

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

此外,在字符串操作方面,SQLite是最差的数据库,因为它不支持SQL Server或MySql之类的功能,这将非常有用

尽管如此,这个问题还是很有趣,我试了一下

我使用以下语句创建表dir_struct:

CREATE TABLE dir_struct (
  Directory INTEGER PRIMARY KEY, 
  Directory_Parent INTEGER REFERENCES dir_struct(Directory), 
  Value TEXT
);
我插入“根”行:

此外,我还通过以下方式关闭外键强制:

PRAGMA foreign_keys = OFF;
虽然默认情况下它是关闭的,以防万一

首先,您需要一个递归CTE,该CTE将路径拆分为各个目录,这与前面问题的答案非常相似。 然后,在第二个CTE中,通过条件聚合,每个目录都位于其自己的列中,最多限制为10个目录。 3d CTE删除重复项,第四个CTE使用行号窗口功能为目录分配唯一ID。 最后,通过第四次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分钟,这对于较大的数据集来说要长得多

更有趣的是,代码越简单,性能越差,但您也可以对其进行测试:

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分钟。 看


结论是,如果这是一次性的,也许您可以使用它,但我不建议将其作为满足此要求的解决方案。

我认为它在纯sql中不起作用,如果它起作用,将不会得到优化。我认为最好使用脚本语言,这样就可以利用缓存生成文件夹的ID。您需要一个字典将文件夹映射到O1中的ID。您希望文件名也在表中还是仅在目录中?此外,您还标记了MySql和SQLite。在我看来,在两个数据库中都能使用的解决方案是不可能的。@理想情况下,文件名会被隔离到一个不同的表中,该表只包含FileID Int主键、父文件夹ID外键和字符串文件名。这个问题中的表应该只包含目录数据,所以它很小,可以像您在资源管理器窗口中看到的那样构建目录树。当你点击一个特定的文件夹
r、 然后,该目录中的文件将从数据库加载/显示。该表包含100万个文件。我认为它在纯sql中不起作用,如果它起作用,它将不会得到优化。我认为最好使用脚本语言,这样就可以利用缓存生成文件夹的ID。您需要一个字典将文件夹映射到O1中的ID。您希望文件名也在表中还是仅在目录中?此外,您还标记了MySql和SQLite。在我看来,在两个数据库中都能使用的解决方案是不可能的。@理想情况下,文件名会被隔离到一个不同的表中,该表只包含FileID Int主键、父文件夹ID外键和字符串文件名。这个问题中的表应该只包含目录数据,所以它很小,可以像您在资源管理器窗口中看到的那样构建目录树。当您单击某个特定文件夹时,该目录中的文件将从数据库加载/显示,该表包含100万个文件这超出了stackoverflow答案的预期。我学到了很多,我很感激你在这里分享了你的时间、知识和专业知识。非常感谢你!这项工作在10分钟内完成,涉及100万个文件。forpas是SQL大师@福帕斯,你是怎么用分钟记录时间的?您可以共享测试数据集来复制您的实验吗?@ggordon我使用DB Browser for SQLite,它报告每个查询完成后的时间(以毫秒为单位)。我的测试数据集由硬盘中的所有目录组成。不,我不能分享。@forpas谢谢。我将使用此工具使用不同的数据集进行复制。我试图更好地理解为什么您认为更简单的第二个解决方案的运行时间更长。这是因为第一个解决方案中最多有10个目录的限制吗?这超出了stackoverflow答案的预期范围。我学到了很多,我很感激你在这里分享了你的时间、知识和专业知识。非常感谢你!这项工作在10分钟内完成,涉及100万个文件。forpas是SQL大师@福帕斯,你是怎么用分钟记录时间的?您可以共享测试数据集来复制您的实验吗?@ggordon我使用DB Browser for SQLite,它报告每个查询完成后的时间(以毫秒为单位)。我的测试数据集由硬盘中的所有目录组成。不,我不能分享。@forpas谢谢。我将使用此工具使用不同的数据集进行复制。我试图更好地理解为什么您认为更简单的第二个解决方案的运行时间更长。这是因为第一个解决方案中最多有10个目录的限制吗?