差不多九年后,不幸的是,我所知道的没有开箱即用的解决方案。所以我仍在研究xp_dirtree
并需要一个解决方案。
我尝试了 Arion 的答案,发现它正在产生结果。但是,对于超过 11K 对象的大型文件系统,它的运行速度非常慢。我看到它甚至从一开始就非常慢:
UPDATE #dirtree
SET ParentId = (SELECT MAX(Id) FROM #dirtree
WHERE Depth = T1.Depth - 1 AND Id < T1.Id)
FROM #dirtree T1
尽管这不是孤岛和差距问题,但它有一些相似之处,并且对这些问题的思考对我有所帮助。最后的代码是我的存储过程。这些部分对代码的作用有一些评论。
你会像这样使用它:
exec directory
@root = 'c:\somepath',
@depth = 3,
@outputTable = '##results';
select * from ##results;
这导致输出如下:
+---------------------------------+------------+------------+-----------+--------+-----------+----------+
| path | name | nameNoExt | extension | isFile | runtimeId | parentId |
+---------------------------------+------------+------------+-----------+--------+-----------+----------+
| c:\somePath\DataMovers | DataMovers | DataMovers | NULL | 0 | 4854 | NULL |
| c:\somePath\DataMovers\main.ps1 | main.ps1 | main | ps1 | 1 | 4859 | 4854 |
+---------------------------------+------------+------------+-----------+--------+-----------+----------+
我必须以这种方式构建它,因为在内部它需要 xp_dirtree 输出并将其加载到临时表中。由于禁止嵌套的 insert-exec 语句,这阻止了获取 proc 的结果并将它们加载到 proc 之外的表中的能力。不要将 @outputTable 暴露给不受信任的用户,因为它容易受到 sql 注入的影响。当然,重新处理 proc 以避免这种情况,但它可以满足您的需求。
/*
Summary: Lists file directory contents.
Remarks: - stackoverflow.com/q/10298910
- This assumes that the tree is put in order where
subfolders are listed right under their parent
folders. If this changes in the future, a
different logic will need to be implemented.
Example: exec directory 'c:\somepath', 3, '##results';
select * from ##results;
*/
create procedure directory
@root nvarchar(255),
@depth int,
@outputTable sysname
as
-- initializations
if @outputTable is null or not (left(@outputTable,2) = '##') or charindex(' ', @outputTable) > 0
throw 50000, '@outputTable must be a global temp table with no spaces in the name.', 1;
if exists (select 0 from tempdb.information_schema.tables where table_name = @outputTable)
begin
declare @msg nvarchar(255) = '''tempdb.dbo.' + @outputTable + ''' already exists.';
throw 50000, @msg, 1;
end
-- fetch the tree (it doesn't have full path names)
drop table if exists #dir;
create table #dir (
id int identity(1,1),
parentId int null,
path nvarchar(4000),
depth int,
isFile bit,
isLeader int default(0),
groupId int
)
insert #dir (path, depth, isFile)
exec xp_dirtree @root, @depth, 1;
-- identify the group leaders (based on a change in depth)
update d
set isLeader = _isLeader
from (
select id,
isLeader,
_isLeader = iif(depth - lag(depth) over(order by id) = 0, 0, 1)
from #dir
) d;
-- find the parents for each leader (subsetting just for leaders improves efficiency)
update #dir
set parentId = (
select max(sub.id)
from #dir sub
where sub.depth = d.depth - 1
and sub.id < d.id
and d.isLeader = 1
)
from #dir d
where d.isLeader = 1;
-- assign an identifier to each group (groups being objects that are 'siblings' of the leader)
update d
set groupId = _groupId
from (
select *, _groupId = sum(isLeader) over(order by id)
from #dir
) d;
-- set the parent id for each item based on the leader's parent id
update d
set d.parentId = leads.parentId
from #dir d
join #dir leads
on d.groupId = leads.groupId
and leads.parentId is not null;
-- convert the path names to full path names and calculate path parts
drop table if exists #pathBuilderResults;
with pathBuilder as (
select id, parentId, origId = id, path, pseudoDepth = depth
from #dir
union all
select par.id,
par.parentId,
pb.origId,
path = par.path + '\' + pb.path,
pseudoDepth = pb.pseudoDepth - 1
from pathBuilder pb
join #dir par on pb.parentId = par.id
where pb.pseudoDepth >= 2
)
select path = @root + '\' + pb.path,
name = d.path,
nameNoExt = iif(ext.value is null, d.path, left(d.path, len(d.path) - len(ext.value) - 1)),
extension = ext.value,
d.isFile,
runtimeId = pb.origId,
parentId = d.parentId
into #pathBuilderResults
from pathBuilder pb
join #dir d on pb.origId = d.id
cross apply (select value = charindex('.', d.path)) dotPos
cross apply (select value = right(d.path, len(d.path) - dotPos.value)) pseudoExt
cross apply (select value = iif(d.isFile = 1 and dotPos.value > 0, pseudoExt.value, null)) ext
where pb.pseudoDepth = 1
order by pb.origId;
-- terminate
declare @sql nvarchar(max) = 'select * into ' + @outputTable + ' from #pathBuilderResults';
exec (@sql);