计算天数并平均分配剩余天数

Ker*_*mit 6 sql-server

我在计算给定时间段内在日期范围内分布的小时数计算剩余天数时遇到了困难。对于此示例,时间段将是 2013-11-01 到 2013-05-01(它始终是 6 个月的时间段)。

这张calendar桌子有一个 30 年的套装。11 for 的值weekday表示周一至周五。

设置

CREATE TABLE #calendar (
  [d] date PRIMARY KEY,
  [weekday] tinyint DEFAULT 0,
);

INSERT INTO #calendar ([d]) VALUES
('2006-01-01', 0),
...
('2013-01-01', 1),
('2013-01-02', 1),
('2013-01-03', 1),
...
('2014-12-28', 0),
('2014-12-29', 1),
('2014-12-30', 1),
('2014-12-31', 1),
...
('2035-12-31', 1);

CREATE TABLE #tasks (
  [task] int PRIMARY KEY,
  [begin] date,
  [end] date,
  [hours] decimal(6,3)
);

INSERT INTO #tasks ([task], [begin], [end], [hours]) VALUES
(1, '2013-11-05', '2013-11-05', 5.0),
(2, '2013-10-01', '2013-11-15', 2.0),
(3, '2013-11-15', '2013-12-31', 80.0),
(4, '2013-05-01', '2013-12-04', 2.0),
(5, '2013-07-01', '2013-11-27', 15),
(6, '2013-11-01', '2013-12-10', 40),
(7, '2013-07-01', '2013-08-01', 50.0),
(8, '2014-06-01', '2014-07-01', 10.0);
Run Code Online (Sandbox Code Playgroud)

第1部分

获取任务的总工作日:

DECLARE @periodStart date, @periodEnd date;

SET @periodStart = '2013-11-01';
SET @periodEnd = '2014-05-01';

SELECT a.[task], a.[begin], a.[end], a.[hours],
    (SELECT SUM([weekday])
    FROM #calendar
    WHERE [d] BETWEEN a.[begin] AND a.[end]) AS weekdays
FROM #tasks a
WHERE (a.[begin] < @periodStart AND a.[end] > @periodEnd)
    OR (a.[begin] >= @periodStart AND a.[end] < @periodEnd)
    OR (a.[begin] < @periodEnd AND a.[end] > @periodStart)
Run Code Online (Sandbox Code Playgroud)

结果

任务开始结束时间工作日
----------- ---------- ---------- ------- --------
1 2013-11-05 2013-11-05 5.000 1
2 2013-10-01 2013-11-15 2.000 34
3 2013-11-15 2013-12-31 80.000 33
4 2013-05-01 2013-12-04 2.000 156
5 2013-07-01 2013-11-27 15.000 108
6 2013-11-01 2013-12-10 40.000 28

第2部分

获取该时间段的工作日:

DECLARE @periodStart date, @periodEnd date;

SET @periodStart = '2013-11-01';
SET @periodEnd = '2014-05-01';

SELECT a.[task], a.[begin], a.[end], a.[hours],
    (SELECT SUM([weekday])
    FROM #calendar
    WHERE [d] BETWEEN a.[begin] AND a.[end]) AS weekdays,
    m1.weekdays AS m1wd,
    m2.weekdays AS m2wd,
    m3.weekdays AS m3wd,
    m4.weekdays AS m4wd,
    m5.weekdays AS m5wd,
    m6.weekdays AS m6wd
FROM #tasks a
CROSS JOIN
    -- Month 1 Weekdays
    (SELECT CAST(SUM([weekday]) AS decimal(3,1)) AS weekdays FROM #calendar
    WHERE [d] BETWEEN DATEADD(MONTH, DATEDIFF(MONTH, 0, @periodStart)+1, 0) 
        AND DATEADD(MONTH, DATEDIFF(MONTH, 0, @periodStart)+2, -1)) m1
