【问题标题】:GetAncestor query on SQL hierarchyid to always pull certain levelsGetAncestor 查询 SQL hierarchyid 以始终拉取某些级别
【发布时间】:2014-10-07 18:52:22
【问题描述】:

我们通常被要求在报表中包含某些层次结构级别,我正在寻找一种方法来使用 hierarchyid 来提高查询性能。我做了一些测试,并且我有一些有效的查询,但我认为性能可能会更好。下面是一个示例,它为给定条目提取级别 2 到 4。我想出了一种方法来使用 GetAncestor() 函数结合hierarchyid的级别。第一个查询似乎很快,但它被硬编码为只返回某个级别的行,以避免用负值破坏 GetAncestor 查询。第二个示例解决了该问题,但速度要慢得多。理想情况下,第二个选项是我想使用的,但速度不够快。

--drop table #hier

CREATE TABLE #hier
    (
    rec_ID int NOT NULL,
    rec_NAME varchar(6),
    nodeID hierarchyid NULL,
    lvl  AS [nodeid].[GetLevel]() PERSISTED 
    )  ON [PRIMARY]
GO
ALTER TABLE #hier ADD CONSTRAINT
    rec_ID PRIMARY KEY CLUSTERED 
    (
    rec_ID
    ) WITH( STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
GO
CREATE NONCLUSTERED INDEX IX_hier_nodeID ON #hier
    (
    nodeID
    ) WITH( STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
GO
ALTER TABLE #hier SET (LOCK_ESCALATION = TABLE)
GO

insert into #hier (rec_ID, rec_NAME, nodeID)
    SELECT 1, 'CEO', cast('/' as hierarchyid)
    union
    SELECT 2, 'VP1', cast('/1/' as hierarchyid)
    union
    SELECT 3, 'VP2', cast('/2/' as hierarchyid)
    union
    SELECT 4, 'VP3', cast('/3/' as hierarchyid)
    union
    SELECT 5, 'Mgr1', cast('/1/1/' as hierarchyid)
    union
    SELECT 6, 'Mgr2', cast('/1/2/' as hierarchyid)
    union
    SELECT 7, 'Super1', cast('/1/2/1/' as hierarchyid)
    union
    SELECT 8, 'Ldr1', cast('/1/2/1/1/' as hierarchyid)
    union
    SELECT 9, 'Work1', cast('/1/2/1/1/1/' as hierarchyid)
    union
    SELECT 10, 'Work2', cast('/1/2/1/1/2/' as hierarchyid)
    union
    SELECT 11, 'Work3', cast('/1/2/1/1/3/' as hierarchyid)
GO

-- this runs fast but is hard coded to a level

declare @recname varchar(6)
set @recname = 'Work3'

select 
    x.rec_name
    ,x.lvl
    ,(select rec_name from  #hier where nodeid = x.nodeid.GetAncestor(x.lvl - 2) ) as l2
    ,(select rec_name from  #hier where nodeid = x.nodeid.GetAncestor(x.lvl - 3) ) as l3
    ,(select rec_name from  #hier where nodeid = x.nodeid.GetAncestor(x.lvl - 4) ) as l4
from #hier x 
where x.rec_name = @recname
    and x.lvl >= 4


-- this works for all levels but runs too slow

set @recname = 'Mgr2'
select 
    x.rec_name
    ,x.lvl
    ,case
        when x.lvl >=2 then (select rec_name from  #hier where nodeid = x.nodeid.GetAncestor(x.lvl - 2) )
        else '*N/A' end as l2
    ,case
        when x.lvl >=3 then (select rec_name from  #hier where nodeid = x.nodeid.GetAncestor(x.lvl - 3) )
        else '*N/A' end as l2
    ,case
        when x.lvl >=4 then (select rec_name from  #hier where nodeid = x.nodeid.GetAncestor(x.lvl - 4) )
        else '*N/A' end as l2
from #hier x 
where x.rec_name = @recname

【问题讨论】:

    标签: sql-server tsql hierarchy hierarchyid


    【解决方案1】:

    在我看来,您应该创建以下索引:

    1)

    CREATE INDEX IX_hier_rec_name_#_lvl_nodeid 
    ON #hier (rec_name) 
    INCLUDE (lvl, nodeid)
    

    满足查询 #2

    select 
        x.rec_name
        ,x.lvl
        ,case
            when x.lvl >=2 then (... = x.nodeid.GetAncestor(x.lvl - 2) )
            else '*N/A' end as l2
        ,...
    from #hier x 
    where x.rec_name = @recname
    

    注意:对于查询 #1,您可以使用

    CREATE INDEX IX_hier_rec_name_lvl_#_nodeid 
    ON #hier (rec_name, lvl) 
    INCLUDE (nodeid)
    

    2) 你应该用

    改变这个索引IX_hier_nodeID
    CREATE INDEX IX_hier_nodeID_#_rec_name 
    ON #hier (nodeid) 
    INCLUDE (rec_name)
    

    或(更好)与

    CREATE UNIQUE INDEX IUN_hier_nodeID_#_rec_name 
    ON #hier (nodeid) 
    INCLUDE (rec_name)
    

    如果nodeid 值不允许重复。

    【讨论】:

    • 这很好,我应该先深入研究索引。我还应该创建一个带有循环和更多行的示例,以更好地测试性能。最初我认为也许有一种方法可以重新处理查询,但也许这只是一个简单的索引问题。在示例中,rec_ID 肯定是唯一的,并且 nodeID 应该是唯一的(假设我的维护工作正常工作)。让我研究一下您对 INCLUDE 选项的使用 - 我不熟悉。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-08-14
    • 1970-01-01
    • 1970-01-01
    • 2017-04-19
    • 1970-01-01
    • 2017-11-01
    相关资源
    最近更新 更多