Running Totals Skipping Over Certain Values - sql-server

I need to calculate a column that has a value column subtracted from a Total column but can skip rows until it can no longer find a smaller value. The sequence relates to dates so the order must be preserved. The value (Need) cannot be larger than the total as those are deleted prior.
This is for SQL Server 2016. My initial thought process was to use window functions and a running total, but I cannot figure out how to skip the 400 and continue to the 2 rows below. I included my attempts in the CASE statement as TransferQty and the running total as ReferenceCol.
Code to reproduce:
DECLARE #i TABLE
(
sequence INT IDENTITY(1,1)
,Total INT
,Need INT
)
INSERT INTO #i
VALUES (500,100)
,(500,200)
,(500,50)
,(500,400)
,(500,50)
,(500,50)
SELECT
sequence
,Total
,Need
,CASE
WHEN Total - SUM(Need) OVER (ORDER BY sequence) > 0
THEN Need
ELSE 0
END AS TransferQty
,Total - SUM(Need) OVER (ORDER BY sequence) as ReferenceCol
FROM #i
Current Results
+----------+-------+------+-------------+--------------+
| Sequence | Total | Need | TransferQty | ReferenceCol |
+----------+-------+------+-------------+--------------+
| 1 | 500 | 100 | 100 | 400 |
| 2 | 500 | 200 | 200 | 200 |
| 3 | 500 | 50 | 50 | 150 |
| 4 | 500 | 400 | 0 | -250 |
| 5 | 500 | 50 | 0 | -300 |
| 6 | 500 | 50 | 0 | -350 |
+----------+-------+------+-------------+--------------+
Desired Results
+----------+-------+------+-------------+--------------+
| Sequence | Total | Need | TransferQty | ReferenceCol |
+----------+-------+------+-------------+--------------+
| 1 | 500 | 100 | 100 | 400 |
| 2 | 500 | 200 | 200 | 200 |
| 3 | 500 | 50 | 50 | 150 |
| 4 | 500 | 400 | 0 | 150 | --skip calc
| 5 | 500 | 50 | 50 | 100 |
| 6 | 500 | 50 | 50 | 50 |
+----------+-------+------+-------------+--------------+

