SQL Server 基于集合的 while 循环避免万圣节保护 (LAHP) 用于层次结构透视将某些结果减半

Posted

技术标签:

【中文标题】SQL Server 基于集合的 while 循环避免万圣节保护 (LAHP) 用于层次结构透视将某些结果减半【英文标题】:SQL Server set-based while loop avoiding halloween protection (LAHP) for hierarchy lookthrough halving certain results 【发布时间】:2015-09-09 01:13:05 【问题描述】:

我一直在研究改进对金融投资组合的分层查看(目前使用 C# 任务执行)的方法。目的是通过相应地分配子投资组合的持股量来确定***母公司投资组合的最终持股量。

当前的数据集大约需要 30 分钟才能运行。这适用于大约 1000 个金融投资组合,每个投资组合最多可以链接大约 4 个投资组合(4 个深度,每个平均大约 40 个行项目)。

数据存储在 SQL Server 中,因此这是我的第一个途径,最初尝试了 CTE 方法。 CTE 方法将运行时间缩短到 7 分钟左右,这是一个巨大的改进。但是,当针对更大的数据集运行时,它会停止并且比 C# 任务花费的时间更长。

在阅读了以下文章后,我转向了基于集合的 while 循环 (LAHP) 方法: https://www.simple-talk.com/sql/performance/the-performance-of-traversing-a-sql-hierarchy-/

我不想详细介绍它是如何工作的,因为这个问题已经足够长了,但是它使用了 2 个临时表的组合,然后是一个 while 循环插入到交替表中,侧步万圣节保护。

当前的 LAHP 甚至比 CTE 更快,并且似乎更擅长处理更大的投资组合集,并且更深入地观察,但是一些结果(投资组合中工具(儿童/证券)的金融持有价值)出来的结果正好是应有的一半。

请看下面的查询,这是以存储过程格式使用的,然后将每个根投资组合作为一个变量。

    DECLARE @root AS VARCHAR(50) = 'Portfolio1';

    CREATE TABLE #T1
    (
      ID            INT             NOT NULL IDENTITY(1,1) PRIMARY KEY,
      lvl             INT             NOT NULL,
      PortfolioCode       varchar(MAX)    NULL,
      InstrumentCode      varchar(MAX)    NULL,
      LinkedID varchar(MAX)    NULL,
      TradedMarketValue     NUMERIC(28, 8)  NOT NULL

    );
    CREATE NONCLUSTERED INDEX IDX_Pcode ON #T1(ID)
    INCLUDE (lvl)

    CREATE TABLE #T2
    (
      ID              INT             NOT NULL IDENTITY(1,1) PRIMARY KEY,
      lvl           INT         NOT NULL,
      PortfolioCode         varchar(MAX)    NULL,
      InstrumentCode        varchar(MAX)    NULL,
      LinkedID   varchar(MAX)  NULL,
      TradedMarketValue     NUMERIC(28, 8)  NOT NULL
    );
    CREATE NONCLUSTERED INDEX IDX_Pcode ON #T2(ID)
    INCLUDE (lvl)

    DECLARE @lvl AS INT = 0;

    -- insert root node
    INSERT INTO #T1
    (
        lvl, 
        LinkedID, 
        PortfolioCode, 
        InstrumentCode, 
        TradedMarketValue
    )
      SELECT 
          @lvl, 
          I.LinkedID, 
          P.PortfolioCode, 
          I.Code, 
          P.TradedMarketValue
      FROM PortfolioHolding P
      join Instrument I on I.Code = P.InstrumentCode
     WHERE P.PortfolioCode =  @root


    WHILE @@ROWCOUNT > 0
    BEGIN
      SET @lvl += 1;

      -- insert children of nodes in prev level
      IF @lvl % 2 = 1

        INSERT INTO #T2
        (
            lvl, 
            LinkedID, 
            PortfolioCode, 
            InstrumentCode, 
            TradedMarketValue
        )
          SELECT 
          @lvl, 
          I.LinkedID, 
          P.PortfolioCode, 
          I.Code, 

          --calculate ratio of holding and apply to holding
          cast(cast(cast(ISNULL(T.TradedMarketValue, 0) as float) / 
          NULLIF(sum(cast(ISNULL(P.TradedMarketValue, 0) as float)) over         (partition by P.PortfolioCode) , 0)  as float) 
          * ISNULL(P.TradedMarketValue, 0) as float)

          FROM #T1 AS T
            INNER JOIN PortfolioHolding AS P
              ON T.lvl = @lvl - 1
              AND P.PortfolioCode = T.LinkedID
         JOIN Instrument I on I.Code = P.InstrumentCode;

      ELSE

        INSERT INTO #T1
        (
            lvl, 
            LinkedID, 
            PortfolioCode, 
            InstrumentCode, 
            TradedMarketValue
        )
        SELECT 
        @lvl, 
        I.LinkedID, 
        P.PortfolioCode, 
        I.Code, 

        --calculate ratio of holding and apply to holding
         (cast(cast(cast(ISNULL(T.TradedMarketValue, 0) as float) / 
         NULLIF(sum(cast(ISNULL(P.TradedMarketValue, 0) as float)) over         (partition by P.PortfolioCode) , 0) as float) 
        * ISNULL(P.TradedMarketValue, 0) as float))

        FROM #T2 AS T
          INNER JOIN PortfolioHolding AS P
            ON T.lvl = @lvl - 1
            AND P.PortfolioCode = T.LinkedID
        JOIN Instrument I on I.Code = P.InstrumentCode;
    END


    SELECT 
    @root, InstrumentCode ,sum(tradedmarketvalue) As TradedMarketValue
    FROM 
    (SELECT * FROM #T1 UNION ALL SELECT * FROM #T2) AS U
    WHERE LinkedID is null
    group by InstrumentCode

    DROP TABLE #T1, #T2;

从查询中可以看出,PortfolioCode (ParentID) 包含 InstrumentCodes (ChildId),LinkedIdInstrumentCode 与其相关的 PortfolioCode 匹配。

TradedMarketValueLinkedIDnull 相加得到所有叶节点。

如前所述,一些 TradedMarketValues 的收入仅为应有的一半。非常感谢您的帮助。

【问题讨论】:

这是一个非常好的第一篇文章,人们可能会发现唯一有用的是一组简化的源数据以及所需的输出,以便他们可以运行您的代码并尝试产生所需的输出.这样他们就可以看到它的作用并尝试对其进行优化。 有点困惑。你想让这个更快吗?还是说 TradedMarketValue 不正确?鉴于这非常复杂,如果您提供示例数据和预期输出将会很有帮助。 您好,感谢您的提示,我将提供一组示例数据。问题是它提供了不正确的结果。但只是一部分。大约 70% 的结果是正确的,另外 30% 的结果(对于 TradedMarketValue)正好是一半。 @Thematkinson 如果您可以提供一些示例行来展示理想的输出好坏 @Tanner,我正忙于混淆一些数据,因为它的机密客户持有数据。 【参考方案1】:

好吧好吧……在花了 2 个小时创建示例数据以发布我的问题并运行它之后,答案就出现了!

请看下面的示例数据:

仪器表:

InstrumentCode  LinkedId
Inst1           NULL
Inst2           Port2
Inst3           NULL
Inst4           Port4
Inst5           Port5
Inst6           NULL
Inst7           NULL
Inst8           NULL
Inst9           NULL
Inst10          NULL
Inst11          NULL

投资组合表:

PortfolioCode   InstrumentCode  TradedMarketValue
Port1           Inst1           150.00
Port1           Inst2           60.00
Port1           Inst3           45.00
Port2           Inst1           75.00
Port2           Inst4           95.00
Port2           Inst5           100.00
Port3           Inst2           110.00
Port3           Inst6           95.00
Port4           Inst8           145.00
Port4           Inst9           100.00
Port4           Inst7           125.00
Port5           Inst8           150.00
Port5           Inst11          175.00
Port5           Inst10          120.00

举个例子,如果我们想要分解投资组合 1 Port1,这将最终形成 3 个投资组合深度,首先是 Port2,然后是第三层 Port4Port5

见图表:

整个系统工作正常,查询提供正确的结果。 当一个投资组合中的两种不同工具指向相同的LinkedId 或“投资组合代码”时,就会出现问题。

见图表:

然后发生的是这部分代码:

--calculate ratio of holding and apply to holding
          cast(cast(cast(ISNULL(T.TradedMarketValue, 0) as float) / 
          NULLIF(sum(cast(ISNULL(P.TradedMarketValue, 0) as float)) over (partition by P.PortfolioCode) , 0)  as float) 
          * ISNULL(P.TradedMarketValue, 0) as float)

计算父投资组合的 TradedMarketValue (TMV) 与子投资组合总价值的比率是两次求和,导致分母加倍。

要解决此问题,可以将上述代码部分更改为

  --calculate ratio of holding and apply to holding
          cast(cast(cast(ISNULL(T.TradedMarketValue, 0) as float) / 
          NULLIF(sum(cast(ISNULL(P.TradedMarketValue, 0) as float)) over (partition by T.InstrumentCode, P.PortfolioCode) , 0)  as float) 
          * ISNULL(P.TradedMarketValue, 0) as float)

添加一个额外的变量以按(分组)T.InstrumentCode 进行分区,然后停止上述发生。

我希望这对以后的人有所帮助。

【讨论】:

以上是关于SQL Server 基于集合的 while 循环避免万圣节保护 (LAHP) 用于层次结构透视将某些结果减半的主要内容,如果未能解决你的问题,请参考以下文章

sql server中do while循环怎么写

如何在 SQL Server 的 While 循环中设置变量

避免 SQL Server 中的 while 循环

sql server中的while循环语句

如何在 SQL Server 中组织无限 while 循环?

如何删除 MS SQL Server 存储过程中的 While 循环? [关闭]