在 SQL 服务器中存储不同类型的分层项目的最佳方式

Best Way to Store Hierarchical Items of Different Types in SQL Server

我正在处理的项目有 7 个级别的业务层次结构。 None 个属于同一类型。意思是,这不是组织结构图,所有项目都是某个级别或其他级别的员工。它们是部门、地区、销售副总裁、业务部门等。是的,其中一些可能是员工,但不是全部。

目前,我有自己的 table,每个 child 都有自己的 parent 外键。所以从层次结构的最小部分开始:

业务单位 (table)

ID
Name
AreaManagerID

区域管理器(table)

ID
Name
RegionalManagerID

区域经理(table)

ID
Name
DivisionID

师(table)

ID
Name

还有 3 个 table 混合在一起,但这应该向您展示层次结构的每个级别之间相当简单的 link。每个 child 必须有一个 parent。不会有任何没有 BusinessUnits 的 AreaManager。

仔细阅读 HierarchyID,我不确定它是否对我有帮助。

我知道上面的方法有效,没问题。但我想知道是否有更好的方法 and/or 当我被分配到一个部门并需要找到其中的所有 BU 时,速度会更快。或者甚至被赋予一个区域并需要找到其中的所有 BU。

如果您正在寻找 "get me descendants",HierarchyID 可以非常快速地找到任意深度的后代。如果您要这样做,我会将所有实体放入一个 table 中,并针对不同类型拆分 table。它看起来有点像这样:

CREATE TABLE [dbo].[BusinessEntity] (
    [EntityID] INT IDENTITY NOT NULL PRIMARY KEY,
    [ParentEntityID] INT
        REFERENCES [dbo].[BusinessEntity] ([EntityID]),
    [EntityType] TINYINT NOT NULL,
    [Path] HIERARCHYID
);

CREATE TABLE [dbo].[BusinessUnit] (
    [ID] INT NOT NULL PRIMARY KEY
        REFERENCES [dbo].[BusinessEntity] ([EntityID]),
    [Name] VARCHAR(255) NOT NULL,
    [AreaManagerID] INT NOT NULL
);

CREATE TABLE [dbo].[AreaManager] (
    [ID] INT NOT NULL PRIMARY KEY
        REFERENCES [dbo].[BusinessEntity] ([EntityID]),
    [Name] VARCHAR(255) NOT NULL,
    [RegionalManagerID] INT NOT NULL
);

CREATE TABLE [dbo].[RegionalManager] (
    [ID] INT NOT NULL PRIMARY KEY
        REFERENCES [dbo].[BusinessEntity] ([EntityID]),
    [Name] VARCHAR(255) NOT NULL,
    [DivisionID] INT NOT NULL
);

CREATE TABLE [dbo].[Division] (
    [ID] INT NOT NULL PRIMARY KEY
        REFERENCES [dbo].[BusinessEntity] ([EntityID]),
    [Name] VARCHAR(255)
);

当您要插入您的实际 table 之一(例如 BusinessUnit、RegionalManager 等)时,您首先会在 BusinessEntity 中创建一条记录,然后使用生成的身份值作为标识符插入物。您还需要根据其在层次结构中的关系使 Path 列保持最新。也就是说,假设我在 BusinessEntity 中有以下数据:

SET IDENTITY_INSERT [dbo].[BusinessEntity] ON;
INSERT INTO [dbo].[BusinessEntity]
        ( [EntityID],
          [ParentEntityID] ,
          [EntityType] 
        )
VALUES  
    (1, NULL, 1),
    (2, 1, 2),
    (3, 1, 2),
    (4, 2, 3),
    (5, 3, 3),
    (6, 4, 4),
    (7, 6, 5);

然后我可以使用以下 CTE 生成路径值

WITH cte AS (
    SELECT [be].[EntityID], [be].[ParentEntityID], CAST(CONCAT('/', [be].[EntityID], '/') AS VARCHAR(MAX)) AS [Path] 
    FROM [dbo].[BusinessEntity] AS [be]
    WHERE [be].[ParentEntityID] IS null

    UNION ALL

    SELECT [child].[EntityID], [child].[ParentEntityID], CAST(CONCAT([parent].[Path], child.[EntityID], '/') AS VARCHAR(MAX))
    FROM [dbo].[BusinessEntity] AS [child]
    JOIN [cte] AS [parent]
        ON [child].[ParentEntityID] = [parent].[EntityID]

)
UPDATE [be]
SET [be].[Path] = cte.[Path]
FROM [dbo].[BusinessEntity] AS be
JOIN cte
    ON [be].[EntityID] = [cte].[EntityID]
WHERE [Path] IS NULL;

当然,保持它们是最新的要容易得多。当您插入一个新行时,从父行中获取路径,将您的 ID 添加到它上面,这就是您的路径。更新行的父级有点棘手,但并不可怕。我将把它留作 reader 的练习。但是作为提示,它涉及到HierarchyID数据类型的GetReparentedValue()方法。最后,如果您不信任 Path 中的值(因为它是派生值),您可以将您不信任的任何值设置为 NULL 并重新运行 上述 cte 更新。