C# 计算安装基数的最有效方法是什么?

C# 计算安装基数的最有效方法是什么?,c#,sql,excel,optimization,sql-server-2008-r2,C#,Sql,Excel,Optimization,Sql Server 2008 R2,我有一项要求,即根据分配给每个装置的一组特定“退役率”,计算多年来在不同“环境”的不同国家/地区进行不同放置/装运的装置的安装基数。放置、曲线定义和曲线指定存储在不同的数据库表中(下面的DDL和示例数据也处于打开状态)。装机基数计算公式如下: 其中,1990年是我们有安置数据的第一年 问题是: 使用300万到1600万行单位/国家/环境/年份布局组合的数据集进行这些计算所需的时间远远超过30秒到1分钟的目标负载/计算时间 Sql Server方法 当PIVOTed使每年都成为自己的列时,我得到

我有一项要求,即根据分配给每个装置的一组特定“退役率”,计算多年来在不同“环境”的不同国家/地区进行不同放置/装运的装置的安装基数。放置、曲线定义和曲线指定存储在不同的数据库表中(下面的DDL和示例数据也处于打开状态)。装机基数计算公式如下:

其中,1990年是我们有安置数据的第一年

问题是: 使用300万到1600万行单位/国家/环境/年份布局组合的数据集进行这些计算所需的时间远远超过30秒到1分钟的目标负载/计算时间

Sql Server方法 当
PIVOT
ed使每年都成为自己的列时,我得到了从100000到400000行返回的原始数据(放置+速率),这大约需要8-15秒。然而,若我要通过下面包含的SQL语句手动计算,至少需要10分钟

我们还尝试了一种SQL触发器解决方案,该解决方案在每次修改位置或速率时都会更新安装基数,但这会使数据库更新在批更新时不合理地慢,而且也不可靠。如果这真的是最好的选择,我想这可能值得更多的调查

Excel VSTO方法(迄今为止,最快的方法): 这些数据最终会出现在一个以C#VSTO为动力的Excel工作簿中,在该工作簿中,数据是通过一系列的
VLOOKUPs
计算出来的,但当在6年内以每个单元格20个
VLOOKUPs
加载150000个位置时(约2000万
VLOOKUPs
),Excel崩溃。当
VLOOKUPs
以较小的批量完成,并将公式转换为值时,虽然不会崩溃,但计算时间仍要比一分钟长得多

问题是: 是否有一些数学或编程结构可以帮助我通过C#或SQL更高效地计算这些数据?蛮力迭代也太慢,所以这也不是一个选项

DECLARE @Placements TABLE 
(
    UnitId int not null,
    Environment varchar(50) not null,
    Country varchar(100) not null,
    YearColumn smallint not null,
    Placement decimal(18,2) not null,
    PRIMARY KEY (UnitId, Environment, Country, YearColumn)
)


DECLARE @CurveAssignments TABLE 
(
    UnitId int not null,
    Environment varchar(50) not null,
    Country varchar(100) not null,
    YearColumn smallint not null,
    RateId int not null,
    PRIMARY KEY (UnitId, Environment, Country, YearColumn)
)

DECLARE @CurveDefinitions TABLE
(
    RateId int not null,
    YearOffset int not null,
    Rate decimal(18,2) not null,
    PRIMARY KEY (RateId, YearOffset)
)

INSERT INTO
    @Placements
    (
        UnitId,
        Country,
        YearColumn,
        Environment,
        Placement
    )
VALUES
    (
        1,
        'United States',
        1991,
        'Windows',
        100
    ),
    (
        1,
        'United States',
        1990,
        'Windows',
        100
    )

INSERT INTO
    @CurveAssignments
    (
        UnitId,
        Country,
        YearColumn,
        Environment,
        RateId
    )
VALUES
    (
        1,
        'United States',
        1991,
        'Windows',
        1
    )

INSERT INTO
    @CurveDefinitions
    (
        RateId,
        YearOffset,
        Rate
    )
VALUES
    (
        1,
        0,
        1
    ),
    (
        1,
        1,
        0.5
    )

