需要将递归CTE查询转换为索引友好查询

时间:2011-01-25 22:13:21

标签: sql sql-server-2008 hierarchical-data common-table-expression recursive-query

在完成编写递归CTE查询以满足我的需求的所有艰苦工作之后,我意识到我无法使用它,因为它在索引视图中不起作用。所以我需要其他东西来替换下面的CTE。 (是的,您可以在非索引视图中使用CTE,但这对我来说太慢了。)

要求:

  1. 我的最终目标是拥有一个自我更新的索引视图(它不一定是视图,但类似的东西)...也就是说,如果视图加入的任何表中的数据发生更改,那么视图需要自我更新。

  2. 视图需要编制索引,因为它必须非常快,并且数据不会经常更改。不幸的是,使用CTE的非索引视图需要3-5秒才能运行,这对我的需求来说太长了。我需要查询以毫秒运行。递归表中有几十万条记录。

  3. 就我的研究而言,满足所有这些要求的最佳解决方案是索引视图,但我对任何解决方案持开放态度。

    CTE可以在other post的答案中找到。 或者又来了:

    DECLARE @tbl TABLE ( 
         Id INT 
        ,[Name] VARCHAR(20) 
        ,ParentId INT 
        ) 
    
    INSERT INTO @tbl( Id, Name, ParentId ) 
    VALUES 
     (1, 'Europe', NULL) 
    ,(2, 'Asia',   NULL) 
    ,(3, 'Germany', 1) 
    ,(4, 'UK',      1) 
    ,(5, 'China',   2) 
    ,(6, 'India',   2) 
    ,(7, 'Scotland', 4) 
    ,(8, 'Edinburgh', 7) 
    ,(9, 'Leith', 8) 
    
    ; 
    DECLARE @tbl2 table (id int, abbreviation varchar(10), tbl_id int)
    INSERT INTO @tbl2( Id, Abbreviation, tbl_id ) 
    VALUES 
     (100, 'EU', 1) 
    ,(101, 'AS', 2) 
    ,(102, 'DE', 3) 
    ,(103, 'CN', 5)
    
    ;WITH abbr AS (
        SELECT a.*, isnull(b.abbreviation,'') abbreviation
        FROM @tbl a
        left join @tbl2 b on a.Id = b.tbl_id
    ), abcd AS ( 
              -- anchor 
            SELECT  id, [Name], ParentID,
                    CAST(([Name]) AS VARCHAR(1000)) [Path],
                    cast(abbreviation as varchar(max)) abbreviation
            FROM    abbr
            WHERE   ParentId IS NULL 
            UNION ALL
              --recursive member 
            SELECT  t.id, t.[Name], t.ParentID, 
                    CAST((a.path + '/' + t.Name) AS VARCHAR(1000)) [Path],
                    isnull(nullif(t.abbreviation,'')+',', '') + a.abbreviation
            FROM    abbr AS t 
                    JOIN abcd AS a 
                      ON t.ParentId = a.id 
           )
    SELECT *, [Path] + ':' + abbreviation
    FROM abcd
    

1 个答案:

答案 0 :(得分:3)

在使用索引视图(自联接,cte,udf访问数据等)点击所有包版广告后,我建议将以下内容作为解决方案。

创建支持功能

基于根的最大深度为4(总共5个)。或者使用CTE

CREATE FUNCTION dbo.GetHierPath(@hier_id int) returns varchar(max)
WITH SCHEMABINDING
as
begin
return (
    select FullPath =
               isnull(H5.Name+'/','') + 
               isnull(H4.Name+'/','') +
               isnull(H3.Name+'/','') +
               isnull(H2.Name+'/','') +
               H1.Name
             +
               ':'
             +
               isnull(STUFF(
               isnull(','+A1.abbreviation,'') +
               isnull(','+A2.abbreviation,'') + 
               isnull(','+A3.abbreviation,'') +
               isnull(','+A4.abbreviation,'') +
               isnull(','+A5.abbreviation,''),1,1,''),'')
    from dbo.HIER H1
    left join dbo.ABBR A1 on A1.hier_id = H1.Id
    left join dbo.HIER H2 on H1.ParentId = H2.Id
    left join dbo.ABBR A2 on A2.hier_id = H2.Id
    left join dbo.HIER H3 on H2.ParentId = H3.Id
    left join dbo.ABBR A3 on A3.hier_id = H3.Id
    left join dbo.HIER H4 on H3.ParentId = H4.Id
    left join dbo.ABBR A4 on A4.hier_id = H4.Id
    left join dbo.HIER H5 on H4.ParentId = H5.Id
    left join dbo.ABBR A5 on A5.hier_id = H5.Id
    where H1.id = @hier_id)
end
GO

将列添加到表本身

例如,如果需要,可以通过在':'(left=>path, right=>abbreviations)

上拆分dbo.GetHierPath的结果,在CTE中添加其他2列。
-- index maximum key length is 900, based on your data, 400 is enough
ALTER TABLE HIER ADD FullPath VARCHAR(400)

维护列

由于分层性质,可以删除影响Y后代和Z祖先的记录X,这在INSTEAD OF或AFTER触发器中很难识别。所以替代方法是基于条件

  • 如果视图加入的任何表中的数据发生更改,则视图需要自行更新。
  • 使用CTE的非索引视图需要3-5秒才能运行,这对我的需求来说太长了

我们只是通过再次遍历整个表来维护数据,每次更新需要3-5秒(如果5连接查询效果更好,则更快)。

CREATE TRIGGER TG_HIER
ON HIER
AFTER INSERT, UPDATE, DELETE
AS
UPDATE HIER
SET FullPath = dbo.GetHierPath(HIER.Id)

最后,索引表本身的新列

create index ix_hier_fullpath on HIER(FullPath)

如果您打算通过id访问路径数据,那么它已经在表中,而不添加额外的索引。

上述TSQL引用了这些对象

修改表名和列名以适合您的架构。

CREATE TABLE dbo.HIER (Id INT Primary Key Clustered, [Name] VARCHAR(20) ,ParentId INT)
;
INSERT dbo.HIER( Id, Name, ParentId ) VALUES 
     (1, 'Europe', NULL) 
    ,(2, 'Asia',   NULL) 
    ,(3, 'Germany', 1) 
    ,(4, 'UK',      1) 
    ,(5, 'China',   2) 
    ,(6, 'India',   2) 
    ,(7, 'Scotland', 4) 
    ,(8, 'Edinburgh', 7) 
    ,(9, 'Leith', 8)
    ,(10, 'Antartica', NULL) 
; 
CREATE TABLE dbo.ABBR (id int primary key clustered, abbreviation varchar(10), hier_id int)
;
INSERT dbo.ABBR( Id, Abbreviation, hier_id ) VALUES 
     (100, 'EU', 1) 
    ,(101, 'AS', 2) 
    ,(102, 'DE', 3) 
    ,(103, 'CN', 5)
GO

编辑 - 可能更快的替代

鉴于每次重新计算所有记录,实际上不需要为单个HIER.ID返回FullPath的函数。 support function中的查询可以在最后没有where H1.id = @hier_id过滤器的情况下使用。此外,FullPath的表达式可以轻松地分为PathOnlyAbbreviation。或者只使用原始CTE,以较快者为准。