层次结构表选择

时间:2016-03-22 01:55:12

标签: sql sql-server sql-server-2014

我有一张桌子可以放置位置,比方说,一个监狱。

CREATE TABLE [dbo].[Location](
    [ID] [int] IDENTITY(1,1) NOT NULL,
    [ParentID] [int] NULL,
    [LocationTypeID] [int] NOT NULL,
    [GeoLocation] [geometry] NULL,
    [Name] [varchar](50) NOT NULL,
    [Description] [varchar](100) NOT NULL
 CONSTRAINT [pk_location] PRIMARY KEY (ID)
)

此处插入数据文件: data.sql file 当ParentID为null时,这是系统的根节点。

在数据中,我们可能有一个监狱(TypeID = 5),一个建筑物(TypeID = 6),一个楼层,一个牢房和一个床。父ID是当前位置所在的位置。

我创建了一个视图,可以轻松访问此表。

CREATE VIEW [dbo].[vwLocations]
AS

 WITH MyLocation 
 AS (
        SELECT  ParentID, 
                ID, 
                Name, 
                0 AS LevelNo, 
                CAST(CAST(LocationTypeID AS VARCHAR) + '|' + Name AS VARCHAR(512)) AS SORT_PATH
        FROM dbo.Location AS A
        WHERE        (ParentID IS NULL)
        UNION ALL
        SELECT  C.ParentID, 
                C.ID, 
                C.Name, 
                MyLocation_1.LevelNo + 1 AS Expr1, 
                CAST(MyLocation_1.SORT_PATH + '//' + CAST(C.LocationTypeID AS VARCHAR) + '|' + C.Name AS VARCHAR(512)) AS SORT_PATH
        FROM  dbo.Location AS C 
        INNER JOIN MyLocation AS MyLocation_1 
            ON MyLocation_1.ID = C.ParentID
    )
    SELECT  ISNULL(ID, -999) AS LocationID, 
            ParentID, 
            LevelNo AS LevelNumber, 
            SORT_PATH AS FullPath, 
            Name AS Description
    FROM   MyLocation AS ML
GO

这对所有情况都很有效,而且效率很高。如果开发人员想要将其分解为每个组件,则会返回一个字符串。例如,我返回一个字符串:

5 |监狱// 6 | P 1 // 7 | P 1 // 8 | 1 // 9 | 01 // 10 | 01

然后他们在'//'上拆分以获得

5 |监狱 6 | P 1 7 | P 1 8 | 1 9 | 01 10 | 01

这意味着,ID 5表示它是一个监狱,ID 6表示它是一个建筑物,ID 7是一个部分,ID 8是一个楼层,9是一个单元,10是一个单元床。

他们在代码中这样做。

问题是,他们希望能够按楼层排序,例如。哪个,我做不到。不知何故,我需要做的是为每一行添加额外的列,称为“监狱”,“建筑物”......等等,并填充它们。

所以有一些可能是null,因为如果位置是一个Floor,那么Prison,Building,section和floor列将有数据,但其余的将为null。

这可能吗?

2 个答案:

答案 0 :(得分:1)

我是HierarchyID数据类型的忠实粉丝。我要切入追逐:

alter table dbo.Location add 
    [Path] hierarchyid null, 
    [Level] as [Path].GetLevel() persisted;

WITH MyLocation AS (
    SELECT  ParentID, 
            ID, 
            Name, 

            cast(concat('/', ID, '/') as varchar(1000)) as [Path]
    FROM dbo.Location AS A
    WHERE        (ParentID IS NULL)
    UNION ALL
    SELECT  C.ParentID, 
            C.ID, 
            C.Name, 
            cast(concat(p.[Path], c.ID, '/') as varchar(1000)) as [Path]
    FROM  dbo.Location AS C 
    INNER JOIN MyLocation AS p 
        ON p.ID = C.ParentID
)
update l
set [Path] = ml.[Path]
from dbo.Location as l
join MyLocation as ml
    on l.ID = ml.ID
GO

我在这里做的是在表格中添加一列HierarchyID。这将允许我将路径一直存储到最终祖先(在您的情况下,监狱)并有效地查询路径上的祖先。

接下来是一个表值函数。

create function dbo.tvf_PivotHierarchy(@id int)
returns table
as return

    select ID,
        [1],
        [2],
        [3],
        [4],
        [5],
        [6]  
    from (
        select p.ID, l.[Description], l.[Level]
        from dbo.Location as l
        join dbo.Location as p
            on p.[Path].IsDescendantOf(l.[Path]) = 1
        where p.ID = @id
    ) as p
    pivot (
        max(Description)
        for [Level] in (
            [1],
            [2],
            [3],
            [4],
            [5],
            [6]
        )
    ) as pvt;
go

样本用法:

with data as (
    select * from (values
        (67),
        (115)
    ) as x(ID)
)
select * 
from data as d
join dbo.Location as l
    on d.ID = l.ID
cross apply dbo.tvf_PivotHierarchy(l.ID);

这里发生的一切就是,对于给定的ID,我发现所有记录都是该记录的祖先,并通过一个简单的数据透镜运行它。

现在,如果你想按特定方式排序,你就有了选择。如果您想以深度优先的方式排序(即监狱,建筑物,该建筑物中的所有楼层,下一座建筑物等),请在最后一个查询上抛出order by [Path];如果你想做广度优先(即监狱等),请order by [Level], [Path]

我正在草率地执行像select *这样的事情,而不是将数据库中的列命名为更有意义的部分因为结果集更能说明发生了什么,部分原因是因为我很懒。如果您选择使用它,您应该将其清理干净以供生产使用。

答案 1 :(得分:0)

这样的任何解决方案的最大问题(并且你不是第一个尝试的)在数据库引擎中没有特殊功能,你不能代表任意深度的树...任何关于级别1将(应该)连接到节点为0的父节点,级别2上的任何节点都应该连接到级别1上的父节点等等。所以它不仅仅是尝试排序在地板上(老实说,在视图选择结束时应该只是一个ORDER BY)但更基本的是,除非你有一个每个可能级别的连接,否则你不能遍历树。

简化为"树不能存储在关系数据库中"