问题
I am working with some tables that represent a file system, and I need to select the full path of each folder as a flattened string.
The first table lists the details of each folder:
CREATE TABLE Folders(
FolderID int IDENTITY(1,1) NOT NULL,
[Name] nvarchar(255) NOT NULL)
The second table lists transitive closures of folder relationships:
CREATE TABLE FolderClosures(
FolderClosuresID int IDENTITY(1,1) NOT NULL,
AncestorFolderID int NOT NULL, --Foreign key to Folders.FolderID
DescendantFolderID int NOT NULL --Foreign key to Folders.FolderID
IsDirect bit NOT NULL)
For sample data, let's assume the following folders exist:
Documents/
Documents/Finance/
Documents/HumanResources/
Documents/HumanResources/Training/
These would be persisted in those tables as follows:
| FolderID | Name |
+----------+----------------+
| 1 | Documents |
| 2 | Finance |
| 3 | HumanResources |
| 4 | Training |
| FolderClosureID | AncestorFolderID | DescendantFolderID | IsDirect |
+-----------------+------------------+--------------------+----------+
| 1 | 1 | 1 | 0 |
| 2 | 2 | 2 | 0 |
| 3 | 1 | 2 | 1 |
| 4 | 3 | 3 | 0 |
| 5 | 1 | 3 | 1 |
| 6 | 4 | 4 | 0 |
| 7 | 1 | 4 | 0 |
| 8 | 3 | 4 | 1 |
Some details to note:
Every folder has an "identity row" in
FolderClosures
, whereAncestorFolderID = DescendantFolderID AND IsDirect = 0
.Every folder that is not a top-level folder has exactly one row in
FolderClosures
whereIsDirect = 1
FolderClosures
can contain many rows per folder, whereAncestorFolderID <> DescendantFolderID AND IsDirect = 0
. Each of these represents a "grandparent" or more distant relationship.Since no columns are nullable, no rows explicitly state that a given folder is a top-level folder. This can only be discerned by checking that there are no rows in
FolderClosures
whereIsDirect = 1 AND DescendantFolderID = SomeID
whereSomeID
is the ID of the folder in question.
I want to be able to run a query that returns this data:
| FolderID | Path |
+----------+------------------------------------+
| 1 | Documents/ |
| 2 | Documents/Finance/ |
| 3 | Documents/HumanResources/ |
| 4 | Documents/HumanResources/Training/ |
Folders may be nested at unlimited depth, but realistically probably only up to 10 levels. Queries may require returning paths for a few thousand folders.
I've found a lot of advice on creating this type of query when data is persisted as an adjacency list, but I haven't been able to find an answer for a transitive closure setup like this. The adjacency list solutions I've found rely on rows being persisted with nullable parent folder IDs, but that doesn't work here.
How can I get the desired output?
If it helps, I am using SQL Server 2016.
回答1:
One way to get desired output is to do a recursive query. For this, I think the best is to only use the rows that have IsDirect = 1
and use the anchor as all folders that don't have direct parent in FolderClosures
, which should be all your root folders.
WITH FoldersCTE AS (
SELECT F.FolderID, CAST(F.Name as NVARCHAR(max)) Path
FROM Folders F
WHERE NOT EXISTS (
SELECT 1 FROM FolderClosures FC WHERE FC.IsDirect = 1 AND FC.DescendantFolderID = F.FolderID
)
UNION ALL
SELECT F.FolderID, CONCAT(PF.Path, '\', F.Name)
FROM FoldersCTE PF
INNER JOIN FolderClosures FC
ON FC.AncestorFolderID = PF.FolderId
AND FC.IsDirect = 1
INNER JOIN Folders F
ON F.FolderID = FC.DescendantFolderID
)
SELECT *
FROM FoldersCTE
OPTION (MAXRECURSION 1000) --> how many nested levels you think you will have
This produces:
FolderID Path
1 Documents
2 Documents\Finance
3 Documents\HumanResources
4 Documents\HumanResources\Training
Hope it helps.
来源:https://stackoverflow.com/questions/45717930/sql-convert-non-null-adjacency-list-to-path