Sql server 在自引用表中选择透视数据

Sql server 在自引用表中选择透视数据,sql-server,Sql Server,我有一张桌子,比如说满是地方。例如: ID Name ParentId -------------------------- 1 UK NULL 2 England 1 3 Bedfordshire 2 4 Bedford 3 5 ShopA 4 6 Hertfordshire 2 7 Stevenage 6 8 ShopB 7 9 ShopsX

我有一张桌子,比如说满是地方。例如:

ID  Name          ParentId
--------------------------
1   UK            NULL
2   England       1
3   Bedfordshire  2
4   Bedford       3
5   ShopA         4   
6   Hertfordshire 2
7   Stevenage     6
8   ShopB         7
9   ShopsX        6
我想运行一个查询,将数据作为层次结构返回

UK | England | Bedfordshire | Bedford   | ShopA
UK | England | Herfordshire | Stevenage | ShopB
UK | England | Herfordshire | ShopsX    | NULL
注意最后一行,我不想这样称呼它:

NULL | UK | England | Herfordshire | ShopsX   
使用如下查询:

 SELECT c.name as cname, b.name as bname, a.name as aname
  FROM table a 
 left JOIN table b
  ON b.Id = a.Parentid
 left join table c
  ON c.Id = b.Parentid
我得到的结果是第一个值为NULL

NULL | UK | England | Herfordshire | ShopsX   

是否可以切换查询轮以使空值以某种方式向右对齐?

假设您的层次结构具有固定数量的级别,您可以使用尽可能多的自联接来检索每个车间的一行:

select country.name,region.name,county.name,town.name,shop.name
from shops country
   inner join shops region on region.ParentID=country.id
   inner join shops county on county.ParentID=region.id
   inner join shops town on town.ParentID=county.id
   inner join shops shop on shop.parentid=town.id
这将返回:

UK|England|Bedfordshire |Bedford    |ShopA
UK|England|Hertfordshire|Stevenage  |ShopB
要获取没有商店的类别
ShopsX
,您需要将最后一个连接更改为左连接,并检查
国家的空ParentID

select country.name,region.name,county.name,town.name,shop.name
from shops country
inner join shops region on region.ParentID=country.id
inner join shops county on county.ParentID=region.id
inner join shops town on town.ParentID=county.id
left join shops shop on shop.parentid=town.id
where country.parentid is null
返回

UK|England|Bedfordshire |Bedford    |ShopA
UK|England|Hertfordshire|Stevenage  |ShopB
UK|England|Hertfordshire|ShopsX |NULL
如果未检查
Country.ParentID是否为null
,则每个车间生产线都会再次尝试加入该表,未找到匹配项,并返回null:

UK      |   England      |  Bedfordshire |  Bedford |   ShopA
England |   Bedfordshire |  Bedford      |  ShopA   |   NULL
UK      |   England      |  Hertfordshire|  Stevenage|  ShopB
England |   Hertfordshire|  Stevenage    |  ShopB   |   NULL
UK      |   England      |  Hertfordshire|  ShopsX  |   NULL

如果对
ID
ParentID
进行索引,这种“旋转”将执行得非常快

假设您的层次结构具有固定数量的级别,您可以使用尽可能多的自联接来检索每个车间的一行:

select country.name,region.name,county.name,town.name,shop.name
from shops country
   inner join shops region on region.ParentID=country.id
   inner join shops county on county.ParentID=region.id
   inner join shops town on town.ParentID=county.id
   inner join shops shop on shop.parentid=town.id
这将返回:

UK|England|Bedfordshire |Bedford    |ShopA
UK|England|Hertfordshire|Stevenage  |ShopB
要获取没有商店的类别
ShopsX
,您需要将最后一个连接更改为左连接,并检查
国家的空ParentID

select country.name,region.name,county.name,town.name,shop.name
from shops country
inner join shops region on region.ParentID=country.id
inner join shops county on county.ParentID=region.id
inner join shops town on town.ParentID=county.id
left join shops shop on shop.parentid=town.id
where country.parentid is null
返回

