Sql 按类型划分的每月余额

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 |

在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 |       | 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如果您需要年和月,您可以只使用年和月函数。然而,在实际操作中,当您想要表示日期值时,很少不希望实际返回日期值。