T-SQL:每季度将花费的资金分配到里程碑上

时间:2014-03-04 16:02:49

标签: sql tsql

目前我正在考虑使用SQL Query解决下面描述的问题。但是我还没有能够提出一个非常令人满意的解决方案。 我总是可以在查询数据库的程序中解决问题,但是首选SQL服务器端解决方案(因为它是大量数据,服务器比我的计算机快得多)。

方案(MS SQL Server 2008): 两个表,一个称为“预算”,另一个称为“里程碑”。预算包含季度花费,里程碑表包含...惊喜,里程碑。

Relation of table budget: (year, quarter, spent_money)
year | quarter | spent_money
-----|---------|------------
2014 | 1       | 1000 $
2014 | 2       | 2000 $
2014 | 3       | 1500 $
2014 | 4       | 1000 $

Relation of table milestones (name, date)
name | date
-----|-----------
MS1  | 2014-01-31
MS2  | 2014-02-28
MS3  | 2014-08-31

现在我想知道,我为每个里程碑花了多少钱。我假设每个季度的资金都是平均花费的。 示例:第1季度大约三分之一的资金将用于里程碑1,另外三分之一用于里程碑2。最后一个加上所有第二季度和第三季度的某些部分属于里程碑3。预期结果:

Milestone | spent_money
----------|------------
MS1       | 30/90 * 1000 $
MS2       | 28/90 * 1000 $
MS3       | 31/90 * 1000 $ + 2000 $ + 31/92 * 1500

将属于一个里程碑的季度天数除以该季度的总天数给出了一个因子,可以乘以每季度的spend_money。

到目前为止我的想法: 创建一个虚拟表,其中包含所有相关季度的所有日期(和日期)以及因子1 / [季度的天数],然后加入预算表并将钱与因子相乘。最后一步是加入里程碑表并使用SUM()函数按里程碑进行分组,这会产生结构性问题:

我必须加入里程碑表,例如ON budget_per_day.day <= milestones.date AND budget_per_day.day >= milestones.[previous date]。一种解决方案可以是为里程碑关系添加开始日期,或者首先将里程碑加入自身,并将上一个里程碑的日期作为下一个里程碑的开始日期。

生成的查询如下所示:

SELECT milestones.name,SUM(alldays.factor*budget.spent_money) FROM
((SELECT 2014-01-01 AS day_date, 1 AS day_id, 1/90 AS factor
UNION SELECT 2014-01-02, 2, 1/90
...
UNION SELECT 2014-08-15, 2) AS alldays
LEFT JOIN budget ON alldays.date>budget.quarter_start_date AND alldays.date<=budget.quarter_end_date)
LEFT JOIN milestones ON alldays.date>milestones.start_date AND alldays.date<=date
GROUP BY milestones.name

高度赞赏任何提高性能的想法。

斯文

1 个答案:

答案 0 :(得分:0)

经过几个小时的研究,我想出了以下解决方案。希望我没有破坏任何东西,同时将查询减少到其重要的核心。我很确定那里有更好的解决方案,但现在它对我有用(4分钟内有10 Mio)。我没有为actual_per_day列包含SUM()。

    WITH sample AS (
    SELECT CAST('2010-01-01' AS date) AS dt
    UNION ALL
    SELECT DATEADD(dd, 1, dt)
    FROM sample s
    WHERE DATEADD(dd, 1, dt) <= CAST('2014-12-31' AS date)),

    /****************************************************/

    cte_gates AS (
    SELECT 
    rownum = ROW_NUMBER() OVER (ORDER BY project,milestone),
    *
    FROM milestones
    )

    /****************************************************/

    SELECT dt,b.project,b.quarter_start,quarter_days,
    CAST((b.spent_money) AS money)/CAST((dates.quarter_days) AS money) AS [actual_per_day],
    gates.gate,gates.cur_gate_date,gates.gate_start_date
    FROM 
    ((SELECT dt,
    CAST(DATEADD(q, DATEDIFF(q, 0, dt), 0) AS date) AS [quarter_start],      
    DATEDIFF(day,CAST(DATEADD(q, DATEDIFF(q, 0, dt), 0) AS date),DATEADD(d, -1, DATEADD(q, DATEDIFF(q, 0, dt) + 1, 0)))+1 AS 'quarter_days'
    FROM sample 
    ) dates
    LEFT JOIN budget b ON dates.quarter_start=b.quarter_start)
    LEFT JOIN
    (SELECT cte1.project,cte1.milestone, cte1.date as cur_gate_date,
    CAST(DATEADD(DAY,1,cte2.date) AS date) AS gate_start_date
    FROM cte_gates cte1
    LEFT JOIN cte_gates cte2 ON cte1.rownum=cte2.rownum+1 AND cte1.project=cte2.project) gates ON gates.project=b.project AND dt<=gates.cur_gate_date AND dt>=gates.gate_start_date
    ORDER BY b.project,dates.dt ASC
    OPTION (MAXRECURSION 10000)