You should be able to use this code if you have a single skip, but when you have multi skips then you have to loop through and perform the delete of record based on the existence of rolling value exceeding the total.
DECLARE #i TABLE
(
sequence INT IDENTITY(1,1)
,Total INT
,Need INT
)
INSERT INTO #i
VALUES
(500,100 )
,(500,200 )
,(500,50 )
,(500,400 )
,(500,50 )
,(500,50 )
select sequence,Total,Need
into #temp_original
from #i
select
b.sequence,b.Total, SUM( a.need) rollingvalue ,
case when SUM( a.need) > b.Total
then 0
when SUM( a.need) = b.Total then SUM( a.need)
else b.Total - SUM( a.need) end how_much_needed
into #temp
from #i a
join #i b
on a.sequence < b.sequence + 1
group by b.sequence,b.Total
delete from a
from #i a
join (
select min(sequence) min_sequence
from #temp
where how_much_needed = 0
) minseq
on minseq.min_sequence = a.sequence
select
b.sequence,b.Total, SUM( a.need) rollingvalue ,
case when SUM( a.need) > b.Total
then 0
when SUM( a.need) = b.Total then SUM( a.need)
else b.Total - SUM( a.need) end how_much_needed
into #temp2
from #i a
join #i b
on a.sequence < b.sequence + 1
group by b.sequence,b.Total
select a.sequence,a.Total,a.Need, case when isnull (b.rollingvalue , 0) = 0 then 0 else case when b.rollingvalue > a.Total then 0 else a.Need end end as TransferQty , ISNULL( case when b.how_much_needed = b.Total then a.Need else b.how_much_needed end, case when ( select how_much_needed from #temp2 where sequence = a.sequence -1) = a.Total then 0 else (select how_much_needed from #temp where sequence = a.sequence -1) end ) ReferenceCol
from #temp_original a
LEFT join #temp2 b
on a.sequence = b.sequence
join #temp c
on c.sequence = a.sequence
drop table #temp
drop table #temp2
drop table

Here is the solution I went with which is based off of the "Quirky Update" from the original comment.
DROP TABLE IF EXISTS #i
GO
CREATE TABLE #i
(
sequence INT IDENTITY(1,1) PRIMARY KEY CLUSTERED
,Total INT
,Need INT
,RunningTransfer INT NULL
)
INSERT INTO #i
VALUES
(500,100,NULL)
,(500,200,NULL)
,(500,50,NULL)
,(500,400,NULL)
,(500,50,NULL)
,(500,50,NULL)
,(500,100,NULL)
,(500,49,NULL)
,(500,50,NULL)
DECLARE #TransferRunningTotal INT
UPDATE #i
SET #TransferRunningTotal = RunningTransfer = CASE
--this skips values larger than running total
WHEN #TransferRunningTotal < Need THEN #TransferRunningTotal
--this creates the running total
WHEN #TransferRunningTotal > Need THEN #TransferRunningTotal - Need
--creates the initial value
ELSE Total - Need
END
FROM #i WITH (TABLOCKX)
OPTION (MAXDOP 1)
SELECT sequence
,Total
,Need
,CASE
WHEN need <= RunningTransfer THEN Need
ELSE 0
END AS TsfQty
,RunningTransfer
FROM #i

Related

How to make autoincrement value for each customer SQL Query

I want to increment InvoiceNumber for every Customer in format "customer/invoicenumber" and make it as a trigger (every time when I add some data, It should add InvoiceNumber.
CustomerID | Price | InvoiceNumber |
1 | 100 | 1/1 |
1 | 200 | 1/2 |
1 | 250 | 1/3 |
2 | 400 | 2/1 |
2 | 100 | 2/2 |
3 | 20 | 3/1 |
4 | 10 | 4/1 |
5 | 1 | 5/1 |
During inserting customer information , you need to insert column 'InvoiceNumber' as null. Then below trigger will update Invoicenumber with new number like 1/2 ...etc
CREATE TRIGGER trig1 ON triggertest
AFTER INSERT
AS
BEGIN
SET NOCOUNT ON;
DECLARE #CustomerID INT;
DECLARE #MaxID INT;
SELECT #CustomerID = INSERTED.CustomerID FROM INSERTED;
SELECT #MaxID=(select ISNULL(rn,0) from
(select top 1 CustomerID, ROW_NUMBER() OVER(order by CustomerID) as rn from [triggertest] where CustomerID=#CustomerID
order by rn desc) t)
UPDATE triggertest SET InvoiceNumber=CAST(#CustomerID as nvarchar)+'/'+CAST(#MaxID as nvarchar) WHERE CustomerID=#CustomerID and InvoiceNumber is null;
END

how to subtract values in consecutive rows?

I have following table with some data.
CREATE TABLE #NetProfit (ID int, [Name] varchar(50),[Class] varchar(50), Balance money)
go
--Populate Sample records
INSERT INTO #NetProfit VALUES(4,'Income','No Class',303386.8462)
INSERT INTO #NetProfit VALUES(6,'Expenses','No Class',22443.5317)
INSERT INTO #NetProfit VALUES(4,'Income','2 TestUser3',0.00)
INSERT INTO #NetProfit VALUES(5,'Cost','2 TestUser3',0.3875)
INSERT INTO #NetProfit VALUES(6,'Expenses','2 TestUser3',6439.2129)
INSERT INTO #NetProfit VALUES(5,'Cost','3 TestUser3',0.1395)
INSERT INTO #NetProfit VALUES(6,'Expenses','3 TestUser3',6451.6129)
INSERT INTO #NetProfit VALUES(5,'Cost','38 Code#1012',3.0225)
INSERT INTO #NetProfit VALUES(6,'Expenses','38 Code#1012',30.225)
go
select * from #NetProfit
drop table #NetProfit
+----+----------+--------------+-------------+
| ID | Name | Class | Balance |
+----+----------+--------------+-------------+
| 4 | Income | No Class | 303386.8462 |
| 6 | Expenses | No Class | 22443.5317 |
| 4 | Income | 2 TestUser3 | 0 |
| 5 | Cost | 2 TestUser3 | 0.3875 |
| 6 | Expenses | 2 TestUser3 | 6439.2129 |
| 5 | Cost | 3 TestUser3 | 0.1395 |
| 6 | Expenses | 3 TestUser3 | 6451.6129 |
| 5 | Cost | 38 Code#1012 | 3.0225 |
| 6 | Expenses | 38 Code#1012 | 30.225 |
+----+----------+--------------+-------------+
I want to subtract [Balance] column row wise group by [Class] column.
For ex. NetProfit = (Income - Cost - Expenses) for each [Class] column with group by [Class].
Here is the output I am expecting.
+-------------+-------------+-------------+--------------+
| No Class | 2 TestUser3 | 3 TestUser3 | 38 Code#1012 |
+-------------+-------------+-------------+--------------+
| 280943.3145 | 6439.6004 | 6451.7524 | 33.2475 |
+-------------+-------------+-------------+--------------+
Any help would be appreciated. Thanks!
This could be the first step to achieve desired result:
SELECT NP.Class, ABS(ISNULL(SUM(CASE NP.Name WHEN 'Income' THEN NP.Balance END), 0) - SUM(CASE NP.Name WHEN 'Income' THEN NULL ELSE NP.Balance END)) AS Balance
FROM #NetProfit AS NP
GROUP BY NP.Class;
Then you would have to dynamically pivot these results. Statically, your query should look like that:
SELECT *
FROM (
SELECT NP.Class, ABS(ISNULL(SUM(CASE NP.Name WHEN 'Income' THEN NP.Balance END), 0) - SUM(CASE NP.Name WHEN 'Income' THEN NULL ELSE NP.Balance END)) AS Balance
FROM #NetProfit AS NP
GROUP BY NP.Class) AS SourceTable
PIVOT (MAX(Balance) FOR Class IN ([2 TestUser3], [3 TestUser3], [38 Code#1012], [No Class])) AS PivotTable;
So by plugging in dynamic SQL, This is the result:
DECLARE #SQL NVARCHAR(MAX);
DECLARE #Col NVARCHAR(MAX);
SET #Col = STUFF(( SELECT DISTINCT ',' + QUOTENAME(NP.Class)
FROM #NetProfit AS NP
FOR XML PATH('')), 1, 1, '');
SET #SQL = N'
SELECT *
FROM (
SELECT NP.Class, ABS(ISNULL(SUM(CASE NP.Name WHEN ''Income'' THEN NP.Balance END), 0) - SUM(CASE NP.Name WHEN ''Income'' THEN NULL ELSE NP.Balance END)) AS Balance
FROM #NetProfit AS NP
GROUP BY NP.Class) AS SourceTable
PIVOT (MAX(Balance) FOR Class IN (' + #Col + N')) AS PivotTable';
EXECUTE sys.sp_executesql #SQL;
That's the output:
+-------------+-------------+--------------+-------------+
| 2 TestUser3 | 3 TestUser3 | 38 Code#1012 | No Class |
+-------------+-------------+--------------+-------------+
| 6439,6004 | 6451,7524 | 33,2475 | 280943,3145 |
+-------------+-------------+--------------+-------------+
This query return calculation in rows.
With pivot table you can turn rows to columns
select isnull(c1, c2), isnull(i, 0)-isnull(e,0) from (
select * from
(select class c1, sum(balance) i
from #NetProfit
where Name = 'income'
group by class) i
full join
(
select class c2, sum(balance) e
from #NetProfit
where Name != 'income'
group by class) e on i.c1 = e.c2) t
Try this (assuming ID 4, for income, is a positive whereas the rest should be treated as negatives;
select
[Class No]=SUM(case when [Class]='No Class' then (case when ID=4 then Balance else Balance*-1 end) else 0 end),
[TestUser2]=SUM(case when [Class]='2 TestUser3' then (case when ID=4 then Balance else Balance*-1 end) else 0 end),
[TestUser3]=SUM(case when [Class]='3 TestUser3' then (case when ID=4 then Balance else Balance*-1 end) else 0 end),
[Code#1012]=SUM(case when [Class]='Code#1012' then (case when ID=4 then Balance else Balance*-1 end) else 0 end)
from
#NetProfit

Grouping by a same range of multiple values with sum and counts in SQL

I want to group different colons in same range by row. Example:
Amount1 | Amount2
------------------------
20,00 | 30,00
35,00 | 32,00
12,00 | 51,00
101,00 | 100,00
Here result should be;
Range |TotalAmount1 |TotalAmount2 | CountAmount1 | CountAmount2 | RateOfCountAmount1
-----------------------------------------------------------------------------
0-50 | 67,00 | 62,00 | 3 | 1 | %75
50-100 | 0,00 | 151,00 | 0 | 2 | %0
100+ | 101,00 | 0,00 | 1 | 0 | %25
Total | 168,00 | 213,00 | 4 | 3 | %100
Here is the example : http://sqlfiddle.com/#!9/05fd3
you can query like this
;with cte as (
select case when amount1 < 50 then '0-50'
when amount1 between 50.01 and 100 then '50-100'
when amount1 > 100 then '100+' end as rngamt1,
case when amount2 < 50 then '0-50'
when amount2 between 5.01 and 100 then '50-100'
when amount2 > 100 then '100+' end as rngamt2,
* from amounts
), cte2 as (select coalesce(rngamt1, rngamt2) as [Range], isnull(a.TotalAmount1,0) as TotalAmount1, isnull(b.TotalAmount2, 0) as TotalAmount2, isnull( a.TotalCount1 , 0) as TotalCount1, isnull(b.TotalCount2, 0) as Totalcount2 from
(select rngamt1, sum(amount1) TotalAmount1, count(amount1) TotalCount1 from cte c
group by rngamt1) a
full join
(select rngamt2, sum(amount2) TotalAmount2, count(amount2) TotalCount2 from cte c
group by rngamt2) b
on a.rngamt1 = b.rngamt2
)
select *, (TotalCount1 * 100 )/sum(TotalCount1) over () as RateCount1
from cte2
union
select 'Total' as [Range], sum(TotalAmount1) as TotalAmount1, sum(totalAmount2) as TotalAmount2,
sum(TotalCount1) as TotalCount1, sum(Totalcount2) as TotalCount2, (sum(TotalCount1)*100)/Sum(TotalCount1) as RateCount1 from cte2

Display Data Year Wise + Horizontal Format

I am Using SQLServer2008.
Below is my Stored Procedure
DECLARE #planTable TABLE
(
Year VARCHAR(20) ,
PlanTypeId INT
)
INSERT INTO #planTable
( Year ,
PlanTypeId
)
( SELECT DISTINCT
pm.Year ,
ptm.PlanTypeId
FROM dbo.PlanMaster AS pm
INNER JOIN dbo.PlanTypeMaster AS ptm ON pm.PlanTypeId = ptm.PlanTypeId
)
DECLARE #tmp TABLE
(
BrokerCode VARCHAR(20) ,
Year VARCHAR(20) ,
PlanType VARCHAR(20) ,
Amount DECIMAL(18, 2)
)
DECLARE #Year VARCHAR(20)
DECLARE #PlanTypeId INT
DECLARE c1 CURSOR READ_ONLY
FOR
SELECT pt.Year,pt.PlanTypeId FROM #planTable AS pt
OPEN c1
FETCH NEXT FROM c1
INTO #Year,#PlanTypeId
WHILE ##FETCH_STATUS = 0
BEGIN
INSERT INTO #tmp
( BrokerCode ,
Year ,
PlanType ,
Amount
)
( SELECT ( SELECT bm.BrokerCode
FROM dbo.BrokerMaster AS bm
WHERE BrokerId = 30
) ,
#Year ,
( CASE WHEN ( SELECT ptm.IsSingleInstallment
FROM dbo.PlanTypeMaster AS ptm
WHERE ptm.PlanTypeId = #PlanTypeId
) = 'true' THEN 'Single'
WHEN ( SELECT ptm.IsSingleInstallment
FROM dbo.PlanTypeMaster AS ptm
WHERE ptm.PlanTypeId = #PlanTypeId
) = 'false' THEN 'Multiple'
END ) ,
ISNULL(( SUM(SelfAmount) + SUM(UnitAmount) ), 0)
FROM dbo.MemberBusiness AS mb
INNER JOIN dbo.PlanMaster AS pm ON mb.PlanId = pm.PlanId
INNER JOIN dbo.PlanTypeMaster AS ptm2 ON pm.PlanTypeId = ptm2.PlanTypeId
WHERE mb.BrokerId = 30
AND pm.Year = #Year
AND ptm2.PlanTypeId = #PlanTypeId
)
FETCH NEXT FROM c1
INTO #Year,#PlanTypeId
END
CLOSE c1
DEALLOCATE c1
SELECT *
FROM #tmp
And result of this stored procedure is something like this
----------------------------------------------------
BrokerCode | Year | PlanType | Amount |
----------------------------------------------------
102 | 1 | Single | 100 |
----------------------------------------------------
102 | 2 | Single | 200 |
----------------------------------------------------
102 | 3 | Single | 300 |
----------------------------------------------------
102 | 1 | Multiple | 100 |
----------------------------------------------------
102 | 2 | Multiple | 200 |
----------------------------------------------------
102 | 3 | Multiple | 300 |
Now I want to result like this..
------------------------------------------------------------------------------------
Single | Multiple |
-------------------------------------------------------------------------------------
BrokerCode | 1 | 2 | 3 | 1 | 2 | 3 |
------------------------------------------------------------------------------------
102 | 100 | 200 | 300 | 100 | 200 | 300 |
------------------------------------------------------------------------------------
How can i get result like this?

cross apply in sql with sum

I have the following query:
insert into [MyDB].[dbo].[Reports_ActivityStat] (ActivityID,TaskID,LS_HowOthersAnswered,LS_ImproveMyChances)
(
SELECT
ActivityID=tasks.ActivityID,
TaskID=tasks.ID,
CAST(
CASE
WHEN CHARINDEX('stats',item.value('(UsedLifeSavers)[1]', 'NVARCHAR(MAX)'))>0
THEN 1
ELSE 0
END AS bit) as LS_HowOthersAnswered,
CAST(
CASE
WHEN CHARINDEX('FiftyFifty',item.value('(UsedLifeSavers)[1]', 'NVARCHAR(MAX)'))>0
THEN 1
ELSE 0
END AS bit) as LS_ImproveMyChances
FROM [MyDB].[dbo].[Tasks] as tasks CROSS APPLY [Progress].nodes ('//Progress/Steps/ProgressStep') Progress(item)
)
which acts on the following Tasks table:
ID | ActivityID | Progress
1 | 1 | [example below..]
2 | 1 | [example below..]
Where Progress is an xml of the sort:
<Progress xmlns:i="http://www.w3.org/2001/XMLSchema-instance">
<Steps>
<ProgressStep>
<FinishedOn>2012-10-30T13:07:52.6374861+02:00</FinishedOn>
<Score>0</Score>
<StartedOn>2012-10-30T13:07:45.8234861+02:00</StartedOn>
<Status>Finished</Status>
<StepIndex>0</StepIndex>
<StepType>Summary</StepType>
<UsedLifeSavers xmlns:a="http://schemas.datacontract.org/2004/07/MindLab.Logic.Study" />
</ProgressStep>
<ProgressStep>
<FinishedOn i:nil="true" />
<PartNumber>1</PartNumber>
<Score>0</Score>
<StartedOn>2012-10-30T13:07:52.6374861+02:00</StartedOn>
<Status>NotFinished</Status>
<StepIndex>1</StepIndex>
<StepType>Information</StepType>
<SubmittedAnswersCount>0</SubmittedAnswersCount>
<UsedLifeSavers xmlns:a="http://schemas.datacontract.org/2004/07/MindLab.Logic.Study">
<a:LifeSavers>Stats</a:LifeSavers>
<a:LifeSavers>FiftyFifty</a:LifeSavers>
</UsedLifeSavers>
</ProgressStep>
</Steps>
</Progress>
(usually there are many more than 2 steps..)
My query yields (not actual data, just sample):
ID | ActivityID | TaskID | LS_HowOthersAnswered | LS_ImproveMyChances
1 | 1 | 1 | 0 | 0
2 | 1 | 1 | 1 | 0
3 | 1 | 1 | 0 | 0
This is almost what I need but not quite.
I need for every unique TaskID the SUM of all LS_HowOthersAnswered and LS_ImproveMyChances.
I was trying to group by, but couldn't make it because it's just too different from regular inner join in that matter..
Found out:
insert into [MyDB].[dbo].[Reports_ActivityStat] (ActivityID,TaskID,LS_HowOthersAnswered,LS_ImproveMyChances)
(
SELECT
ActivityID=tasks.ActivityID,
TaskID=tasks.ID,
SUM (CAST(
CASE
WHEN CHARINDEX('stats',item.value('(UsedLifeSavers)[1]', 'NVARCHAR(MAX)'))>0
THEN 1
ELSE 0
END AS int)
),
SUM (CAST(
CASE
WHEN CHARINDEX('FiftyFifty',item.value('(UsedLifeSavers)[1]', 'NVARCHAR(MAX)'))>0
THEN 1
ELSE 0
END AS int)
)
FROM [MyDB].[dbo].[Tasks] as tasks CROSS APPLY [Progress].nodes ('//Progress/Steps/ProgressStep') Progress(item)
group by tasks.ID
)

Resources