SQL Server Cumulative Sum breaks - sql-server

I have one Amount field and I want to build the following table (CumulativeSum and GroupNo columns):
| Id | Amount | CumulativeSum | GroupNo |
| 1 | 1000 | 1000 | 0 |
| 2 | 2000 | 3000 | 0 |
| 3 | 1000 | 4000 | 0 |
| 4 | 3000 | 3000 | 1 |
| 5 | 2000 | 5000 | 1 |
| 6 | 3000 | 3000 | 2 |
| 7 | 1000 | 4000 | 2 |
| 8 | 4000 | 4000 | 3 |
| 9 | 2000 | 2000 | 4 |
Note: when ever the CumulativeSum becomes grater than 5000, I need it to start summing from 0 with a new Group Number (GroupNo).
For Example the sum of rows 1, 2 and 3 equals 4000. The amount of row 4 is 3000 and 4000 + 3000 = 7000 and it is grater than 5000 so it must break and start from 0 again.

The easiest way to do this is with a recursive CTE. These are effectively loops, where the next value is calculated based on previous values. Here's a link to a basic example and writeup of how they work: https://www.sqlservertutorial.net/sql-server-basics/sql-server-recursive-cte/
In this case, it checks if the previous value + the current value is over 5000; if so, it resets the counter and adds 1 to the GroupNo; otherwise it just adds the current value to CumulativeSum.
CREATE TABLE #Amts (ID int PRIMARY KEY, Amount int);
INSERT INTO #Amts (ID, Amount)
VALUES
(1, 1000), -- | 1000 | 0 |
(2, 2000), -- | 3000 | 0 |
(3, 1000), -- | 4000 | 0 |
(4, 3000), -- | 3000 | 1 |
(5, 2000), -- | 5000 | 1 |
(6, 3000), -- | 3000 | 2 |
(7, 1000), -- | 4000 | 2 |
(8, 4000), -- | 4000 | 3 |
(9, 2000); -- | 2000 | 4 |
WITH RunningTotals AS
(SELECT A.ID, A.Amount, A.Amount AS CumulativeSum, 0 AS GroupNo
FROM #Amts A
WHERE ID = 1
UNION ALL
SELECT RT.ID + 1,
A.Amount,
CASE WHEN RT.CumulativeSum + A.Amount > 5000 THEN A.Amount
ELSE RT.CumulativeSum + A.Amount END,
CASE WHEN RT.CumulativeSum + A.Amount > 5000 THEN RT.GroupNo + 1
ELSE RT.GroupNo END
FROM RunningTotals RT
INNER JOIN #Amts A ON RT.ID + 1 = A.ID
)
SELECT *
FROM RunningTotals
OPTION (MAXRECURSION 1000);
Results are as follows
ID Amount CumulativeSum GroupNo
1 1000 1000 0
2 2000 3000 0
3 1000 4000 0
4 3000 3000 1
5 2000 5000 1
6 3000 3000 2
7 1000 4000 2
8 4000 4000 3
9 2000 2000 4
Notes
This currently requires the IDs to be consecutive and starting with 1. If not, you may need to make an initial CTE with ROW_NUMBER() OVER (ORDER BY Id) to get this
I've put MAXRECURSION 1000 on there - that means it will do this looping 1000 times. If you have a larger table than this, you'll need to increase that number.
Update: fixed bug in code ( RT.GroupNo + 1 was in wrong spot). Also added 'Amount' column to output.

Related

How to use Lag with while to calculate the effectiveness of discount , SQL