SELECT
    P.UnitId,
    P.Country,
    P.YearColumn,
    P.Placement *
    (
        SELECT
            Rate
        FROM
            @CurveDefinitions CD
            INNER JOIN @CurveAssignments CA ON
                CD.RateId = CA.RateId
        WHERE
            CA.UnitId = P.UnitId
            AND CA.Environment = P.Environment
            AND CA.Country = P.Country
            AND CA.YearColumn = P.YearColumn - 0
            AND CD.YearOffset = 0
    )
    +
    (
        SELECT
            Placement
        FROM
            @Placements PP
        WHERE
            PP.UnitId = P.UnitId
            AND PP.Environment = P.Environment
            AND PP.Country = P.Country
            AND PP.YearColumn = P.YearColumn - 1
    )
    *
    (
        SELECT
            Rate
        FROM
            @CurveDefinitions CD
            INNER JOIN @CurveAssignments CA ON
                CD.RateId = CA.RateId
        WHERE
            CA.UnitId = P.UnitId
            AND CA.Environment = P.Environment
            AND CA.Country = P.Country
            AND CA.YearColumn = P.YearColumn
            AND CD.YearOffset = 1
    ) [Installed Base - 1993]
FROM
    @Placements P
WHERE
    P.UnitId = 1
    AND P.Country = 'United States'
    AND P.YearColumn = 1991
    AND P.Environment = 'Windows'

针对以下声明:

我们还尝试了一个SQL触发器解决方案,该解决方案更新了已安装的 每次修改位置或速率时的基准,但这使 数据库更新在批处理更新时不合理地慢,并且 不可靠的。如果这是事实,我想这可能值得更多的调查 这确实是最好的选择


你听说过吗?它做得很好的一件事是允许您将数据排队以进行异步处理。如果触发器本身太慢,您可以使用触发器将记录排队以进行异步处理。

看起来,在这种情况下,提问可能会得到正确的答案。事实证明,答案主要在于我上面给出的查询,这是完全没有效率的。通过如下优化查询,我已经能够获得我正在寻找的附近的加载时间

SELECT
    P.UnitId,
    P.Country,
    P.YearColumn,
    P.Environment,
    P.Placement,
    sum(IBP.Placement * FRR.Rate) InstalledBase
FROM
    @Placements P
    INNER JOIN @Placements IBP ON
        P.UnitId = IBP.UnitId
        AND P.Country = IBP.Country
        AND P.Environment = IBP.Environment
        AND P.YearColumn >= IBP.YearColumn
    INNER JOIN @CurveAssignments RR ON
        IBP.UnitId = RR.UnitId
        AND IBP.Country = RR.Country
        AND IBP.Environment = RR.Environment
        AND IBP.YearColumn = RR.YearColumn
    INNER JOIN @CurveDefinitions FRR ON
        Rr.RateId = FRR.RateId
        AND P.YearColumn - IBP.YearColumn = FRR.YearOffset
GROUP BY
    P.UnitId,
    P.YearColumn,
    P.Country,
    P.Environment,
    P.Placement

这听起来像是我通常保存在单独表格中的汇总数据。该表可以每天更新,也可以在其他适当的时间间隔内使用计划作业进行更新。这些数据需要“实时”吗?或者在计算中有一些延迟是可以接受的吗?当数据透视使每年都成为自己的列时,我可以得到100000到400000行返回的原始数据(放置+费率),大约需要8-15秒。你这是什么意思?这是否意味着你使用了PIVOT子句?@HABO:谢谢你的评论。我在上一次迭代中就是这么做的。我禁用了表更新的触发,因为用户最终想要一个实时解决方案,并且更新时间太长。最终可能会再次出现这种情况。@dana:谢谢你的评论,和HABO的评论类似。这让我意识到,这可能会促使我与用户就“实时”数据与“实时”加载的优缺点进行坦诚的对话,因为如果这两种情况相等(一种情况下加载10分钟/秒,另一种情况下更新10分钟/秒),那么这两种情况实际上都是“实时”的。不,我没有!异步处理可能是一种方法。我会调查一下,让你知道结果如何。