我需要一个更可靠的CTE层次结构查询排序

时间:2017-05-17 21:25:57

标签: sql-server tsql

示例表:

CREATE TABLE Fruit (
  ID int identity(1,1) NOT NULL,
  ParentID int NULL,
  Name varchar(255)
);

我想按字母顺序(多个级别深度)对同一个表中的父记录和子记录进行排序:

Apples
--Green
----Just Sour
----Really Sour        
--Red
----Big
----Small
Bananas
--etc.

我试过这个:

;WITH CTE(ID, ParentID, Name, Sort) AS
(
    SELECT 
         ID
        ,ParentID
        ,Name
        ,cast('\' + Name as nvarchar(255)) AS Sort          
    FROM Fruit
    WHERE ParentID IS NULL

    UNION ALL

    SELECT 
         a.ID
        ,a.ParentID
        ,a.Name
        ,cast(b.Sort + '\' + a.Name as nvarchar(255)) AS Sort           
    FROM Fruit a
    INNER JOIN CTE b ON a.ParentID = b.ID           
)
SELECT * FROM CTE Order by Sort

这会产生类似的结果:

\Apples
\Apples\Green
\Apples\Green\Just Sour
\etc.

就在我认为事情好的时候,它是不可靠的。例如,如果一个项目有多个单词。像:

\Apples
\Apples A <-- culprit
\Apples\Green

如果我可以扩展我的问题,我想在结果中显示实际的连字符或其他内容:

Parent
- Child
--Grandchild

我快速做到这一点的方法是在表中添加一个前缀列,其值为-,用于所有记录。然后我可以这样做:

;WITH CTE(ID, ParentID, Name, Sort, Prefix) AS
(
    SELECT 
         ID
        ,ParentID
        ,Name
        ,cast('\' + Name as nvarchar(255)) AS Sort  
        ,Prefix

    FROM Fruit
    WHERE ParentID IS NULL

    UNION ALL

    SELECT 
         a.ID
        ,a.ParentID
        ,a.Name
        ,cast(b.Sort + '\' + a.Name as nvarchar(255)) AS Sort
        ,cast(b.Prefix + a.Prefix as nvarchar(10)) AS Prefix


    FROM Fruit a
    INNER JOIN CTE b ON a.ParentID = b.ID           
)
SELECT * FROM CTE Order by Sort

但这似乎不正确或不是最佳的。

这些分层查询仍让我头疼,所以也许我只是没有看到明显的问题。

2 个答案:

答案 0 :(得分:1)

我猜你想要这个结果

\Apples
\Apples\Green
\Apples A

也许尝试这样的事情:

SELECT * 
FROM CTE 
ORDER BY replace(Sort, ' ', '~')

'~'是ascii 126,您还可以使用excel排序进行检查。

enter image description here

答案 1 :(得分:1)

在这种情况下,我倾向于使用按名称排序的row_number()

示例

Declare @YourTable table (id int,ParentId  int,Name varchar(50))
Insert into @YourTable values 
 ( 1, NULL,'Apples')
,( 2, 1   ,'Green')
,( 3, 2   ,'Just Sour')
,( 4, 2   ,'Really Sour')
,( 5, 1   ,'Red')
,( 6, 5   ,'Big')
,( 7, 5   ,'Small')
,( 8, NULL,'Bananas')

Declare @Top    int         = null      --<<  Sets top of Hier Try 5 
Declare @Nest   varchar(25) = '|-----'  --<<  Optional: Added for readability

;with cteP as (
      Select Seq  = cast(1000+Row_Number() over (Order by Name) as varchar(500))
            ,ID
            ,ParentId 
            ,Lvl=1
            ,Name 
      From   @YourTable 
      Where  IsNull(@Top,-1) = case when @Top is null then isnull(ParentId ,-1) else ID end
      Union  All
      Select Seq  = cast(concat(p.Seq,'.',1000+Row_Number() over (Order by r.Name)) as varchar(500))
            ,r.ID
            ,r.ParentId 
            ,p.Lvl+1
            ,r.Name 
      From   @YourTable r
      Join   cteP p on r.ParentId  = p.ID)
Select A.ID
      ,A.ParentId 
      ,A.Lvl
      ,Name = Replicate(@Nest,A.Lvl-1) + A.Name
      ,Seq  --<< Can be removed
 From cteP A
 Order By Seq

<强>返回

ID  ParentId    Lvl Name                      Seq
1   NULL        1   Apples                    1001
2   1           2   |-----Green               1001.1001
3   2           3   |-----|-----Just Sour     1001.1001.1001
4   2           3   |-----|-----Really Sour   1001.1001.1002
5   1           2   |-----Red                 1001.1002
6   5           3   |-----|-----Big           1001.1002.1001
7   5           3   |-----|-----Small         1001.1002.1002
8   NULL        1   Bananas                   1002