CROSS JOIN
    -- Month 2 Weekdays
    (SELECT CAST(SUM([weekday]) AS decimal(3,1)) AS weekdays FROM #calendar
    WHERE [d] BETWEEN DATEADD(MONTH, DATEDIFF(MONTH, 0, @periodStart)+2, 0) 
        AND DATEADD(MONTH, DATEDIFF(MONTH, 0, @periodStart)+3, -1)) m2
CROSS JOIN
    -- Month 3 Weekdays
    (SELECT CAST(SUM([weekday]) AS decimal(3,1)) AS weekdays FROM #calendar
    WHERE [d] BETWEEN DATEADD(MONTH, DATEDIFF(MONTH, 0, @periodStart)+3, 0) 
        AND DATEADD(MONTH, DATEDIFF(MONTH, 0, @periodStart)+4, -1)) m3
CROSS JOIN
    -- Month 4 Weekdays
    (SELECT CAST(SUM([weekday]) AS decimal(3,1)) AS weekdays FROM #calendar
    WHERE [d] BETWEEN DATEADD(MONTH, DATEDIFF(MONTH, 0, @periodStart)+4, 0) 
        AND DATEADD(MONTH, DATEDIFF(MONTH, 0, @periodStart)+5, -1)) m4
CROSS JOIN
    -- Month 5 Weekdays
    (SELECT CAST(SUM([weekday]) AS decimal(3,1)) AS weekdays FROM #calendar
    WHERE [d] BETWEEN DATEADD(MONTH, DATEDIFF(MONTH, 0, @periodStart)+5, 0) 
        AND DATEADD(MONTH, DATEDIFF(MONTH, 0, @periodStart)+6, -1)) m5
CROSS JOIN
    -- Month 6 Weekdays
    (SELECT CAST(SUM([weekday]) AS decimal(3,1)) AS weekdays FROM #calendar
    WHERE [d] BETWEEN DATEADD(MONTH, DATEDIFF(MONTH, 0, @periodStart)+6, 0) 
        AND DATEADD(MONTH, DATEDIFF(MONTH, 0, @periodStart)+7, -1)) m6
WHERE (a.[begin] < @periodStart AND a.[end] > @periodEnd)
    OR (a.[begin] >= @periodStart AND a.[end] < @periodEnd)
    OR (a.[begin] < @periodEnd AND a.[end] > @periodStart)
Run Code Online (Sandbox Code Playgroud)

结果

任务开始结束时间工作日 m1wd m2wd m3wd m4wd m5wd m6wd
------ ---------- ---------- ------- ----------- ----- - ---- ----- ----- ----- -----
1 2013-11-05 2013-11-05 5.000 1 22.0 23.0 20.0 21.0 22.0 22.0
2 2013-10-01 2013-11-15 2.000 34 22.0 23.0 20.0 21.0 22.0 22.0
3 2013-11-15 2013-12-31 80.000 33 22.0 23.0 20.0 21.0 22.0 22.0
4 2013-05-01 2013-12-04 2.000 156 22.0 23.0 20.0 21.0 22.0 22.0
5 2013-07-01 2013-11-27 15.000 108 22.0 23.0 20.0 21.0 22.0 22.0
6 2013-11-01 2013-12-10 40.000 28 22.0 23.0 20.0 21.0 22.0 22.0

问题

由于每个任务可以在不同时间开始/结束,我如何计算和分配月份中剩余天数的小时数?在第 2 部分中,是否只返回一个包含工作日的期间日期之间的表格,然后对其进行透视会更容易吗?

想要的结果

任务开始结束时间工作日 m1h m2h ... m6h
------ ---------- ---------- ------- ----------- ------ -- -------- ----- ---
1 2013-11-05 2013-11-05 5.000 1 5         
2 2013-10-01 2013-11-15 2.000 34 0.647059  
3 2013-11-15 2013-12-31 80.000 33 26.66667 53.33333
4 2013-05-01 2013-12-04 2.000 156 0.269231 0.038462
5 2013-07-01 2013-11-27 15.000 108 2.638889  
6 2013-11-01 2013-12-10 40.000 28 30 10

1 生成一个没有循环的集合或序列

Adr*_*ell 2

