TSQL-用总计填写缺失的月份

时间:2020-11-09 19:58:12

标签: sql-server tsql sum

我的表“金额”提供每月总计,但不是每月总计:

Employee  Reference_no  YearMonth  Amount
1         1             202001     400
1         1             202002     600
1         1             202005     250
1         2             202001     100
1         2             202003     700   

加入默认的“日历”表后,我可以显示缺少的月份:

Employee  Reference_no  YearMonth  Amount
1         1             202001     400
1         1             202002     600
1         1             202003     NULL
1         1             202004     NULL
1         1             202005     250
1         2             202001     100
1         2             202002     NULL
1         2             202003     700 

很遗憾,缺少总计,但应从上一个填充月份将其转移。因此,对于202003年和202004年(Ref_no 1),该数量应为600。对于202002年(Ref_no_2),该数量应为100。

我已经苦苦挣扎很长时间了,但似乎无法解决这个问题。对于Amount字段解决方案,我发现OUTER APPLY TOP(1)和SELECT TOP(1)ORDER BY,但是由于数据集很大,因此在尝试运行时查询会卡住。

任何建议都将受到赞赏!

4 个答案:

答案 0 :(得分:2)

使用查询创建一个CTE,然后为每行返回最后一个YearMonth和非null Amount的CTE。
然后加入CTE:

WITH 
  query AS (............), -- this is your query
  cte AS (
    SELECT *,
      MAX(CASE WHEN [Amount] IS NOT NULL THEN [YearMonth] END) OVER(
        PARTITION BY [Employee], [Reference_no]
        ORDER BY [YearMonth] 
        ROWS UNBOUNDED PRECEDING
      ) AS n
    FROM query  
  )
SELECT c.[Employee], c.[Reference_no], c.[YearMonth],
       COALESCE(c.[Amount], q.[Amount]) [Amount]
FROM cte c LEFT JOIN query q
ON q.[Employee] = c.[Employee] AND q.[Reference_no] = c.[Reference_no] AND q.[YearMonth] = c.n
ORDER BY c.[Employee], c.[Reference_no], c.[YearMonth]

请参见demo

答案 1 :(得分:2)

您需要做的就是从“金额”中转换行,以使它们涵盖有效范围,然后将其加入日历表。

行将在YearMonth列中隐式包含开始日期。要获取截止日期,可以使用 LEAD 窗口功能按顺序从下一行获取日期。如果没有下一行,则只需将1添加到当前行的YearMonth中,以便仅使用该行。

SELECT amt.Employee, amt.Reference_no, cal.YearMonth, amt.Amount
FROM (
  SELECT *, 
    LEAD(YearMonth, 1, YearMonth + 1)
      OVER (PARTITION BY Employee, Reference_no ORDER BY YearMonth) [Cutoff]
  FROM Amount
) amt
INNER JOIN Calendar cal ON cal.YearMonth >= amt.YearMonth AND cal.YearMonth < amt.Cutoff

答案 2 :(得分:0)

注意:我稍微扩大了输出范围,以说明需要处理雇员上个月没有累积总额的情况,例如一年中开始有新员工。

下面的方法不是很优雅,因为它不使用窗口函数,但是如果您不熟悉窗口函数,它可能会更直观。

第一部分只是设置数据。我已经包含了它,以便脚本独立存在,您可以使用它。跳至cte_padded_employee_totals以获得第一条实际答案。

内联函数(OUTER APPLY)获取上一行,该行具有每个员工/ reference_no对的金额值。


DECLARE @vd_start_calendar_date DATE = '20191101'
DECLARE @vd_end_calendar_date   DATE = '20200801'

DECLARE @t_calendar TABLE
(   first_of_month  DATE,
    year_mth_string CHAR(6)
)

DECLARE @t_cumulative_employee_totals_by_month TABLE
(   employee    INT,
    ref_no      INT,
    year_mth    CHAR(6),
    amount      INT
)

INSERT INTO @t_cumulative_employee_totals_by_month
(   employee    ,
    ref_no      ,
    year_mth    ,
    amount      
)
VALUES
    (1,1,'202001',400),
    (1,1,'202002',600),
    (1,1,'202005',250),
    (1,2,'202001',100),
    (1,2,'202003',700) 

 ;WITH cte_gen_month_data
    AS (SELECT first_of_month = @vd_start_calendar_date
 UNION ALL
        SELECT first_of_month = DATEADD(month, 1, first_of_month)
          FROM cte_gen_month_data
         WHERE first_of_month < @vd_end_calendar_date
       )
INSERT INTO @t_calendar
(      first_of_month,
       year_mth_string
)
SELECT first_of_month,
       CONVERT(CHAR(4), YEAR(first_of_month)) + RIGHT('0' + CONVERT(VARCHAR(2), MONTH(first_of_month)), 2)
  FROM cte_gen_month_data

 -- I have used a common table expression here because we want to refer to the same result set more than once.
;WITH cte_padded_employee_totals
   AS (SELECT ce.*, 
              et.amount
         FROM (SELECT * 
                 FROM @t_calendar c
                 CROSS JOIN (SELECT DISTINCT employee, ref_no
                               FROM @t_cumulative_employee_totals_by_month) e
              ) ce
         LEFT JOIN 
              @t_cumulative_employee_totals_by_month et ON ce.year_mth_string = et.year_mth
                                                       AND ce.employee        = et.employee
                                                       AND ce.ref_no          = et.ref_no
       )
SELECT pt.employee,
       pt.ref_no,
       pt.year_mth_string,
       amount = COALESCE(pt.amount, mt.amount, 0)
  FROM cte_padded_employee_totals pt
 OUTER APPLY 
       (SELECT TOP 1 amount = et.amount
          FROM cte_padded_employee_totals et
         WHERE et.employee = pt.employee
           AND et.ref_no = pt.ref_no
           AND et.first_of_month < pt.first_of_month
           AND et.amount IS NOT NULL
           AND pt.amount IS NULL
         ORDER BY first_of_month DESC
        ) mt -- missing totals

答案 3 :(得分:0)

我认为您想从先前的非空值中填充空值,对。尝试这种方式,看看它是否满足您的要求。

CREATE TABLE mytest (YearMonth Varchar(10) , Amount int)
INSERT INTO mytest values('202001', 400)
INSERT INTO mytest values('202002', 600)
INSERT INTO mytest values('202003', NULL)
INSERT INTO mytest values('202004', NULL)
INSERT INTO mytest values('202005', 250)
INSERT INTO mytest values('202006', 100)
INSERT INTO mytest values('202007', NULL)
INSERT INTO mytest values('202008', 700)


select * from mytest

enter image description here

SELECT  yearMonth,
        ISNULL(Amount, (SELECT TOP 1 Amount FROM mytest WHERE YearMonth < t.YearMonth AND Amount IS NOT NULL ORDER BY YearMonth DESC))
from mytest t

enter image description here