从字符串中提取层次结构
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,包含列 key
、value
和 type
,如果是 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, '/');
你可以使用 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
我有一个包含两列的 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,包含列 key
、value
和 type
,如果是 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, '/');
你可以使用 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