Home > OS >  SQLite recursive query, to collect filepath from filenames
SQLite recursive query, to collect filepath from filenames

Time:01-12

I have a SQLite table, below is its structure

TABLE file(
    "ID" STRING PRIMARY KEY,
    "filename" string NOT NULL,
    "parent" string,
    "is_folder" BOOLEAN NOT NULL,
    FOREIGN KEY (parent) REFERENCES file(ID)
)

I need to create a recursive query in SQLite to get a list of names, to get the path to the file, an example of implementation through simple queries in Python:

    async def get_file_path(self, ID: str):
        # bad example
        cursor = await self.db.execute(
            'SELECT * FROM file WHERE ID = ?', 
            (ID, )
        )
        file = await cursor.fetchone()
        print(file)
        names = [file[1]]
        while file[2]:  # while file has parent
            cursor = await self.db.execute(
                'SELECT * FROM file WHERE ID = ?',
                (file[2], )
            )
            file = await cursor.fetchone()
            names.append(file[1])
        return '/'.join(names[::-1])

This will work, but if the file is deep in file-tree, it will have to make many consecutive queries to the database, which will be slow. I found, that SQLite supports 'WITH RECURSIVE' construction, but I can't figure out how to correctly compose my query in SQLite.

There are many places in my project where I could apply recursive queries, so I want to figure out how to do it.

[UPDATE] Data sample:

INSERT INTO file (ID, filename, parent, is_folder) VALUES ('ID1', 'folder_one', NULL, true); 
INSERT INTO file (ID, filename, parent, is_folder) VALUES ('ID2', 'folder_in_folder_one', 'ID1', true); 
INSERT INTO file (ID, filename, parent, is_folder) VALUES ('ID3', 'another_folder_in_first', 'ID1', true); 
INSERT INTO file (ID, filename, parent, is_folder) VALUES ('ID4', 'deep_file', 'ID2', false); 

The output for "deep_file" (which has "ID4") must be:

["deep_file", "folder_in_folder_one", "folder_one"] 

Then I can get filepath of "deep_file". In this example file with "ID3" is ignored because it's not parent of "ID4"

CodePudding user response:

Use a recursive CTE and the aggregate function json_group_array():

WITH cte AS (
  SELECT * FROM file WHERE ID = ?
  UNION ALL
  SELECT f.*
  FROM file f INNER JOIN cte c
  ON c.parent = f.ID
)
SELECT json_group_array(filename) AS json_names FROM cte;

See the demo.

Although the above query (in every test I did) always aggregates the filenames in the same expected order, this is not guaranteed and it is not mentioned in the documentation.
Just to be on the safe side, another way to do it, which concatenates the filenames one-by-one in the expected order:

WITH cte AS (
  SELECT *, json_array(filename) AS json_names FROM file WHERE ID = 'ID4'
  UNION ALL
  SELECT f.*, json_insert(json_names, '$[#]', f.filename)
  FROM file f INNER JOIN cte c
  ON c.parent = f.ID
)
SELECT json_names FROM cte ORDER BY LENGTH(json_names) DESC LIMIT 1;

See the demo.

  • Related