我的表“金额”提供每月总计,但不是每月总计:
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,但是由于数据集很大,因此在尝试运行时查询会卡住。
任何建议都将受到赞赏!
答案 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
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