在 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 更新。
我正在处理的项目有 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 更新。