I want to calculate the effectiveness of a discount.
I want to update the last column as 'pozitive' if s_quantity increased and negative, if decreased. Neutral, if no change. I've written the code:
DECLARE #count AS INT
SET #count=1
WHILE #count< 316
BEGIN
IF product_id = #count
WHEN s_quantity > (LAG(s_quantity) OVER (ORDER BY product_id ASC, discount ASC))
UPDATE [SampleRetail].[sale].[Analiz] SET hesap_kitap = 'pozitif'
SET #count +=1
IF #count > 316
BREAK
ELSE
CONTINUE
END
Where do I make the mistake? Can you help me?
We can do this with the function LAG() in a SELECT.
(see the dbFiddle link below for the test schema.)
SELECT
period,
sales,
LAG(sales) OVER (ORDER BY period) p_1,
sales - LAG(sales) OVER (ORDER BY period) increase
FROM sales
ORDER BY period;
period | sales | p_1 | increase
-----: | ----: | ---: | -------:
1 | 100 | null | null
2 | 200 | 100 | 100
3 | 300 | 200 | 100
4 | 250 | 300 | -50
5 | 500 | 250 | 250
6 | 500 | 500 | 0
WITH sale as (
SELECT
period,
sales,
LAG(sales) OVER (ORDER BY period) p_1
FROM sales)
SELECT
period,
sales,
sales - p_1 increase,
CASE WHEN sales < p_1 THEN 'negative'
WHEN sales = p_1 THEN 'stable'
ELSE 'positive' END AS "change"
FROM sale;
period | sales | increase | change
-----: | ----: | -------: | :-------
1 | 100 | null | positive
2 | 200 | 100 | positive
3 | 300 | 100 | positive
4 | 250 | -50 | negative
5 | 500 | 250 | positive
6 | 500 | 0 | stable
db<>fiddle here

Select All rows in when count(*) = count(IDField)

I have this table in SQL Server 2008.
id | TaskID | TaskHours
------------------------
0 | 25 | 1
1 | 25 | 0
2 | 24 | 1
3 | 24 | 2
4 | 24 | 2
5 | 23 | 0
I want to know how to select all by TaskID where the TaskHours all have a value > 0. I also want to select all rows by TaskID where the TaskHours have a 0 in them.
Basically I want to know if a task is completed by giving me all rows.
so Completed Tasks should show
id | TaskID | TaskHours
------------------------
2 | 24 | 1
3 | 24 | 2
4 | 24 | 2
and non completed tasks should show
id | TaskID | TaskHours
------------------------
0 | 25 | 1
1 | 25 | 0
5 | 23 | 0
I've tried
select * from tblTasks where TaskHours > 0
but I got this and I don't want 25 because it has a 0.
id | TaskID | TaskHours
------------------------
0 | 25 | 1
2 | 24 | 1
3 | 24 | 2
4 | 24 | 2
I've tried count(*) and count(Taskhours) > 0 but I couldn't get any further.
Any ideas?
For both queries, you may use exists logic. For the first query, consider:
SELECT t1.id, t1.TaskID, t1.TaskHours
FROM tblTasks t1
WHERE NOT EXISTS (SELECT 1 FROM tlbTasks t2
WHERE t2.TaskID = t1.TaskID AND t2.TaskHours = 0);
And for all tasks which have at least one record with zero task hours:
SELECT t1.id, t1.TaskID, t1.TaskHours
FROM tblTasks t1
WHERE EXISTS (SELECT 1 FROM tlbTasks t2
WHERE t2.TaskID = t1.TaskID AND t2.TaskHours = 0);
You can use CTE(Common Table expressions) together with NOT EXISTS, EXISTS clause to derive the results.
DECLARE #tasks TABLE(id int, taskid int, taskhours int)
INSERT INTO #tasks
values
(0 , 25 , 1),
(1 , 25 , 0),
(2 , 24 , 1),
(3 , 24 , 2),
(4 , 24 , 2),
(5 , 23 , 0);
;WITH CTE_TasksCompleted AS
(
SELECT DISTINCT TaskId FROM #tasks
WHERE taskhours = 0
)
-- Completed Tasks
SELECT ot.* FROM #tasks as ot
WHERE Not exists (SELECT TaskId from CTE_TasksCompleted
WHERE taskId = ot.taskId)
;WITH CTE_TasksCompleted AS
(
SELECT DISTINCT TaskId FROM #tasks
WHERE taskhours = 0
)
-- NotCompleted Tasks
SELECT ot.* FROM #tasks as ot
WHERE exists (SELECT TaskId from CTE_TasksCompleted
WHERE taskId = ot.taskId)
Completed Tasks
+----+--------+-----------+
| id | taskid | taskhours |
+----+--------+-----------+
| 2 | 24 | 1 |
| 3 | 24 | 2 |
| 4 | 24 | 2 |
+----+--------+-----------+
Not Completed Tasks
+----+--------+-----------+
| id | taskid | taskhours |
+----+--------+-----------+
| 0 | 25 | 1 |
| 1 | 25 | 0 |
| 5 | 23 | 0 |
+----+--------+-----------+