UK|England|Bedfordshire |Bedford    |ShopA
UK|England|Hertfordshire|Stevenage  |ShopB
UK|England|Hertfordshire|ShopsX |NULL
如果未检查
Country.ParentID是否为null
,则每个车间生产线都会再次尝试加入该表,未找到匹配项,并返回null:

UK      |   England      |  Bedfordshire |  Bedford |   ShopA
England |   Bedfordshire |  Bedford      |  ShopA   |   NULL
UK      |   England      |  Hertfordshire|  Stevenage|  ShopB
England |   Hertfordshire|  Stevenage    |  ShopB   |   NULL
UK      |   England      |  Hertfordshire|  ShopsX  |   NULL

如果对
ID
ParentID
进行索引,这种“旋转”将执行得非常快

另一个选项是我的标准递归cte的混搭,以构建层次结构。(新增COC或指挥链)

然后我们应用一点xml来解析COC。目前有9个职位,但易于扩展或收缩

Declare @YourTable table (id int,Name varchar(50),ParentId  int)
Insert into @YourTable values 
 (1   ,'UK',            NULL)
,(2   ,'England',       1)
,(3   ,'Bedfordshire',  2)
,(4   ,'Bedford',       3)
,(5   ,'ShopA',         4) 
,(6   ,'Hertfordshire', 2)
,(7   ,'Stevenage',     6)
,(8   ,'ShopB',         7)
,(9   ,'ShopsX',        6)

Declare @Nest   varchar(25) = '|-----'  --<<  Optional: Added for readability

;with cteP as (
      Select Seq  = cast(10000+Row_Number() over (Order by Name) as varchar(500))
            ,ID
            ,ParentId 
            ,Lvl=1
            ,Name 
            ,COC  = cast(Name as varchar(max))
      From   @YourTable 
      Where  ParentId is null
      Union  All
      Select Seq  = cast(concat(p.Seq,'.',10000+Row_Number() over (Order by r.Name)) as varchar(500))
            ,r.ID
            ,r.ParentId 
            ,p.Lvl+1
            ,r.Name 
            ,COC  = p.COC+'||'+cast(r.Name as varchar(max))
      From   @YourTable r
      Join   cteP p on r.ParentId  = p.ID)
     ,cteR1 as (Select *,R1=Row_Number() over (Order By Seq) From cteP)
     ,cteR2 as (Select A.ID,R2=Max(B.R1) From cteR1 A Join cteR1 B on (B.Seq like A.Seq+'%') Group By A.Seq,A.ID )
Select C.*
 From cteR1 A
 Join cteR2 B on A.ID=B.ID
 Cross Apply (
                Select Pos1 = ltrim(rtrim(xDim.value('/x[1]','varchar(max)')))
                      ,Pos2 = ltrim(rtrim(xDim.value('/x[2]','varchar(max)')))
                      ,Pos3 = ltrim(rtrim(xDim.value('/x[3]','varchar(max)')))
                      ,Pos4 = ltrim(rtrim(xDim.value('/x[4]','varchar(max)')))
                      ,Pos5 = ltrim(rtrim(xDim.value('/x[5]','varchar(max)')))
                      ,Pos6 = ltrim(rtrim(xDim.value('/x[6]','varchar(max)')))
                      ,Pos7 = ltrim(rtrim(xDim.value('/x[7]','varchar(max)')))
                      ,Pos8 = ltrim(rtrim(xDim.value('/x[8]','varchar(max)')))
                      ,Pos9 = ltrim(rtrim(xDim.value('/x[9]','varchar(max)')))
                From  (Select Cast('<x>' + replace((Select A.COC as [*] For XML Path('')),'||','</x><x>')+'</x>' as xml) as xDim) as A 
             ) C
 Where R1=R2
 Order By A.R1
结果将是


另一个选项是将我的标准递归cte混搭起来,以构建层次结构。(新增COC或指挥链)

然后我们应用一点xml来解析COC。目前有9个职位,但易于扩展或收缩

Declare @YourTable table (id int,Name varchar(50),ParentId  int)
Insert into @YourTable values 
 (1   ,'UK',            NULL)
