【问题标题】:SQL to get an daily average from month totalSQL从月总数中获取每日平均值
【发布时间】:2011-12-01 05:53:34
【问题描述】:

我有一个列出月份总计(目标)的表格

person      total                 month       
----------- --------------------- ----------- 
1001        114.00                201005      
1001        120.00                201006      
1001        120.00                201007      
1001        120.00                201008      
.
1002        114.00                201005      
1002        222.00                201006      
1002        333.00                201007      
1002        111.00                201008      
.
.

但是月份是一个整数(!)

我还有另一个包含工作日列表(日历)的表格

tran_date               day_type
----------------------- ---------------------------------
1999-05-01 00:00:00.000 WEEKEND
1999-05-02 00:00:00.000 WEEKEND
1999-05-03 00:00:00.000 WORKING_DAY
1999-05-04 00:00:00.000 WORKING_DAY

1999-06-01 00:00:00.000 .....
.
.
.

我想要做的是根据当月的 day_type 为“WORKING_DAY”的天数/当月的总天数,获取包含该天平均值的日期列表。

所以如果我在 201005 年说 20 个工作日,那么我每个工作日平均得到 114/20,而其他天为 0。

有点像

person   tran_date               day_avg
-------  ----------------------- ---------------------------------
1001     2010-05-01 00:00:00.000 0
1001     2010-05-02 00:00:00.000 0
1001     2010-05-03 00:00:00.000 114/2 (as there are two working days)
1001     2010-05-04 00:00:00.000 114/2 (as there are two working days)
.
.
.

它必须作为 CTE 完成,因为这是目标系统的限制(我只能做一个声明) 我可以从(日期到

WITH 
Dates AS
(
    SELECT CAST('19990501' as datetime) TRAN_DATE
    UNION ALL
    SELECT TRAN_DATE + 1
    FROM Dates
    WHERE TRAN_DATE + 1 <= CAST('20120430' as datetime)
),
Targets as
(
   select CAST(cast(month as nvarchar) + '01' as dateTime) mon_start, 
            DATEADD(MONTH, 1, CAST(cast(month as nvarchar) + '01' as dateTime)) mon_end, 
             total
   from targets
)
select ????

【问题讨论】:

    标签: sql group-by common-table-expression


    【解决方案1】:

    样本数据(可能有所不同):

    select * into #totals from (
    select '1001' as person, 114.00  as total, 199905 as month union
    select '1001', 120.00, 199906 union
    select '1001', 120.00, 199907 union
    select '1001', 120.00, 199908  
    
    ) t
    
    select * into #calendar from (
    select cast('19990501' as datetime) as tran_date, 'WEEKEND' as day_type union
    select '19990502', 'WEEKEND' union
    select '19990503', 'WORKING_DAY' union
    select '19990504', 'WORKING_DAY' union
    select '19990505', 'WORKING_DAY' union
    select '19990601', 'WEEKEND' union
    select '19990602', 'WORKING_DAY' union
    select '19990603', 'WORKING_DAY' union
    select '19990604', 'WORKING_DAY' union
    select '19990605', 'WORKING_DAY' union
    select '19990606', 'WORKING_DAY' union
    select '19990701', 'WORKING_DAY' union
    select '19990702', 'WEEKEND' union
    select '19990703', 'WEEKEND' union
    select '19990704', 'WORKING_DAY' union
    select '19990801', 'WORKING_DAY' union
    select '19990802', 'WORKING_DAY' union
    select '19990803', 'WEEKEND' union
    select '19990804', 'WEEKEND' union
    select '19990805', 'WORKING_DAY' union
    select '19990901', 'WORKING_DAY'
    ) t
    

    Select 语句,如果calendar 表中不存在日期,则返回 0。请记住,MAXRECURSION 是一个介于 0 和 32,767 之间的值。

    ;with dates as ( 
        select cast('19990501' as datetime) as tran_date 
        union all 
        select dateadd(dd, 1, tran_date) 
        from dates where dateadd(dd, 1, tran_date) <= cast('20010101' as datetime) 
    ) 
    select t.person , d.tran_date, (case when wd.tran_date is not null then t.total / w_days else 0 end) as day_avg 
    from dates d 
    left join #totals t on  
        datepart(yy, d.tran_date) * 100 + datepart(mm, d.tran_date) = t.month 
    left join ( 
            select datepart(yy, tran_date) * 100 + datepart(mm, tran_date) as month, count(*) as w_days 
            from #calendar 
            where day_type = 'WORKING_DAY' 
            group by datepart(yy, tran_date) * 100 + datepart(mm, tran_date) 
    ) c on t.month = c.month  
    left join #calendar wd on d.tran_date = wd.tran_date and wd.day_type = 'WORKING_DAY' 
    where t.person is not null
    option(maxrecursion 20000) 
    

    【讨论】:

    • 我更新了您的答案以消除不需要的结果。谢谢你 - 这是完美的!
    【解决方案2】:

    您可以在子查询中计算每月的工作日数。只有子查询必须使用group by。例如:

    select   t.person
    ,        wd.tran_date
    ,        t.total / m.WorkingDays as day_avg
    from     @Targets t
    join     @WorkingDays wd
    on       t.month =  convert(varchar(6), wd.tran_date, 112) 
    left join
            (
            select  convert(varchar(6), tran_date, 112) as Month
            ,       sum(case when day_type = 'WORKING_DAY' then 1 end) as WorkingDays
            from    @WorkingDays
            group by
                    convert(varchar(6), tran_date, 112)
            ) as  m
    on      m.Month = t.month
    

    Working example at SE Data.
    convert中的“幻数”112见the MSDN page

    【讨论】:

    • 好答案 - 我必须选择另一个,因为这省略了不应用平均值的日子(非工作日)。谢谢想法,我从这个答案中学到了新技巧。
    【解决方案3】:

    如果我正确理解了您的问题,则应该使用以下查询:

    SELECT
        *,
        ISNULL(
            (
                SELECT total
                FROM targets
                WHERE
                    MONTH(tran_date) = month - ROUND(month, -2)
                    AND c1.day_type = 'WORKING_DAY'
            ) /
            (
                SELECT COUNT(*)
                FROM calendar c2
                WHERE
                    MONTH(c1.tran_date) = MONTH(c2.tran_date)
                    AND c2.day_type = 'WORKING_DAY'
            ),
            0
        ) day_avg
    FROM
        calendar c1
    

    简单的英语:

    • 对于calendar 中的每一行,
    • 如果该行是工作日,则获取对应月份的合计(否则为NULL),
    • 获取当月工作日天数
    • 然后划分它们。
    • 最后,将 NULL(非工作日)转换为 0。

    【讨论】:

    • 谢谢,但是当我有多个不同的人时,这会失败。对不起我的问题我应该说清楚。不错的尝试。我从解释中学到了很多东西。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-07-14
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多