Calculating week numbers from custom dates

I have client ids and their dates of login. i want to calculate the week number with respect to their first login date
i am fairly new to sql
Demo output
ClientID Date of login Week Number
1 2019-12-20 1
1 2019-12-21 1
1 2019-12-21 1
1 2019-12-22 1
1 2019-12-29 2
1 2019-12-29 2
2 2020-01-27 1
2 2020-01-28 1
2 2020-02-05 2
2 2020-02-06 2
2 2020-02-16 3
This is very trivial date arithmetic that just requires the min DateOfLogin for each ClientID, which you can find with a windowed function.
Calculate the datediff in days between this date and the current DateOfLogin, integer divide by 7 (to return no fractional days) and then add 1 to correctly offset the WeekNum value:
declare #l table(ClientID int, DateOfLogin date);
insert into #l values(1,'2019-12-20'),(1,'2019-12-21'),(1,'2019-12-21'),(1,'2019-12-22'),(1,'2019-12-29'),(1,'2019-12-29'),(2,'2020-01-27'),(2,'2020-01-28'),(2,'2020-02-05'),(2,'2020-02-06'),(2,'2020-02-16');
select ClientID
,DateOfLogin
,(datediff(day,min(DateOfLogin) over (partition by ClientID),DateOfLogin) / 7) + 1 as WeekNum
from #l;
Output
+----------+-------------+---------+
| ClientID | DateOfLogin | WeekNum |
+----------+-------------+---------+
| 1 | 2019-12-20 | 1 |
| 1 | 2019-12-21 | 1 |
| 1 | 2019-12-21 | 1 |
| 1 | 2019-12-22 | 1 |
| 1 | 2019-12-29 | 2 |
| 1 | 2019-12-29 | 2 |
| 2 | 2020-01-27 | 1 |
| 2 | 2020-01-28 | 1 |
| 2 | 2020-02-05 | 2 |
| 2 | 2020-02-06 | 2 |
| 2 | 2020-02-16 | 3 |
+----------+-------------+---------+
This query returns the week number.
select DATENAME(WW, '2019-12-20')
This is for MSSQL.
Here might be a solution for you, you'll maybe just have to look at the way you are going to do the insert and maybe optimize it a bit better.
select 1 AS 'ClientID', '2019-12-20' AS 'LogInDate', 1 AS 'Week'
into #test
insert into #test
select top(1) 1, '2020-02-05', case DATEDIFF(week,'2020-02-05',LogInDate) when 0 then week else Week +1 end from #test where ClientID = 1 order by LogInDate desc

SQL Server : filling in sparse values

