在SQL Server中找到顶级父级的最有效方法是什么?

时间:2015-02-28 02:27:09

标签: sql sql-server

给出下表

catName     catID     parentID
=================================
vehicles    1         0
cars        2         1
sedans      3         2
animals     4         0
cows        5         4

鉴于catID,我需要找到它的顶级父级(parentID = 0)。

此查询每天执行50-100次。目前有100-200行(将来可能更多)。最多8级。我在考虑三种选择:

  1. 使用递归方法
  2. 创建视图
  3. 添加另一列topParentID(最不利)
  4. 哪种效率最高?

2 个答案:

答案 0 :(得分:4)

SQL2008 +:

要存储层次结构,SQL Server包含HIERARCHYID数据类型。以上数据可以进行转换"使用HIERARCHYID"值"因此:

catName     catID     parentID  hierarchyNode
=============================================
vehicles    1         0         /1/
cars        2         1         /1/2/
sedans      3         2         /1/2/3/
animals     4         0         /4/
cows        5         4         /4/5/

转换后,我会删除parentID列。

HIERARCHYID是SQLCLR系统数据类型,包括以下methods

要获得父节点,我将使用这些方法:

DECLARE @node HIERARCHYID
SET     @node = '/1/2/3/'

SELECT  
    currentNodeLvl= @node.GetLevel(),                                 --> 3
    parentAsHID   = @node.GetAncestor(@node.GetLevel() - 1),          --> 0x58
    parentAsString= @node.GetAncestor(@node.GetLevel() - 1).ToString()--> /1/

更多,我会在hierarchyNode列上创建一个索引:

CREATE UNIQUE INDEX IUN_Table_hierarchyNode
ON dbo.Table(hierarchyNode)

,最终查询将是:

SELECT ..., prt.catID AS parentID
FROM dbo.Table crt -- Curent node
LEFT/INNER JOIN -- It depends on hierarchyID nullability 
dbo.MyTable prt -- Parent node
ON @node.GetAncestor(crt.hierarchyID.GetLevel() - 1).ToString() = prt.hierarchyID

答案 1 :(得分:2)

TLDR

对于大型数据集,请使用桥接表。


桥牌表详细信息

在规模(数据仓库规模)中,我知道解决此问题的最有效方法是通过桥接表。查看http://www.askjohnobiee.com/2013/08/how-to-bridge-tables-and-many-to-many.html了解基础知识。

您最终维护的物理表具有一行,该行将每个父对象与每个子对象和子对象(以及从每个对象到自身)匹配。它维护很重(因为它必须在任何关系发生变化时进行更新),但查询非常有效,即使对于大型数据集也是如此。

在您的示例中,我使用以下架构构建(并填充)桥接表:

CREATE TABLE BridgeCategories
(
 ParentCategoryID int,
 ParentCategoryLevel tinyint,
 ChildCategoryID int,
 ChildCategoryLevel tinyint,
 PRIMARY KEY (ParentCategoryID, ChildCategoryID)
)

-- add an index for your particular query
CREATE NONCLUSTERED INDEX ix_BridgeCategories_ByChildIDAndParentLevel
ON BridgeCategories (ChildCategoryID, ParentCategoryLevel) INCLUDE (ParentCategoryID)

当您填充BridgeCategories表时,我会根据它们在层次结构中的距离设置适当的级别(即,在您的示例中,类别ID 1 - “车辆”将具有0,而ID 2 - “轿车”的等级为2)。

在您的示例中,(我认为)以下查询将填充具有上述结构的桥接表,假设您的源表名为 DimCategories

TRUNCATE TABLE BridgeCategories;

declare @currentLevel tinyint;
declare @MAX_LEVELS tinyint;

set @currentLevel = 0;
set @MAX_LEVELS = 16;

-- seed your root level entries
insert into BridgeCategories
SELECT
    catID, @currentLevel, catID, @currentLevel
from DimCategories c
WHERE
    c.parentID = 0;

set @currentLevel = @currentLevel + 1

while (@currentLevel < @MAX_LEVELS)
BEGIN
    -- add any current level parent -> child mappings   
    insert into BridgeCategories
    SELECT
        b.ParentCategoryID,
        b.ParentCategoryLevel,
        c.catID,
        @currentLevel
    from BridgeCategories b
    join DimCategories c
        on b.ChildCategoryID = c.parentID


    -- add the current level self-referencing entries
    insert into BridgeCategories
    SELECT
        c.catID,
        @currentLevel,
        c.catID,
        @currentLevel
    from BridgeCategories b
    join DimCategories c
        on b.ChildCategoryID = c.parentID
    group by c.catID

    set @currentLevel = @currentLevel + 1
end

使用此结构,您可以运行以下查询以获取任何@catID的根父catID。

select
  ParentCategoryID
from BridgeCategories b
where
  b.ChildCategoryID = @catID
  and b.ParentCategoryLevel = 0

如果您想深入了解,可以查看有关Ralph Kimball数据仓库概念的更多信息。