在T-SQL中排序查询结果的更高级逻辑?

在T-SQL中排序查询结果的更高级逻辑?,sql,sql-server,tsql,sql-server-2000,Sql,Sql Server,Tsql,Sql Server 2000,我目前正在编写一个SQL查询,该查询应该显示建筑物内部区域的树状视图,其中包含区域、子区域等。不幸的是,我无法模拟我们的一些软件工具使用的顺序。我仅限于MS SQL 2000,因此顺序问题变得更加复杂,此时我简直不知所措 排序逻辑是子列和父列是相关的。如果第一行的子列的值与第二行的父列匹配,则第二行在第一行之后 --How it currently returns data Child Level Parent 562 Campus 0 86 Area 1

我目前正在编写一个SQL查询,该查询应该显示建筑物内部区域的树状视图,其中包含区域、子区域等。不幸的是,我无法模拟我们的一些软件工具使用的顺序。我仅限于MS SQL 2000,因此顺序问题变得更加复杂,此时我简直不知所措

排序逻辑是子列和父列是相关的。如果第一行的子列的值与第二行的父列匹配,则第二行在第一行之后

--How it currently returns data
Child Level      Parent
562   Campus      0
86  Area        1
87  Area        1
88  Area        1
90  Sub-Area    86
91  Sub-Area    86
92  Sub-Area    87
93  Sub-Area    87
94  Sub-Area    88
95  Sub-Area    88
3    Unit        90
16    Unit      90
4    Unit        91
6    Unit        91
etc, so on and therefore