Need help with SQL Server; what will be the easiest way to update the missing begin and end inventory values? Values shown are verified numbers for that week.
+------+--------+-------+----------+-----+
| Week | ItemNr | Begin | Increase | End |
+------+--------+-------+----------+-----+
| 1 | 1001 | 100 | -10 | 90 |
| 2 | 1001 | | 0 | |
| 3 | 1001 | 90 | 0 | 90 |
| 4 | 1001 | | 20 | |
| 5 | 1001 | | 100 | |
| 6 | 1001 | | -20 | |
| 7 | 1001 | | 0 | |
| 8 | 1001 | 200 | 10 | 210 |
| 9 | 1001 | | 0 | |
| 10 | 1001 | | -50 | -50 |
| 11 | 1001 | | 0 | |
+------+--------+-------+----------+-----+
if Begin is NULL then previous week End
END = Begin + Increase
A couple of window functions gets you the result. ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW is the default scope when you specify an ORDER BY in the OVER clause, however, as the other window function has it explicitly stated and doesn't use the default scope, I felt it was important to show; as you can see the difference.
WITH VTE AS(
SELECT *
FROM (VALUES ( 1,1001,100,-10),
( 2,1001,NULL, 0),
( 3,1001, 90, 0),
( 4,1001,NULL, 20),
( 5,1001,NULL,100),
( 6,1001,NULL,-20),
( 7,1001,NULL, 0),
( 8,1001,200, 10),
( 9,1001,NULL, 0),
(10,1001,NULL,-50),
(11,1001,NULL, 0)) V(Week, ItemNr, [Begin],Increase))
SELECT Week,
ItemNr,
ISNULL([Begin],S.Starting + SUM(Increase) OVER (PARTITION BY ItemNr ORDER BY Week ROWS BETWEEN UNBOUNDED PRECEDING AND 1 PRECEDING)) AS [Begin],
Increase,
S.Starting + SUM(Increase) OVER (PARTITION BY ItemNr ORDER BY Week ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) AS [End]
FROM VTE V
CROSS APPLY (SELECT TOP 1 [Begin] AS Starting
FROM VTE ca
WHERE ca.ItemNr = V.ItemNr
ORDER BY Week ASC) S;
Note: This appears to be some kind of stock system. it's worth noting that this doesn't take into account that the stock level could go wrong. For example, say an item is stolen; the value of [End] and [Begin] (when it has a value of NULL) would be wrong in those events. If this needs to be taken into consideration, then we need to know this in the question.
Edit: Solution to cater for "lost" stock. With this, this takes the last "known" value for the stock and aggregates. So, for this example, in Week 1, although 10 items were "sold", the start of the week 2 shows the beginning value as 35. This means that 5 items are missing (stolen?). This there needs to effect all stock levels going forward. Thus you get:
WITH VTE AS(
SELECT *
FROM (VALUES ( 1,1001,100,-10),
( 2,1001,NULL, 0),
( 3,1001, 90, 0),
( 4,1001,NULL, 20),
( 5,1001,NULL,100),
( 6,1001,NULL,-20),
( 7,1001,NULL, 0),
( 8,1001,200, 10),
( 9,1001,NULL, 0),
(10,1001,NULL,-50),
(11,1001,NULL, 0),
(1,1002,50,-10),
(2,1002,35,0),--Begin value lowered. Some items went "missing"
(3,1002,NULL,5),
(4,1002,40,10)) V(Week, ItemNr, [Begin],Increase))
SELECT Week,
ItemNr,
[Begin],
Increase,
LastKnown,
WeekKnown,
ISNULL([Begin],S.LastKnown + SUM(Increase) OVER (PARTITION BY ItemNr, WeekKnown ORDER BY Week ROWS BETWEEN UNBOUNDED PRECEDING AND 1 PRECEDING)) AS ActualBegin,
ISNULL([Begin],S.LastKnown + SUM(Increase) OVER (PARTITION BY ItemNr, WeekKnown ORDER BY Week ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW)) AS [End]
FROM VTE V
CROSS APPLY (SELECT TOP 1 [Begin] AS LastKnown, Week AS WeekKnown
FROM VTE ca
WHERE ca.ItemNr = V.ItemNr
AND ca.Week <= V.Week
AND ca.[Begin] IS NOT NULL
ORDER BY Week DESC) S
ORDER BY V.ItemNr, V.Week;
Here is another way too
SELECT T1.Week,
T1.ItemNr,
CASE WHEN T1.[Begin] IS NULL THEN
(SELECT MAX([Begin]) + SUM(Increase) FROM #T WHERE Week < T1.Week AND ItemNr = T1.ItemNr)
ELSE
T1.[Begin]
END [Begin],
T1.Increase,
CASE WHEN T1.[Begin] IS NULL THEN
(SELECT MAX([Begin]) + SUM(Increase) FROM #T WHERE Week < T1.Week AND ItemNr = T1.ItemNr)
ELSE
T1.[Begin]
END + T1.Increase [End]
FROM #T T1;
Returns:
+------+--------+-------+----------+-----+
| Week | ItemNr | Begin | Increase | End |
+------+--------+-------+----------+-----+
| 1 | 1001 | 100 | -10 | 90 |
| 2 | 1001 | 90 | 0 | 90 |
| 3 | 1001 | 90 | 0 | 90 |
| 4 | 1001 | 90 | 20 | 110 |
| 5 | 1001 | 110 | 100 | 210 |
| 6 | 1001 | 210 | -20 | 190 |
| 7 | 1001 | 190 | 0 | 190 |
| 8 | 1003 | 200 | 10 | 210 |
| 9 | 1003 | 210 | 0 | 210 |
| 10 | 1003 | 210 | -50 | 160 |
| 11 | 1003 | 160 | 0 | 160 |
+------+--------+-------+----------+-----+
Demo

How to calculate running balance in SQL Server query?

I have data in given format:
Id | Total_Inv_Amount | Total_Rec_Inv_Amount | Invoice_No Invoice_Amont | Payment_Outstanding | Refund_Outstanding
1 | 25000 | 22000 | 5 | 15000 | 0 | 0
1 | 25000 | 22000 | 6 | 10000 | 0 | 0
2 | 45000 | 48000 | 10| 25000 | 0 | 0
2 | 45000 | 48000 | 15| 20000 | 0 | 0
expected result ....
Id | Total_Inv_Amount | Total_Rec_Inv_Amount | Invoice_No Invoice_Amont | Payment_Outstanding | Refund_Outstanding
1 | 25000 | 22000 | 5 | 15000 | 0 | 0
1 | 25000 | 22000 | 6 | 10000 | 3000 | 0
2 | 45000 | 48000 | 10| 25000 | 0 | 0
2 | 45000 | 48000 | 15| 20000 | 0 | 2000
Calculation :--
invoice No 5 & 6 Total Amount is : 15000+10000 = 25000
And total Received Amount is : 22000
now in field Payment_Outstanding for invoice no is 0
because total_invoice_amt > invoice 5 amount
22000 >15000 than payment outstanding is 0
remain amount is 22000-15000 = 7000
now deduct this amount from next invoice which amount is 10000
10000-7000 = 3000 this is payment outstanding
now please help me how to calculate this in query
recursive queries are great at getting running values.
The first cte adds a rownumber which is used by the recursive part. In the recursive part the calculations are done. In het final result, values below 0 won't be displayed.
Declare #myTable table (Id int, Total_Inv_Amount float,
Total_Rec_Inv_Amount float,Invoice_No int,
Invoice_Amount float, Payment_Outstanding float,
Refund_Outstanding float)
insert into #myTable values
(1 , 25000 , 22000 , 5 , 15000 , 0 , 0 ),
(1 , 25000 , 22000 , 6 , 10000 , 0 , 0 ),
(2 , 45000 , 48000 , 10, 25000 , 0 , 0 ),
(2 , 45000 , 48000 , 15, 20000 , 0 , 0 )
;with first_cte as
(
select *, ROW_NUMBER() over (partition by id order by invoice_no) rn
from #myTable
)
, result_rte as
(
select m.Id, m.Total_Inv_Amount,m.Total_Rec_Inv_Amount,
m.Invoice_No, m.Invoice_Amount, m.Payment_Outstanding,
m.Refund_Outstanding, m.rn, invoice_Amount TotalAmount
from first_cte m
where rn = 1
union all
select m.Id, m.Total_Inv_Amount,m.Total_Rec_Inv_Amount,
m.Invoice_No, m.Invoice_Amount,
r.TotalAmount + m.Invoice_Amount - m.Total_Rec_Inv_Amount ,
m.Total_Rec_Inv_Amount - (r.TotalAmount + m.Invoice_Amount),
m.rn, r.TotalAmount + m.Invoice_Amount
from result_rte r
join first_cte m on r.id = m.id and r.rn+1 = m.rn
)
select Id, Total_Inv_Amount,Total_Rec_Inv_Amount,Invoice_No, Invoice_Amount,
case when Payment_Outstanding > 0 then Payment_Outstanding else 0 end Payment_Outstanding ,
case when Refund_Outstanding > 0 then Refund_Outstanding else 0 end Refund_Outstanding
from result_rte order by invoice_no

Resources