How to calculate running balance in SQL Server query? - sql-server

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

Related

SQL Server Cumulative Sum breaks

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.

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 |
+----+--------+-----------+

How to get the list of products launched in the latest quarter if product gets launched at different time in different regions

I have a table
/---------------------------------------\
|Region | Product | 1 | 2 | 3 | 4 |
|-------|---------|---|-----|-----|-----|
| A | ABC | 0 | 120 | 421 | 520 |
| B | ABC | 0 | 0 | 0 | 670 |
| C | DEF | 0 | 0 | 0 | 125 |
| D | PQR | 0 | 0 | 780 | 560 |
| E | PQR | 0 | 0 | 0 | 340 |
| F | XYZ | 0 | 0 | 0 | 780 |
| G | XYZ | 0 | 0 | 0 | 900 |
\---------------------------------------/
In this table, I need to find the name of products that were launched in quarter 4.
The result that query should give is DEF and XYZ
I will be grateful if someone could help
You need to group by product and aggregate (sum) the values for each quarter per product, regardless of region:
select
Product
from #table
group by Product
having sum([4]) > 0
and sum([3]) = 0
and sum([2]) = 0
and sum([1]) = 0
With sample data to illustrate:
create table #table
(
Region varchar(1),
Product varchar(3),
[1] int,
[2] int,
[3] int,
[4] int
)
insert into #table
values
('A','ABC',0,120,421,520),
('B','ABC',0,0,0,670),
('C','DEF',0,0,0,125),
('D','PQR',0,0,780,560),
('E','PQR',0,0,0,340),
('F','XYZ',0,0,0,780),
('G','XYZ',0,0,0,900)
select
Product
from #table
group by Product
having sum([4]) > 0
and sum([3]) = 0
and sum([2]) = 0
and sum([1]) = 0
drop table #table
Output:
/---------\
| Product |
|---------|
| DEF |
| XYZ |
\---------/
try this
select *
from yourTableName a
where a.field4 > 0
and a.field3 = 0
and a.field2 = 0
and a.field1 = 0
and a.product not in (select b.product
from yourTableName b
where b.field3 >0
or b.field2>0
or b.field1>0)
and if you just want the product use below
select a.product
from yourTableName a
where a.field4 > 0
and a.field3 = 0
and a.field2 = 0
and a.field1 = 0
and a.product not in (select b.product
from yourTableName b
where b.field3 >0
or b.field2>0
or b.field1>0)
here field4 as quarter 4
field3 as quarter 3 and so on.

Get all days between two dates with all day hours in SQL Server

