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

时间:2009-10-28 15:41:56

标签: sql sql-server tsql sql-server-2000

我目前正在编写一个SQL查询,该查询应该显示建筑物内部区域,子区域等区域的树视图。不幸的是,我无法模仿某些软件工具使用的排序。我被限制在MS SQL 2000中,因此顺序问题变得更加复杂,我现在只是在我的脑海中。

排序逻辑是Child列和Parent列是相关的。如果第1行的Child列的值与第2行的Parent列匹配,则第2行在第1行的父列之后。

--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 

为了使这个逻辑正常工作,需要做类似

的事情
  1. 使用父代码和子代码返回构建行
  2. 匹配区域父代码到建筑物子代码,然后在相应的建筑行下插入区域行。
  3. 将子区域父代码与区域子代码匹配,然后在相应区域下插入子区域行。
  4. 将单位父代码与子区域子代码匹配,然后在适当的子区域下插入单位行
  5. 如果这实际上可以用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
    

3 个答案:

答案 0 :(得分:2)

这样的事情:

-- 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

答案 1 :(得分:1)

这是一种方法;非常程序化的。不幸的是,在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;

这实际上是SQL Server 2005中的递归CTE的设计目的。 (这实际上仍然是一个游标,但语法比上面的混乱要清晰得多。)在你可以升级到SQL Server 2005之前,你可能只需使用表示层循环结果集并适当地命令就可以获得更好的运气,如果这太复杂了,无法引入您的查询操作。

答案 2 :(得分:0)

我需要花费更多的时间来研究它以找出细节......但是,如果您使用的是SQL Server 2005(或2008),我建议您考虑使用公用表表达式(CTE)。这使您可以递归地构建查询;所以你可以获取一个建筑物,然后获取它的所有子项以添加到列表中。您可以使用CTE提出编号方案等,以便以正确的顺序获取条目。