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

SQL Server set-based while loop avoiding halloween protection (LAHP) for hierarchy lookthrough halving certain results

我一直在研究改进金融投资组合领域的分层浏览(目前使用 C# 任务执行)的方法。目的是通过相应地分配子投资组合的持有量来确定最高母投资组合的最终持有量。

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

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

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

我不想过多地讨论它是如何工作的,因为这个问题已经足够长了,但是它使用了 2 个临时表的组合,然后是一个 while 循环来插入到交替表中,边加强万圣节保护。

当前的 LAHP 甚至比 CTE 更快,并且似乎更擅长处理更大的投资组合和更深入的观察,但是一些结果(工具的财务持有价值 (child/security)在投资组合中)正好是他们应该的一半。

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

    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.

TradedMarketValue求和,其中LinkedIDnull得到所有叶节点。

如前所述,一些 TradedMarketValues 的价格只有应有的一半。非常感谢您的帮助。

好吧好吧......在花了 2 个小时为 post 创建示例数据后,我的问题 运行 答案就出现了!

请参阅下面的示例数据:

仪器Table:

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

投资组合 Table:

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,然后是 Port4 的第 3 层,然后Port5

见图:

整个系统工作正常,查询提供了正确的结果。 当一个投资组合中的两种不同工具指向相同的 LinkedId 或 'PortfolioCode'.

时,问题就出现了

见图:

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

--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,然后停止上述情况的发生。

我希望这对在线的人有所帮助。