I have to generate a result set of a SQL query which should match the following, but let me explain both inputs and outputs:
I have a table named Orders and this table has some orders in some days at some hours, then, I have been requested to provide a result-set which should get all days between two dates (i.e. 2017-10-01 and 2017-10-07), with all 24 hours for each day, even if that day or that hour had no orders, but it should be appeared with 0 value.
+------------+------+-------------+
| Day | Hour | TotalOrders |
+------------+------+-------------+
| 2017-10-01 | 0 | 0 |
+------------+------+-------------+
| 2017-10-01 | 1 | 3 |
+------------+------+-------------+
| 2017-10-01 | 2 | 4 |
+------------+------+-------------+
| 2017-10-01 | 3 | 0 |
+------------+------+-------------+
| 2017-10-01 | 4 | 7 |
+------------+------+-------------+
| 2017-10-01 | 5 | 0 |
+------------+------+-------------+
| 2017-10-01 | 6 | 0 |
+------------+------+-------------+
| 2017-10-01 | 7 | 9 |
+------------+------+-------------+
| 2017-10-01 | 8 | 0 |
+------------+------+-------------+
| 2017-10-01 | 9 | 0 |
+------------+------+-------------+
| 2017-10-01 | 10 | 0 |
+------------+------+-------------+
| 2017-10-01 | 11 | 0 |
+------------+------+-------------+
| 2017-10-01 | 12 | 0 |
+------------+------+-------------+
| 2017-10-01 | 13 | 0 |
+------------+------+-------------+
| 2017-10-01 | 14 | 0 |
+------------+------+-------------+
| 2017-10-01 | 15 | 0 |
+------------+------+-------------+
| 2017-10-01 | 16 | 0 |
+------------+------+-------------+
| 2017-10-01 | 17 | 0 |
+------------+------+-------------+
| 2017-10-01 | 18 | 0 |
+------------+------+-------------+
| 2017-10-01 | 19 | 0 |
+------------+------+-------------+
| 2017-10-01 | 20 | 0 |
+------------+------+-------------+
| 2017-10-01 | 21 | 0 |
+------------+------+-------------+
| 2017-10-01 | 22 | 0 |
+------------+------+-------------+
| 2017-10-01 | 23 | 0 |
+------------+------+-------------+
| 2017-10-02 | 0 | 0 |
+------------+------+-------------+
| 2017-10-02 | 1 | 0 |
+------------+------+-------------+
| 2017-10-02 | 2 | 0 |
+------------+------+-------------+
| 2017-10-02 | 3 | 0 |
+------------+------+-------------+
| 2017-10-02 | 4 | 0 |
+------------+------+-------------+
| 2017-10-02 | 5 | 0 |
+------------+------+-------------+
| 2017-10-02 | 6 | 0 |
+------------+------+-------------+
| 2017-10-02 | 7 | 0 |
+------------+------+-------------+
| and so on .................. |
+------------+------+-------------+
So, the above result set should contain every day between the given two dates, and each day should have all 24 hours, irrespective off that day had orders and the same for hour (either it had orders or not)
I did it using a nested CTE:
DECLARE #MinDate DATE = '20171001',
#MaxDate DATE = '20171006';
;WITH INNER_CTE as(
SELECT TOP (DATEDIFF(DAY, #MinDate, #MaxDate) + 1)
Date = DATEADD(DAY, ROW_NUMBER() OVER(ORDER BY a.object_id) - 1, #MinDate)
FROM sys.all_objects a
CROSS JOIN sys.all_objects b) ,
OUTER_CTE as (
select * from INNER_CTE
cross apply (
SELECT TOP (24) n = ROW_NUMBER() OVER (ORDER BY [object_id]) -1
FROM sys.all_objects ORDER BY n)) t4
)
select t1.Date, t1.n [Hour], ISNULL(t2.TotalORders,0) TotalOrders from
OUTER_CTE t1
LEFT JOIN orders t2 on t1.Date = t2.[Day] and t1.n = t2.[Hour]
Good Reading about generating sequences using a query here: https://sqlperformance.com/2013/01/t-sql-queries/generate-a-set-1
I prefer to do this with a tally table instead of using loops. The performance is much better. I keep a tally on my system as a view like this.
create View [dbo].[cteTally] as
WITH
E1(N) AS (select 1 from (values (1),(1),(1),(1),(1),(1),(1),(1),(1),(1))dt(n)),
E2(N) AS (SELECT 1 FROM E1 a, E1 b), --10E+2 or 100 rows
E4(N) AS (SELECT 1 FROM E2 a, E2 b), --10E+4 or 10,000 rows max
cteTally(N) AS
(
SELECT ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) FROM E4
)
select N from cteTally
GO
Now that we have our tally table we can use some basic math to get the desired output. Something along these lines.
declare #Date1 datetime = '2017-10-01';
declare #Date2 datetime = '2017-10-07';
select Day = convert(date, DATEADD(hour, t.N, #Date1))
, Hour = t.N - 1
, TotalOrders = COUNT(o.OrderID)
from cteTally t
left join Orders o on o.OrderDate = DATEADD(hour, t.N, #Date1)
where t.N <= DATEDIFF(hour, #Date1, #Date2)
group by DATEDIFF(hour, #Date1, #Date2)
, t.N
The simplest way is to just use a temporary table or table variable to fill the desired result set, and then count the number of Orders for each row.
declare #Date1 date = '2017-10-01';
declare #Date2 date = '2017-10-07';
declare #Hour int;
declare #Period table (Day Date, Hour Time);
while #Date1 <= #Date2
begin
set #Hour = 0;
while #Hour < 24
begin
insert into #Period (Day, Hour) values (#Date1, TimeFromParts(#Hour,0,0,0,0));
set #Hour = #Hour + 1;
end
set #Date1 = DateAdd(Day, 1, #Date1);
end
select Day, Hour,
(select count(*)
from Orders
where Orders.Day = Period.Day and Orders.Hour = Period.Hour) as TotalOrders
from #Period as Period;

Grouping SQL columns from one table

I am currently having difficulty getting the correct values from my table. Here is my table
NOTE: The column Status has 3 possible values (Cleaned, Unclean, Closed)
+-----------+-------------+--------+------------+
|ApplicantID|ApplicantName| Status | HireDate |
+-----------+-------------+--------+------------+
| 1 | John Smith |Cleaned |08/26/2015 |
| 2 | Alex Murphy |Closed |09/12/2015 |
| 3 | Oliver David|Cleaned |01/11/2015 |
| 4 | Max Payne |Unclean |03/18/2015 |
+-----------+-------------+--------+------------+
The output I'm expecting and it should also be sorted by year.
For example I call all these records for the year 2015 which I get using the variable #Year.
NOTE: The column Total is the SUM of Cleaned and Unclean
+---------+-----------+-----------+----------+---------+
| Month | Cleaned | Unclean | Closed | Total |
+---------+-----------+-----------+----------+---------+
| January| 1 | 0 | 0 | 1 |
| February| 0 | 0 | 0 | 0 |
| March | 0 | 1 | 0 | 1 |
| April | 0 | 0 | 0 | 0 |
| May | 0 | 0 | 0 | 0 |
| June | 0 | 0 | 0 | 0 |
| July | 0 | 0 | 0 | 0 |
| August | 1 | 0 | 0 | 1 |
|September| 0 | 0 | 1 | 0 |
| October| 0 | 0 | 0 | 0 |
| November| 0 | 0 | 0 | 0 |
| December| 0 | 0 | 0 | 0 |
+---------+-----------+-----------+----------+---------+
I can't seem to get the right code, for the sql this is my current code.
SELECT Month(HireDate) AS Month, COUNT(*)
FROM Hires
GROUP BY Month(HireDate)
I know my coding is wrong, because it is incomplete.
Generate a list of numbers from 1 to 12 first to hold all months. Then do a LEFT JOIN on Hires to make sure all missing months are accounted for. Then use conditional aggregation for the totals:
SQL Fiddle
;WITH CteMonths AS(
SELECT * FROM(VALUES
(1),(2),(3),(4),(5),(6),(7),(8),(9),(10),(11),(12)
)t(N)
)
SELECT
Month = DATENAME(MONTH, DATEADD(MONTH, N-1,0)),
Cleaned = SUM(CASE WHEN h.Status = 'Cleaned' THEN 1 ELSE 0 END),
Closed = SUM(CASE WHEN h.Status = 'Closed' THEN 1 ELSE 0 END),
Unclean = SUM(CASE WHEN h.Status = 'Unclean' THEN 1 ELSE 0 END),
Total = SUM(CASE WHEN h.Status IN('Cleaned', 'Unclean') THEN 1 ELSE 0 END)
FROM CteMonths m
LEFT JOIN Hires h
ON m.N = MONTH(h.HireDate)
--AND YEAR(h.HireDate) = #year --uncomment this line to filter for year.
GROUP BY m.N
ORDER BY m.N
If you want to include the YEAR:
SQL Fiddle
;WITH CteMonths AS(
SELECT * FROM(VALUES
(1),(2),(3),(4),(5),(6),(7),(8),(9),(10),(11),(12)
)t(N)
),
CteYears(yr) AS(
SELECT DISTINCT YEAR(HireDate) FROM Hires
),
CteAllDates(dt) AS(
SELECT
DATEADD(MONTH, m.N - 1, DATEADD(YEAR, y.yr - 1900, 0))
FROM CteMonths m
CROSS JOIN CteYears y
)
SELECT
Year = YEAR(d.dt),
Month = DATENAME(MONTH, d.dt),
Cleaned = SUM(CASE WHEN h.Status = 'Cleaned' THEN 1 ELSE 0 END),
Closed = SUM(CASE WHEN h.Status = 'Closed' THEN 1 ELSE 0 END),
Unclean = SUM(CASE WHEN h.Status = 'Unclean' THEN 1 ELSE 0 END),
Total = SUM(CASE WHEN h.Status IN('Cleaned', 'Unclean') THEN 1 ELSE 0 END)
FROM CteAllDates d
LEFT JOIN Hires h
ON MONTH(d.dt) = MONTH(h.HireDate)
AND YEAR(d.dt) = YEAR(h.HireDate)
GROUP BY YEAR(d.dt), MONTH(d.dt), DATENAME(MONTH, d.dt)
ORDER BY YEAR(d.dt), MONTH(d.dt)
If you want to filter for year, say #year = 2015, you can replace the previous ctes with:
;WITH CteMonths AS(
SELECT * FROM(VALUES
(1),(2),(3),(4),(5),(6),(7),(8),(9),(10),(11),(12)
)t(N)
),
CteAllDates(dt) AS(
SELECT
DATEADD(MONTH, m.N - 1, DATEADD(YEAR, #year - 1900, 0))
FROM CteMonths m
)...
I suggest to create TEMP table with values from 1 to 12 (numbers of months) and JOIN your table with TEMP table. To achieve values as columns names you can use PIVOT or CASE. You can do It in following:
INSERT INTO #Months VALUES
(1),(2),(3),(4),(5),(6),(7),(8),(9),(10),(11),(12)
SELECT DATENAME(MONTH, DATEADD(MONTH, m.Id-1, 0)) AS [Month]
, SUM(CASE WHEN [Status] = 'Cleaned' THEN 1 ELSE 0 END) AS [Cleaned]
, SUM(CASE WHEN [Status] = 'Closed' THEN 1 ELSE 0 END ) AS [Closed]
, SUM(CASE WHEN [Status] = 'Unclean' THEN 1 ELSE 0 END) AS [Unclean]
, SUM(CASE WHEN [Status] IN ('Unclean', 'Cleaned') THEN 1 ELSE 0 END) AS [Total]
FROM #Test t
RIGHT JOIN #Months m ON m.Id = MONTH(t.HireDate)
GROUP BY m.Id
OUTPUT
+---------+-----------+-----------+----------+---------+
| Month | Cleaned | Unclean | Closed | Total |
+---------+-----------+-----------+----------+---------+
| January | 1 | 0 | 0 | 1 |
| February| 0 | 0 | 0 | 0 |
| March | 0 | 1 | 0 | 1 |
| April | 0 | 0 | 0 | 0 |
| May | 0 | 0 | 0 | 0 |
| June | 0 | 0 | 0 | 0 |
| July | 0 | 0 | 0 | 0 |
| August | 1 | 0 | 0 | 1 |
|September| 0 | 0 | 1 | 0 |
| October | 0 | 0 | 0 | 0 |
| November| 0 | 0 | 0 | 0 |
| December| 0 | 0 | 0 | 0 |
+---------+-----------+-----------+----------+---------+
DEMO
You can test It at: SQL FIDDLE

Resources