希望这对你有用。我相信它符合您的预期结果:

| TASK |      BEGIN |        END | HOURS | WEEKDAYS |             M1H |             M2H |    M3H |    M4H |    M5H |    M6H |
|------|------------|------------|-------|----------|-----------------|-----------------|--------|--------|--------|--------|
|    1 | 2013-11-05 | 2013-11-05 |     5 |        1 |               5 |          (null) | (null) | (null) | (null) | (null) |
|    2 | 2013-10-01 | 2013-11-15 |     2 |       34 |  0.647058823529 |          (null) | (null) | (null) | (null) | (null) |
|    3 | 2013-11-15 | 2013-12-31 |    80 |       33 | 26.666666666667 | 53.333333333333 | (null) | (null) | (null) | (null) |
|    4 | 2013-05-01 | 2013-12-04 |     2 |      156 |  0.269230769231 |  0.038461538462 | (null) | (null) | (null) | (null) |
|    5 | 2013-07-01 | 2013-11-27 |    15 |      108 |  2.638888888889 |          (null) | (null) | (null) | (null) | (null) |
|    6 | 2013-11-01 | 2013-12-10 |    40 |       28 |              30 |              10 | (null) | (null) | (null) | (null) |


DECLARE @periodStart date, @periodEnd date

SET @periodStart = '2013-11-01'
SET @periodEnd   = '2014-05-01'

SELECT
          [task]
        , [begin]
        , [end]
        , [hours]
        , max(task_wd) AS weekdays
        , max( CASE WHEN yrmnth = ( YEAR(@periodStart) * 100 + MONTH(@periodStart) )                                   THEN ([hours] / task_wd) * mnth_wd END ) AS m1h
        , max( CASE WHEN yrmnth = ( YEAR(dateadd(MONTH,1,@periodStart)) * 100 + MONTH(dateadd(MONTH,1,@periodStart)) ) THEN ([hours] / task_wd) * mnth_wd END ) AS m2h
        , max( CASE WHEN yrmnth = ( YEAR(dateadd(MONTH,2,@periodStart)) * 100 + MONTH(dateadd(MONTH,2,@periodStart)) ) THEN ([hours] / task_wd) * mnth_wd END ) AS m3h
        , max( CASE WHEN yrmnth = ( YEAR(dateadd(MONTH,3,@periodStart)) * 100 + MONTH(dateadd(MONTH,3,@periodStart)) ) THEN ([hours] / task_wd) * mnth_wd END ) AS m4h
        , max( CASE WHEN yrmnth = ( YEAR(dateadd(MONTH,4,@periodStart)) * 100 + MONTH(dateadd(MONTH,4,@periodStart)) ) THEN ([hours] / task_wd) * mnth_wd END ) AS m5h
        , max( CASE WHEN yrmnth = ( YEAR(dateadd(MONTH,5,@periodStart)) * 100 + MONTH(dateadd(MONTH,5,@periodStart)) ) THEN ([hours] / task_wd) * mnth_wd END ) AS m6h
FROM (
        SELECT
                  t.[task]
                , t.[begin]
                , t.[end]
                , t.[hours]
                , (year(c.d) * 100 + MONTH(c.d))                                 AS yrmnth

                , count(CASE
                         WHEN c.d >= @periodStart AND c.d < @periodEnd THEN c.d
                        END
                       ) over (partition BY t.[task], year(c.d), MONTH(c.d))     AS mnth_wd
                , count(c.d) over (partition BY t.[task]) * 1.0                  AS task_wd
        FROM Calendar AS c
        INNER JOIN tasks AS t ON c.d BETWEEN t.[begin] and t.[end]
        WHERE c.weekday = 1
        AND t.[begin] <= @periodEnd
        AND t.[end] >= @periodStart
     ) AS derived
GROUP BY
          [task]
        , [begin]
        , [end]
        , [hours]
Run Code Online (Sandbox Code Playgroud)

我在这里做了这个http://sqlfiddle.com/#!3/682ff/42,但我也用 CTE 模拟了你的日历表,所以那里的代码有点混乱。