,(2   ,'England',       1)
,(3   ,'Bedfordshire',  2)
,(4   ,'Bedford',       3)
,(5   ,'ShopA',         4) 
,(6   ,'Hertfordshire', 2)
,(7   ,'Stevenage',     6)
,(8   ,'ShopB',         7)
,(9   ,'ShopsX',        6)

Declare @Nest   varchar(25) = '|-----'  --<<  Optional: Added for readability

;with cteP as (
      Select Seq  = cast(10000+Row_Number() over (Order by Name) as varchar(500))
            ,ID
            ,ParentId 
            ,Lvl=1
            ,Name 
            ,COC  = cast(Name as varchar(max))
      From   @YourTable 
      Where  ParentId is null
      Union  All
      Select Seq  = cast(concat(p.Seq,'.',10000+Row_Number() over (Order by r.Name)) as varchar(500))
            ,r.ID
            ,r.ParentId 
            ,p.Lvl+1
            ,r.Name 
            ,COC  = p.COC+'||'+cast(r.Name as varchar(max))
      From   @YourTable r
      Join   cteP p on r.ParentId  = p.ID)
     ,cteR1 as (Select *,R1=Row_Number() over (Order By Seq) From cteP)
     ,cteR2 as (Select A.ID,R2=Max(B.R1) From cteR1 A Join cteR1 B on (B.Seq like A.Seq+'%') Group By A.Seq,A.ID )
Select C.*
 From cteR1 A
 Join cteR2 B on A.ID=B.ID
 Cross Apply (
                Select Pos1 = ltrim(rtrim(xDim.value('/x[1]','varchar(max)')))
                      ,Pos2 = ltrim(rtrim(xDim.value('/x[2]','varchar(max)')))
                      ,Pos3 = ltrim(rtrim(xDim.value('/x[3]','varchar(max)')))
                      ,Pos4 = ltrim(rtrim(xDim.value('/x[4]','varchar(max)')))
                      ,Pos5 = ltrim(rtrim(xDim.value('/x[5]','varchar(max)')))
                      ,Pos6 = ltrim(rtrim(xDim.value('/x[6]','varchar(max)')))
                      ,Pos7 = ltrim(rtrim(xDim.value('/x[7]','varchar(max)')))
                      ,Pos8 = ltrim(rtrim(xDim.value('/x[8]','varchar(max)')))
                      ,Pos9 = ltrim(rtrim(xDim.value('/x[9]','varchar(max)')))
                From  (Select Cast('<x>' + replace((Select A.COC as [*] For XML Path('')),'||','</x><x>')+'</x>' as xml) as xDim) as A 
             ) C
 Where R1=R2
 Order By A.R1
结果将是


这不是一个层次结构。这更像是旋转,而不是其他任何东西。如果知道级别的数量是固定的,并且使用hierarchyid而不是自引用,则可以使用级别编号来透视,或者编写等效的异常。需要HierarchyID来进行级别计算哦,也许这就是我需要的。我所做的对层次结构正确吗?那不是层次结构。这更像是旋转,而不是其他任何东西。如果知道级别的数量是固定的,并且使用hierarchyid而不是自引用,则可以使用级别编号来透视,或者编写等效的异常。需要HierarchyID来进行级别计算哦,也许这就是我需要的。我所做的对于层次结构是否正确?成本是多少?此外,如果您需要高级层次结构处理,最好花点精力添加一个层次结构ID。即使如此,在这种情况下(0.01658成本)可能也无法击败自连接@PanagiotisKanavos,正如我提到的“另一个选项”。毫无疑问,自联接是非常有效的。但是,这里的键是范围键(R1/R2)。它们促进了非递归聚合和导航。成本是多少?此外,如果您需要高级层次结构处理,最好花点精力添加一个层次结构ID。即使如此,在这种情况下(0.01658成本)可能也无法击败自连接@PanagiotisKanavos,正如我提到的“另一个选项”。毫无疑问,自联接是非常有效的。但是,这里的键是范围键(R1/R2)。它们促进了非递归聚合和导航。