Sql 按类型划分的每月余额
在SQL Server中,我有一个表,其中包含输入和输出值的多个记录,其中包含类型和日期列 诸如此类:Sql 按类型划分的每月余额,sql,sql-server,tsql,Sql,Sql Server,Tsql,在SQL Server中,我有一个表,其中包含输入和输出值的多个记录,其中包含类型和日期列 诸如此类: DATE |INPUT |OUTPUT |TYPE 2018-01-10 | 256.35| |A 2018-02-05 | | 35.00|B 2018-02-15 | 65.30| |A 2018-03-20 | 158.00| |B 2018-04-02 | | 63.32|B 2018-05-12 |
DATE |INPUT |OUTPUT |TYPE
2018-01-10 | 256.35| |A
2018-02-05 | | 35.00|B
2018-02-15 | 65.30| |A
2018-03-20 | 158.00| |B
2018-04-02 | | 63.32|B
2018-05-12 | | 128.12|A
2018-06-20 | | 7.35|B
我需要帮助进行查询,以按类型以余额形式返回输入和输出的总和,但它应在每个月底返回该总和,即:
YEAR|MONTH|TYPE|BALANCE
2018| 1|A | 256.35
2018| 1|B | 0.00
2018| 2|A | 321.65
2018| 2|B | -35.00
2018| 3|A | 321.65
2018| 3|B | 123.00
2018| 4|A | 321.65
2018| 4|B | 59.68
2018| 5|A | 193.53
2018| 5|B | 59.68
2018| 6|A | 193.53
2018| 6|B | 52.33
2018| 7|A | 193.53
2018| 7|B | 52.33
不要忘记,每个月的余额都会受到上个月余额的影响,或者换句话说,每个月的余额不仅是该月的变动,而且也是所有上个月的变动
还应注意的是,它应包括截至当前日期的一年/类型的每个月的记录,即使给定的月份/类型没有变动,从最早变动的第一个月/年开始,到2018年7月的实际日期结束。结果如下:
declare @min_month datetime=(select dateadd(month,datediff(month,0,min([DATE])),0) from _yourtable)
declare @max_month datetime=(select dateadd(month,datediff(month,0,max([DATE])),0) from _yourtable)
;WITH months(d) AS (
select @min_month
UNION ALL
SELECT dateadd(month,1,d) -- Recursion
FROM months
where dateadd(month,1,d)<=getdate()
)
select distinct
year(m.d) as YEAR,
month(m.d) as MONTH,
types.v as [TYPE]
,sum(isnull(t.[INPUT],0)-isnull(t.[OUTPUT],0)) over (partition by types.v order by m.d)
from months m
cross join (select distinct type from _yourtable)types(v)
left join _yourtable t on dateadd(month,datediff(month,0,t.[DATE]),0)=m.d and types.v=t.TYPE
order by m.d,type
option(maxrecursion 0)
您可以使用滞后函数,下面的代码可能会有所帮助:
select year(date), month(date), type
, sum(input-output) + isnull(lag(sum(input-output),1,0) over(order by year(date), month(date), type), 0)
from test group by year(date), month(date), type
假设您的源数据是您最初提供的结构,即:这不是另一个查询的结果,这是一个相当简单的转换,使用一个日期表和通过有序总和计算的运行总数 如果您已有日期表,则可以删除此脚本中的前两个CTE:
declare @t table(DateValue date,InputAmount decimal(8,2),OutputAmount decimal(8,2),ProdType nvarchar(1));
insert into @t values
('2018-01-10',256.35,null,'A')
,('2018-02-05',null, 35.00,'B')
,('2018-02-15', 65.30,null,'A')
,('2018-03-20',158.00,null,'B')
,('2018-04-02',null, 63.32,'B')
,('2018-05-12',null,128.12,'A')
,('2018-06-20',null, 7.35,'B')
;
-- Min date can just be min date in the source table, but the max date should be the month end of the max date in the source table0
declare @MinDate date = (select min(DateValue) from @t);
declare @MaxDate date = (select max(dateadd(day,-1,dateadd(month,datediff(month,0,DateValue)+1,0))) from @t);
with n(n) as (select * from (values(1),(1),(1),(1),(1),(1),(1),(1),(1),(1)) t(t)) -- Using a tally table, built a table of dates
,d(d) as (select top(select datediff(day,@MinDate,@MaxDate)+1) dateadd(day,row_number() over (order by (select null))-1,@MinDate) from n n1,n n2,n n3, n n4)
,m as (select p.ProdType -- Then join to the source data to create a date value for each posible day for each product type
,d.d
,dateadd(day,-1,dateadd(month,datediff(month,0,d)+1,0)) as m -- And calculate a running total using a windowed aggregate
,sum(isnull(t.InputAmount,0) - isnull(t.OutputAmount,0)) over (partition by p.ProdType order by d.d) as RunningTotal
from d
cross join (select distinct ProdType
from @t
) as p
left join @t as t
on d.d = t.DateValue
and p.ProdType = t.ProdType
)
select m
,ProdType
,RunningTotal as Balance
from m
where m = d
order by m.d
,m.ProdType;
输出:
+-------------------------+----------+---------+
| m | ProdType | Balance |
+-------------------------+----------+---------+
| 2018-01-31 00:00:00.000 | A | 256.35 |
| 2018-01-31 00:00:00.000 | B | 0.00 |
| 2018-02-28 00:00:00.000 | A | 321.65 |
| 2018-02-28 00:00:00.000 | B | -35.00 |
| 2018-03-31 00:00:00.000 | A | 321.65 |
| 2018-03-31 00:00:00.000 | B | 123.00 |
| 2018-04-30 00:00:00.000 | A | 321.65 |
| 2018-04-30 00:00:00.000 | B | 59.68 |
| 2018-05-31 00:00:00.000 | A | 193.53 |
| 2018-05-31 00:00:00.000 | B | 59.68 |
| 2018-06-30 00:00:00.000 | A | 193.53 |
| 2018-06-30 00:00:00.000 | B | 52.33 |
+-------------------------+----------+---------+
样本数据和期望的结果真的会有帮助。差不多,但有两个问题:一个,大问题,数值不正确。另一个,几个月没有结果,没有任何变化…尝试使用下面的查询,它会给你正确的结果。但是,它仍将返回表中有数据的年/月/类型的数据。选择yeardate,monthdate,type,sumisnullinput,0-isnulloutput,0+isnulllagsumisnullinput,0-isnulloutput,0,1,0按类型超额分配按yeardate排序,monthdate,type,0从测试组按yeardate排序,monthdate,type值仍然错误。。。其他问题没有问题,但数学需要完美。感谢@Alankrit向我介绍滞后函数。仔细观察之后,我注意到,在没有某种类型的移动的月份之后,滞后函数返回到零,而不是获取最后一个值。所以数学是不对的,就像我说的。。。有什么办法克服这个问题吗?谢谢@George Menoutis,它几乎完美无瑕!这些值是正确的,但在某些月份,它会返回同一类型的多个记录。啊,对。尝试在select之后添加distinct-这是一种惰性编码,可能会导致严重的性能问题。select distinct语句非常完美。有趣的是,您对distinct进行了评论,但您提供了一个同样的代码。在OP的示例中,没有共享相同类型和monthdate的行。然而,OP的数据库中显然存在这种情况,这就是多行的来源。你是如何阻止的?我已经尝试过这个查询,它也起作用了。然而我不认为这是最好的答案,因为它不返回年/月,而是月的最后一天的日期,而且在我看来,在我的数据库中执行比查询@乔治需要更多的时间。@ GoorMeMeNorTIS遵循代码,你会发现每种类型都是为每个日期计算的,然后总计。直到月底。如果您看不到这是如何工作的,请在中添加一些额外的虚拟数据,然后自己测试。尽管看起来很相似,但我的代码实际上与您的代码大不相同。@PJLG如果您需要年和月,您可以只使用年和月函数。然而,在实际操作中,当您想要表示日期值时,很少不希望实际返回日期值。