--How I want it to return the data
Child Level         Parent
562 Campus          0
1   Building        562
86  Area            1
90  Sub-Area        86
91  Sub-Area        86
87  Area            1
95  Sub-Area        87   
95  Sub-Area        87 
为了使这种逻辑正确工作,它需要执行以下操作

  • 返回包含父代码和子代码的建筑行
  • 将面积父代码与建筑子代码匹配,然后在相应的建筑行下插入面积行
  • 将子区域父代码与区域子代码匹配,然后在相应区域下插入子区域行
  • 将单位父代码与子区域子代码匹配,然后在相应子区域下插入单位行
  • 如果SQL真的可以做到这一点

    我很想知道这是否是因为我犹豫是否要在这上面投入更多的时间,除非我知道这实际上是一种可能性。我意识到我可以为ORDER BY语句编写一个带有自定义映射的CASE语句,但这对任何其他校园都不起作用(父/子代码是不同的),我希望将来能够以最少的自定义来重用此代码

    谢谢

    编辑:根据请求添加查询

    DECLARE
    @BuildingType   int,
    @CampusType int
    
    SET @BuildingType= 4
    SET @CampusType= 1
    
    select 
    
    b.fkabc_building_child,
    (select isnull(c.collectionname, 'none') 
    from abc_collections c
    where c.pkabc_collections = b.fkabc_building_child) as 'Child Collection', 
    l.floorname,
    isnull(b.fkabc_collections_parent,0) as fkabc_collections_parent,
    b.fkabc_floorbreakdowns
    
    from abc_breakdowns r
    left join abc_floorbreakdowns fr 
    on fr.pkabc_floorbreakdowns = b.fkabc_floorbreakdowns
    inner join abc_buildingtypescampustypes btct
    on btct.pkabc_buildingtypescampustypes = fr.fkabc_buildingtypescampustypes
    inner join abc_buildingtypes bt
    on btct.fkabc_buildingtypes = bt.pkabc_buildingtypes
    inner join abc_collectiontypes ct
    on btct.fkabc_collectiontypes = ct.pkabc_collectiontypes
    inner join abc_collections c
    on b.fkabc_building_child = c.pkabc_collections
    inner join abc_floors l
    on l.pkabc_floors = c.fkabc_floors
    
    where bt.pkabc_buildingtypes = @BuildingType
    and ct.pkabc_collectiontypes = @CampusType
    
    大概是这样的:

    -- prepare some test data
    declare @table table (Child int, [Level] varchar(30), Parent int)
    
    insert @table values (562 , 'Campus  ',  0  )
    insert @table values (1   , 'Building',  562)
    insert @table values (86  , 'Area    ',  1  )
    insert @table values (87  , 'Area    ',  1  )
    insert @table values (88  , 'Area    ',  1  )
    insert @table values (90  , 'Sub-Area',  86 )
    insert @table values (91  , 'Sub-Area',  86 )
    insert @table values (92  , 'Sub-Area',  87 )
    insert @table values (93  , 'Sub-Area',  87 )
    insert @table values (94  , 'Sub-Area',  88 )
    insert @table values (95  , 'Sub-Area',  88 )
    insert @table values (3   , 'Unit    ',  90 )
    insert @table values (16  , 'Unit    ',  90 )
    insert @table values (4   , 'Unit    ',  91 )
    insert @table values (6   , 'Unit    ',  91 )
    
    select
      a.Child, a.[Level], a.Parent
    , Campus = 
        case a.[Level]
          when 'Unit'     then e.Child
          when 'Sub-Area' then d.Child
          when 'Area'     then c.Child
          when 'Building' then b.Child
          when 'Campus'   then a.Child
        end
    , Building = 
        case a.[Level]
          when 'Unit'     then d.Child
          when 'Sub-Area' then c.Child
          when 'Area'     then b.Child
          when 'Building' then a.Child
        end
    , Area = 
        case a.[Level]
          when 'Unit'     then c.Child
          when 'Sub-Area' then b.Child
          when 'Area'     then a.Child
        end
    , Sub_Area = 
        case a.[Level]
          when 'Unit'     then b.Child
          when 'Sub-Area' then a.Child
        end
    , Unit = 
        case a.[Level]
          when 'Unit'     then a.Child
        end
    
    from @table a
    
    left join @table b on a.Parent = b.Child 
      and ((a.[Level] = 'Unit'     and b.[Level] = 'Sub-Area')
        or (a.[Level] = 'Sub-Area' and b.[Level] = 'Area'    )
        or (a.[Level] = 'Area'     and b.[Level] = 'Building')
        or (a.[Level] = 'Building' and b.[Level] = 'Campus'  ))
    
    left join @table c on b.Parent = c.Child 
      and ((b.[Level] = 'Sub-Area' and c.[Level] = 'Area'    )
        or (b.[Level] = 'Area'     and c.[Level] = 'Building')
        or (b.[Level] = 'Building' and c.[Level] = 'Campus'  ))
    
    left join @table d on c.Parent = d.Child 
      and ((c.[Level] = 'Area'     and d.[Level] = 'Building')
        or (c.[Level] = 'Building' and d.[Level] = 'Campus'  ))
    
    left join @table e on d.Parent = e.Child 
      and ((d.[Level] = 'Building' and e.[Level] = 'Campus'  ))
    
    order by 
      4, 5, 6, 7, 8
    
    也许有一种更聪明的方法可以做到这一点,减少重复,但现在它暗指我

    现在,这段代码只是为了演示,以说明查询是如何工作的。您不需要在SELECT中有5个排序字段,您可以将它们移动到ORDER BY。你不应该在ORDER BY中使用序号位置

    但是您确实需要4个联接和条件联接逻辑来为每个子级提取父级。您确实需要CASE语句,为每个级别提取排序键

    也许您可以将SELECT语句包装在派生表中,并将ORDER BY移动到外部查询。例如:

    SELECT Child, [Level], Parent
    FROM (
      SELECT ....
      ) a
    ORDER BY Campus, Building, Area, Sub_Area, Unit
    
    大概是这样的:

    -- prepare some test data
    declare @table table (Child int, [Level] varchar(30), Parent int)
    
    insert @table values (562 , 'Campus  ',  0  )
    insert @table values (1   , 'Building',  562)
    insert @table values (86  , 'Area    ',  1  )
    insert @table values (87  , 'Area    ',  1  )
    insert @table values (88  , 'Area    ',  1  )
    insert @table values (90  , 'Sub-Area',  86 )
    insert @table values (91  , 'Sub-Area',  86 )
    insert @table values (92  , 'Sub-Area',  87 )
    insert @table values (93  , 'Sub-Area',  87 )
    insert @table values (94  , 'Sub-Area',  88 )
    insert @table values (95  , 'Sub-Area',  88 )
    insert @table values (3   , 'Unit    ',  90 )
    insert @table values (16  , 'Unit    ',  90 )
    insert @table values (4   , 'Unit    ',  91 )
    insert @table values (6   , 'Unit    ',  91 )
    
    select
      a.Child, a.[Level], a.Parent
    , Campus = 
        case a.[Level]
          when 'Unit'     then e.Child
          when 'Sub-Area' then d.Child
          when 'Area'     then c.Child
          when 'Building' then b.Child
          when 'Campus'   then a.Child
        end
    , Building = 
        case a.[Level]
          when 'Unit'     then d.Child
          when 'Sub-Area' then c.Child
          when 'Area'     then b.Child
          when 'Building' then a.Child
        end
    , Area = 
        case a.[Level]
          when 'Unit'     then c.Child
          when 'Sub-Area' then b.Child
          when 'Area'     then a.Child
        end
    , Sub_Area = 
        case a.[Level]
          when 'Unit'     then b.Child
          when 'Sub-Area' then a.Child
        end
    , Unit = 
        case a.[Level]
          when 'Unit'     then a.Child
        end
    
    from @table a
    
    left join @table b on a.Parent = b.Child 
      and ((a.[Level] = 'Unit'     and b.[Level] = 'Sub-Area')
        or (a.[Level] = 'Sub-Area' and b.[Level] = 'Area'    )
        or (a.[Level] = 'Area'     and b.[Level] = 'Building')
        or (a.[Level] = 'Building' and b.[Level] = 'Campus'  ))
    
    left join @table c on b.Parent = c.Child 
      and ((b.[Level] = 'Sub-Area' and c.[Level] = 'Area'    )
        or (b.[Level] = 'Area'     and c.[Level] = 'Building')
        or (b.[Level] = 'Building' and c.[Level] = 'Campus'  ))
    
    left join @table d on c.Parent = d.Child 
      and ((c.[Level] = 'Area'     and d.[Level] = 'Building')
        or (c.[Level] = 'Building' and d.[Level] = 'Campus'  ))
    
    left join @table e on d.Parent = e.Child 
      and ((d.[Level] = 'Building' and e.[Level] = 'Campus'  ))
    
    order by 
      4, 5, 6, 7, 8
    
    也许有一种更聪明的方法可以做到这一点,减少重复,但现在它暗指我

    现在,这段代码只是为了演示,以说明查询是如何工作的。您不需要在SELECT中有5个排序字段,您可以将它们移动到ORDER BY。你不应该在ORDER BY中使用序号位置

    但是您确实需要4个联接和条件联接逻辑来为每个子级提取父级。您确实需要CASE语句,为每个级别提取排序键

    也许您可以将SELECT语句包装在派生表中,并将ORDER BY移动到外部查询。例如:

    SELECT Child, [Level], Parent
    FROM (
      SELECT ....
      ) a
    ORDER BY Campus, Building, Area, Sub_Area, Unit
    

    我将不得不花更多的时间研究它,以找出细节。。。但是,如果您使用的是SQLServer2005(或2008),我建议您考虑使用公共表表达式(CommonTableExpression,CTE)。这允许您递归地构建查询;因此,您可以获取一个建筑,然后获取其所有子建筑以添加到列表中。您可能会想出一个编号方案或类似的方案,以便使用CTE以正确的顺序获取条目。

    我将不得不花更多的时间研究它,以找出细节。。。但是,如果您使用的是SQLServer2005(或2008),我建议您考虑使用公共表表达式(CommonTableExpression,CTE)。这允许您递归地构建查询;因此,您可以获取一个建筑,然后获取其所有子建筑以添加到列表中。您可能会想出一个编号方案或类似的方案,以便使用CTE以正确的顺序获取条目。

    这里有一种方法;非常程序化。不幸的是,在SQL Server 2000上,我不认为您能够摆脱游标,除非您使用像Peter这样的解决方案,该解决方案限制为5个级别,并将级别类型硬编码到查询本身中(混合数据和元数据)。您必须将这些限制与任何可观察到的性能差异进行权衡

    请注意,我没有为循环引用添加任何处理,所以希望您能以其他方式防止这种情况发生

    SET NOCOUNT ON;
    GO
    
    DECLARE @foo TABLE
    (
     AreaID INT PRIMARY KEY,
     [Level] SYSNAME, 
     ParentAreaID INT
    );
    
    INSERT @foo 
    SELECT           562, 'Campus',   0
    UNION ALL SELECT 86,  'Area',     1
    UNION ALL SELECT 87,  'Area',     1
    UNION ALL SELECT 88,  'Area',     1
    UNION ALL SELECT 90,  'Sub-Area', 86
    UNION ALL SELECT 91,  'Sub-Area', 86
    UNION ALL SELECT 92,  'Sub-Area', 87
    UNION ALL SELECT 93,  'Sub-Area', 87
    UNION ALL SELECT 94,  'Sub-Area', 88
    UNION ALL SELECT 95,  'Sub-Area', 88
    UNION ALL SELECT 3,   'Unit',     90
    UNION ALL SELECT 16,  'Unit',     90
    UNION ALL SELECT 4,   'Unit',     91
    UNION ALL SELECT 6,   'Unit',     91
    UNION ALL SELECT 1,   'Building', 562;
    
    DECLARE @nest TABLE
    (
     NestID INT IDENTITY(1,1) PRIMARY KEY,
     AreaID INT,
     [Level] INT,
     ParentNestID INT,
     AreaIDPath VARCHAR(4000)
    );
    
    DECLARE @rc INT, @l INT;
    
    SET @l = 0;
    
    INSERT @nest(AreaID, [Level], AreaIDPath) 
     SELECT AreaID, 0, CONVERT(VARCHAR(12), AreaID)
     FROM @foo
     WHERE ParentAreaID = 0;
    
    SELECT @rc = @@ROWCOUNT;
    
    WHILE @rc >= 1
    BEGIN
     SELECT @l = @l + 1;
    
     INSERT @nest(AreaID, [Level], ParentNestID)
      SELECT f.AreaID, @l, n.NestID
       FROM @foo AS f
       INNER JOIN @nest AS n
       ON f.ParentAreaID = n.AreaID
       AND n.[Level] = @l - 1;
    
     SET @rc = @@ROWCOUNT;
    
     UPDATE n
      SET n.AreaIDPath = COALESCE(n2.AreaIDPath, '') 
       + '\' + CONVERT(VARCHAR(12), n.AreaID) + '\'
         FROM @nest AS n
         INNER JOIN @nest AS n2
         ON n.ParentNestID = n2.NestID
         WHERE n.[Level] = @l
         AND n2.AreaIDPath NOT LIKE '%\' + CONVERT(VARCHAR(12), n.AreaID) + '\%';
    END
    
    SELECT
     structure = REPLICATE(' - ', n.[Level]) + RTRIM(f.AreaID), 
     f.AreaID, f.[Level], f.ParentAreaID 
    FROM @nest AS n
    INNER JOIN @foo AS f
    ON n.AreaID = f.AreaID
    ORDER BY n.AreaIDPath;
    

    这就是SQLServer2005中递归CTE的设计目的。(这本质上仍然是一个游标,但语法比上面的乱七八糟的要干净得多。)在升级到SQL Server 2005之前,如果查询操作过于复杂而无法引入,您可能会更幸运地使用表示层在结果集上循环并适当地排序;非常程序化。不幸的是,在SQL Server 2000上,我不认为您能够摆脱游标,除非您使用像Peter这样的解决方案,该解决方案限制为5个级别,并将级别类型硬编码到查询本身中(混合数据和元数据)。您必须将这些限制与任何可观察到的性能差异进行权衡

    请注意,我没有为循环引用添加任何处理,所以希望您能以其他方式防止这种情况发生

    SET NOCOUNT ON;
    GO
    
    DECLARE @foo TABLE
    (
     AreaID INT PRIMARY KEY,
     [Level] SYSNAME, 
     ParentAreaID INT
    );
    
    INSERT @foo 
    SELECT           562, 'Campus',   0
    UNION ALL SELECT 86,  'Area',     1
    UNION ALL SELECT 87,  'Area',     1
    UNION ALL SELECT 88,  'Area',     1
    UNION ALL SELECT 90,  'Sub-Area', 86
    UNION ALL SELECT 91,  'Sub-Area', 86
    UNION ALL SELECT 92,  'Sub-Area', 87
    UNION ALL SELECT 93,  'Sub-Area', 87
    UNION ALL SELECT 94,  'Sub-Area', 88
    UNION ALL SELECT 95,  'Sub-Area', 88
    UNION ALL SELECT 3,   'Unit',     90
    UNION ALL SELECT 16,  'Unit',     90
    UNION ALL SELECT 4,   'Unit',     91
    UNION ALL SELECT 6,   'Unit',     91
    UNION ALL SELECT 1,   'Building', 562;
    
    DECLARE @nest TABLE
    (
     NestID INT IDENTITY(1,1) PRIMARY KEY,
     AreaID INT,
     [Level] INT,
     ParentNestID INT,
     AreaIDPath VARCHAR(4000)
    );
    
    DECLARE @rc INT, @l INT;
    
    SET @l = 0;
    
    INSERT @nest(AreaID, [Level], AreaIDPath) 
     SELECT AreaID, 0, CONVERT(VARCHAR(12), AreaID)
     FROM @foo
     WHERE ParentAreaID = 0;
    
    SELECT @rc = @@ROWCOUNT;
    
    WHILE @rc >= 1
    BEGIN
     SELECT @l = @l + 1;
    
     INSERT @nest(AreaID, [Level], ParentNestID)
      SELECT f.AreaID, @l, n.NestID
       FROM @foo AS f
       INNER JOIN @nest AS n
       ON f.ParentAreaID = n.AreaID
       AND n.[Level] = @l - 1;
    
     SET @rc = @@ROWCOUNT;
    
     UPDATE n
      SET n.AreaIDPath = COALESCE(n2.AreaIDPath, '') 
       + '\' + CONVERT(VARCHAR(12), n.AreaID) + '\'
         FROM @nest AS n
         INNER JOIN @nest AS n2
         ON n.ParentNestID = n2.NestID
         WHERE n.[Level] = @l
         AND n2.AreaIDPath NOT LIKE '%\' + CONVERT(VARCHAR(12), n.AreaID) + '\%';
    END
    
    SELECT
     structure = REPLICATE(' - ', n.[Level]) + RTRIM(f.AreaID), 
     f.AreaID, f.[Level], f.ParentAreaID 
    FROM @nest AS n
    INNER JOIN @foo AS f
    ON n.AreaID = f.AreaID
    ORDER BY n.AreaIDPath;
    

    这就是SQLServer2005中递归CTE的设计目的。(这本质上仍然是一个游标,但语法比上面的乱七八糟的要干净得多。)在升级到SQL Server 2005之前,只需使用表示层在结果集上循环并适当排序,您可能会有更好的运气,如果这太复杂,无法引入查询操作。

    您可以发布您现在使用的查询吗。如果我们理解您正在使用的模式,这可能会对我们有所帮助。哦,这是一个困难的问题。它需要一个中间表、迭代和一些精细的逻辑。我会帮你解决的,但现在没时间。祝你好运层次结构是否任意深入,或仅限于5个级别?如果限制为5级,则可以在线进行,w/out l