解决递归CTE中的性能问题

问题描述 投票:0回答:1

我有一个维度(星型模式)数据模型,支持劳动力数据的 BI。它包括一个员工维度表。该表是类型 2 缓慢变化的维度表,即将历史记录存储为单独的行。目前大约有 9k 行。

我需要在此员工维度表(在 SQL 视图中)上创建一个新列,其中将包含每个员工的“组织层次结构”(主管列表)的管道分隔列表。例如,如果员工 C 向经理 B 汇报,经理 B 在时间 t1 向经理 A 汇报,则此新列将包含值“A|B|C”。如果员工向经理 D 汇报,经理 D 在时间 t2 向经理 A 汇报,则此新列将包含值“A|D|C”。

我正在为这个新列使用递归 CTE。对于一些小容量(9 行)的模拟数据,这种递归 CTE 效果很好。然而,当我对实际数据(同样,只有大约 9k 行)运行此递归 CTE 时,30 分钟后它仍然没有返回结果集。鉴于数据量非常低,这令人惊讶。

如果重要的话,这是在 Azure SQL MI 上运行的。并且,表的主键 ([EmployeeHistoryKey]) 上有一个聚集索引。

在查看估计的查询执行计划时,我注意到大部分查询成本来自 [ManagerID] 列上的表扫描。因此,我在 [ManagerID] 列上放置了非聚集索引,但这并没有提高性能。那么,如何改进查询以提高性能呢?

下面是我的模拟数据和查询:

IF OBJECT_ID('SomeDB.some_schema.OrgHierarchyMockup', 'U') IS NOT NULL
    DROP TABLE SomeDB.some_schema.OrgHierarchyMockup
;

CREATE TABLE
    SomeDB.some_schema.OrgHierarchyMockup
(
    EmployeeHistoryKey int
    ,EmployeeID char(1)
    ,ManagerID char(1)
    ,SomeAttribute char(1)
    ,RowEffectiveDate date
    ,RowExpirationDate date
)
;

INSERT INTO
    SomeDB.some_schema.OrgHierarchyMockup
VALUES
    (1, 'a', NULL, 'x', '2023-01-01', '2023-06-01')
    ,(2, 'a', NULL, 'y', '2023-06-02', NULL)
    ,(3, 'b', 'a', 'x', '2023-01-01', '2023-06-01')
    ,(4, 'b', 'a', 'y', '2023-06-02', NULL)
    ,(5, 'c', 'a', 'x', '2023-01-01', NULL)
    ,(6, 'd', 'b', 'x', '2023-01-01', NULL)
    ,(7, 'e', 'b', 'x', '2023-01-01', '2023-06-01')
    ,(8, 'e', 'c', 'x', '2023-06-02', NULL)
    ,(9, 'f', 'c', 'x', '2023-01-01', NULL)
;

WITH
traversed_hierarchy AS
(
    SELECT --anchor member
        EmployeeHistoryKey
        ,EmployeeID
        ,ManagerID
        ,RowEffectiveDate
        ,RowExpirationDate
        ,CAST(EmployeeID AS varchar(max)) AS OrgHierarchy --this is the org hierarchy
        ,EmployeeHistoryKey AS m_EmployeeHistoryKey --necessary to "de-dupe" the result set
    FROM
        SomeDB.some_schema.OrgHierarchyMockup
    WHERE
        ManagerID IS NULL

    UNION ALL

    SELECT --recursive member
        s.EmployeeHistoryKey
        ,s.EmployeeID
        ,s.ManagerID
        ,s.RowEffectiveDate
        ,s.RowExpirationDate
        ,m.OrgHierarchy1 + '|' + s.EmployeeID
        ,m.EmployeeHistoryKey
    FROM
        SomeDB.some_schema.OrgHierarchyMockup AS s --direct subordinates
    INNER JOIN --must be an inner join (not a left join) because we want the direct subordinates of the previously-fetched level in traversed_hierarchy
        traversed_hierarchy AS m
        ON
            m.EmployeeID = s.ManagerID
)
,rownumbered AS
(
    SELECT
        EmployeeHistoryKey
        ,EmployeeID
        ,ManagerID
        ,RowEffectiveDate
        ,RowExpirationDate
        ,OrgHierarchy
        ,m_EmployeeHistoryKey
        ,ROW_NUMBER() OVER( --this will allow us to de-dupe the result set
            PARTITION BY
                EmployeeHistoryKey
            ORDER BY
                m_EmployeeHistoryKey
        ) AS RowNum
    FROM
        traversed_hierarchy
)
SELECT
    EmployeeHistoryKey
    ,EmployeeID
    ,OrgHierarchy
    ,m_EmployeeHistoryKey
FROM
    rownumbered
WHERE
    RowNum = 1
ORDER BY
    EmployeeID
    ,EmployeeHistoryKey
    ,m_EmployeeHistoryKey
;
sql sql-server azure-sql-database query-optimization azure-sql-managed-instance
1个回答
0
投票

使用临时表而不是 CTE,仅此一项就可以为您带来更好的性能。当您使用临时表创建查询计划时,SQL Server 可以使用统计信息并做出更好的决策。随着时间的推移,您的数据增长得越多,参与大表联接的嵌套 CTE 请求的内存授予就越大,如果服务器出现内存争用,则请求内存授予的 CTE 将挂起。在您与我们分享的场景中也不要使用表变量。

此外,使用临时表,您可以创建索引来支持涉及临时表的联接操作。对于 CTE,您无法做到这一点。

© www.soinside.com 2019 - 2024. All rights reserved.