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

时间:2012-08-17 21:07:00

标签: c# sql excel optimization sql-server-2008-r2

考虑到为每个单元分配了一定的“退休率”,我需要计算多年来具有不同“环境”的不同国家/地区的不同展示位置/出货量的单位的安装基数。放置,曲线定义和曲线分配存储在不同的数据库表中(下面有DDL和样本数据,也在SQLFiddle.com上)。计算安装基数的公式如下:

enter image description here 1990年是我们有第一年的就业数据。

问题:

使用300万到1600万行单位/国家/环境/年放置组合的数据集进行这些计算需要比目标加载/计算时间30秒到1分钟更多的时间。

Sql Server方法

PIVOT编辑,以便每年成为自己的列时,我可以获得100,000 t0 400,000返回的原始数据行(展示位置+费率),大约需要8-15秒。但是,如果我通过下面包含的SQL语句手动计算,则至少 10分钟。

我们还尝试了一种SQL触发器解决方案,每次修改放置或速率时都会更新已安装的基础,但这会使批量更新时数据库更新速度过慢,并且也不可靠。如果这真的是最好的选择,我想这可能值得进一步调查。

Excel-VSTO方法(到目前为止,最快的方法):

此数据最终以C#VSTO驱动的Excel工作簿结束,其中数据是通过一系列VLOOKUPs计算的,但是当在6年内加载150,000个展示位置时,每个单元格约20 VLOOKUPs(约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'

2 个答案:

答案 0 :(得分:1)

回应以下声明:

  

我们还尝试过更新已安装的SQL触发器解决方案   每次修改展示位置或费率时都会生成基数   数据库更新在批量更新时不合理地缓慢,也是   不可靠的。如果是这样的话,我认为值得进一步调查   真的是最好的选择。

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

答案 1 :(得分:0)

看起来这可能会成为一个问题导致正确答案的情况。事实证明,答案主要在于我上面给出的查询,这完全是低效的。我已经能够通过优化查询来获得我正在寻找的附近的加载时间。

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