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

时间:2017-02-01 12:14:37

标签: 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        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   

是否可以将查询切换为圆形,以便NULL以某种方式向右对齐?

2 个答案:

答案 0 :(得分:1)

假设您的层次结构具有固定数量的级别,您可以使用与每个商店检索一行的级别一样多的自连接:

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这个没有商店的类别,您需要将最后一个连接更改为左连接检查country的空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 is null,则每个Shop行都会尝试再次加入表格,找不到匹配项,仍然返回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

如果IDParentID被编入索引,则此“旋转”将执行得非常快。

答案 1 :(得分:1)

另一种选择是建立层次结构的标准递归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

<强>返回

enter image description here

如果它有助于可视化。

最终选择是:

Select A.R1  
      ,B.R2
      ,A.ID
      ,A.ParentId 
      ,A.Lvl
      ,Name = Replicate(@Nest,A.Lvl-1) + A.Name
 From cteR1 A
 Join cteR2 B on A.ID=B.ID
 Order by R1

结果将是

enter image description here