从字符串中提取层次结构

Extract hierarchy from String

我有一个包含两列的 table,一列带有代码,另一列带有路径格式:

 Code   Path
 H      A/B/C/G/H
 D      B/L/P/D
 G      A/B/C/G
 R      J/X/R

我的目标是,对于每个代码,父级列表如下:

Code  Parent  Level
H     A       1
H     B       2
H     C       3
H     G       4
H     H       5
D     B       1
D     L       2
D     P       3
D     D       4
G     A       1
G     B       2
G     C       3
G     G       4
R     J       1
R     X       2
R     R       3

我尝试使用递归代码,但它只显示当前代码的级别。

我怎样才能得到我的结果?

如果您使用 SQL Server 2016+,一种可能的解决方案是使用基于 JSON 的方法进行一次字符串转换。您需要将每一行中的数据转换为有效的 JSON 数组(A/B/C/G/H 转换为 ["A","B","C","G","H"]),然后使用 OPENJSON() 和默认模式解析数据。 OPENJSON() 的结果是一个 table,包含列 keyvaluetype,如果是 JSON 数组,key 列保存 JSON 数组中项目的索引:

请注意,此处不能使用 STRING_SPLIT(),因为无法保证返回行的顺序。

Table:

CREATE TABLE Data (
   [Code] varchar(1),
   [Path] varchar(100)
)
INSERT INTO Data
   ([Code], [Path])
VALUES
   ('H', 'A/B/C/G/H'),
   ('D', 'B/L/P/D'),
   ('G', 'A/B/C/G'),
   ('R', 'J/X/R')

声明:

SELECT 
   d.[Code],
   j.[value] AS Parent,
   (j.[key] + 1) AS Level
FROM Data d
CROSS APPLY OPENJSON(CONCAT('["', REPLACE(d.[Path], '/', '","'), '"]')) j

结果:

Code Parent Level
H    A      1
H    B      2
H    C      3
H    G      4
H    H      5
D    B      1
D    L      2
D    P      3
D    D      4
G    A      1
G    B      2
G    C      3
G    G      4
R    J      1
R    X      2
R    R      3

FOR SQL SERVER 2016 及以上 你可以试试下面使用 STRING_SPLIT.

的代码
SELECT 
    code, 
    value path,
    row_number() over (partition by code order by (SELECT NULL)) Level
FROM 
    mytable
    CROSS APPLY STRING_SPLIT(path, '/');

CHECK DEMO HERE

你可以使用 split by xml + cross apply + row_number group by code order by rowrank 来完成。

SELECT Code,
    LTRIM(RTRIM(m.n.value('.[1]','varchar(8000)'))) AS Parent,
    row_number() over (partition by code order by (select 1)) AS Level
FROM
(
    SELECT Code,CAST('<XMLRoot><RowData>' 
       + REPLACE(Path,'/','</RowData><RowData>') + '</RowData></XMLRoot>' AS XML) AS x
    FROM   T
)t
CROSS APPLY x.nodes('/XMLRoot/RowData')m(n) 

declare @table1 table (Row1# int,Code nvarchar(max),Path nvarchar(max))
declare @table3 table (code nvarchar,parent nvarchar(max),[Level] nvarchar(max))
Declare @count1 int = 1
Declare @count2 int = 1
declare @var nvarchar(max)
declare @var2 nvarchar(max)


 INSERT INTO @table1
 select ROW_NUMBER() OVER(ORDER BY (SELECT NULL)) Row1#,Code,[dbo].[Path].Path  from 
 [dbo].[Path]


select @count2 = count(*) from [dbo].[Path]

WHILE @count1 <= @count2
        BEGIN


             select top 1 @var = path,@var2 = Code  from @table1 
            --select top 1  @var = path from [dbo].[Path] where code = 'H'

            insert into @table3
            select Item,@var2 as parent, ROW_NUMBER() OVER(ORDER BY (SELECT NULL)) as 'level' from dbo.SplitString(@var,'/')


            delete from @table1 where Row1# = @count1

            set @count1 = @count1 + 1
        end


select * from @table3



ALTER FUNCTION [dbo].[SplitString]
(    
      @Input NVARCHAR(MAX),
      @Character CHAR(1)
)
RETURNS @Output TABLE (
      Item NVARCHAR(MAX)
)
AS
BEGIN
      DECLARE @StartIndex INT, @EndIndex INT

      SET @StartIndex = 1
      IF SUBSTRING(@Input, LEN(@Input) - 1, LEN(@Input)) <> @Character
      BEGIN
            SET @Input = @Input + @Character
      END

      WHILE CHARINDEX(@Character, @Input) > 0
      BEGIN
            SET @EndIndex = CHARINDEX(@Character, @Input)

            INSERT INTO @Output(Item)
            SELECT SUBSTRING(@Input, @StartIndex, @EndIndex - 1)

            SET @Input = SUBSTRING(@Input, @EndIndex + 1, LEN(@Input))
      END

      RETURN
END
/*Here you go!!! */

IF OBJECT_ID('TEMPDB..#PATH') IS NOT NULL 
       DROP TABLE #PATH
CREATE TABLE #PATH (PID INT IDENTITY(1,1), CODE NVARCHAR(10), [PATH] NVARCHAR(200))

INSERT INTO #PATH(CODE, [PATH])
SELECT 'H', 'A/B/C/G/H'
UNION
SELECT 'D', 'B/L/P/D'
UNION
SELECT 'G', 'A/B/C/G'
UNION
SELECT 'R', 'J/X/R'


IF OBJECT_ID('TEMPDB..#ExtendedPath') IS NOT NULL 
       DROP TABLE #ExtendedPath
CREATE TABLE #ExtendedPath ( CODE NVARCHAR(10), Parent NVARCHAR(50),  [Level] INT)


DECLARE @StartOutLoop INT = 1
              , @EndOutLoop INT 
              , @StartInLoop INT = 0
              , @EndInLoop INT
              , @Path NVARCHAR(200)

SELECT @EndOutLoop = MAX(PID) FROM #PATH
SELECT * FROM #PATH
WHILE @StartOutLoop <= @EndOutLoop
BEGIN

       SELECT @StartInLoop = 0
       SELECT @Path = [PATH] FROM #PATH WHERE PID = @StartOutLoop 
       SELECT @EndInLoop = LEN([PATH]) - LEN(REPLACE([PATH],'/','')) FROM #PATH WHERE PID = @StartOutLoop 

       WHILE @StartInLoop <= @EndInLoop
       BEGIN
              INSERT INTO #ExtendedPath (CODE, PARENT, [LEVEL])
              SELECT CODE
                      , CASE WHEN CHARINDEX('/',@PATH) >0 then  SUBSTRING(@Path, 0, CHARINDEX('/',@PATH))
                             ELSE @PATH
                             END
              , @StartInLoop+1
              FROM #PATH WHERE PID = @StartOutLoop

              SELECT @Path = SUBSTRING(@Path, CHARINDEX('/',@PATH) + 1,LEN(@PATH))
              --select @Path
              SELECT @StartInLoop += 1
       END

SELECT @StartOutLoop += 1

END
SELECT * FROM #ExtendedPath