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
),并且 LinkedId
将 InstrumentCode
与其相关的 PortfolioCode
.
将TradedMarketValue
求和,其中LinkedID
是null
得到所有叶节点。
如前所述,一些 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
,然后停止上述情况的发生。
我希望这对在线的人有所帮助。
我一直在研究改进金融投资组合领域的分层浏览(目前使用 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
),并且 LinkedId
将 InstrumentCode
与其相关的 PortfolioCode
.
将TradedMarketValue
求和,其中LinkedID
是null
得到所有叶节点。
如前所述,一些 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
,然后停止上述情况的发生。
我希望这对在线的人有所帮助。