Cumulative subtraction across rows - sql

Table 1:
Table 2:
How can I subtract the Committed value (7) from the IncomingQuantity cumulatively across rows? So that the result would look like:
Thanks!

You need a cumulative sum and some arithmetic:
select t.*,
(case when running_iq - incomingquantity >= committed then 0
when running_iq > committed then running_iq - committed
else incomingquantity
end) as from_this_row
from (select t2.*, t1.committed,
sum(incomingquantity) over (order by rowid) as running_iq
from table1 t1 cross join
table2 t2
) t;

you can also make use of the built-in functions such as ROW_NUMBER(), LAST_VALUE(), and LAG() with CASE
here is an example :
DECLARE
#t1 TABLE ( ProductID VARCHAR(50), ICommitted INT)
INSERT INTO #t1 VALUES ('Some product', 7)
DECLARE
#t2 TABLE (RowID INT, DueDate DATE, IncommingQuantity INT)
INSERT INTO #t2 VALUES
(1,'2018-11-19', 5),
(2,'2018-11-20', 4),
(3,'2018-11-20', 4),
(4,'2018-11-20', 3),
(5,'2018-11-22', 12)
SELECT
RowID
, DueDate
, CASE
WHEN RowID = 1
THEN 0
WHEN RowID = LAST_VALUE(RowID) OVER(ORDER BY (SELECT NULL) )
THEN IncommingQuantity
WHEN ROW_NUMBER() OVER(PARTITION BY DueDate ORDER BY RowID) > 1
THEN IncommingQuantity
ELSE ICommitted - LAG(IncommingQuantity) OVER (ORDER BY RowID)
END IncommingQuantity
FROM #t2 t2
CROSS APPLY (SELECT t1.ICommitted FROM #t1 t1) e

I ended up doing this simply with WHILE loop inside a user function. The other solutions I would not work properly in 100% of cases

Related

How to minus current and previous value in SQL Server

Have one table, need to minus one column previous and current amount. Table value is below, need to write syntax for Cal-Amount column
Id Amount Cal-Amount
1 100 0
2 200 0
3 400 0
4 500 0
Cal-Amount calculation formula with sample value
Id Amount Cal-Amount
1 100 (0-100)=100
2 200 (100-200)=100
3 400 (200-400)=200
4 500 (400-500)=100
Need SQL syntax to minus column current and previous value
LAG is one option if you are using SQL Server 2012 or later:
SELECT
Id,
Amount,
LAG(Amount, 1, 0) OVER (ORDER BY Id) - Amount AS [Cal-Amount]
FROM yourTable;
If you are using an earlier version of SQL Server, then we can use a self join:
SELECT
Id,
Amount,
COALESCE(t2.Amount, 0) - t1.Amount AS [Cal-Amount]
FROM yourTable t1
LEFT JOIN yourTable t2
ON t1.Id = t2.Id + 1;
But note that the self join option might only work if the Id values are continuous. LAG is probably the most efficient way to do this, and is also robust to non sequential Id values, so long as the order is correct.
Well, Tim beat me to the lag(), so here's the old-school using join:
select t.Id,t.Amount,t.Amount-isnull(t2.Amount,0) AS [Cal-Amount]
from yourtable t
left join yourtable t2 on t.id=t2.id+1
SQL Server 2012 or newer:
Select
ID, Amount, [Cal-Amount] = Amount - LAG(Amount, 1, 0) OVER (ORDER BY Id)
From
table
or
Select
current.ID, Current.Amount, Current.Amount - Isnull(Prior.Amount, 0)
from
table current
left join
table prior on current.id - 1 = prior.id
You can use the LAG function if your SQL Server >= 2012
declare #t table (id int, amount1 int)
insert into #t
values (1, 100), (2, 200), (3, 400), (4, 500)
select
*, amount1 - LAG(amount1, 1, 0) over (order by id) as CalAmount
from
#t
You can also use apply :
select t.*, t.Amount - coalesce(tt.Amount, 0) as CalAmount
from table t outer apply (
select top (1) *
from table t1
where t1.id < t.id
order by t1.id desc
) tt;

Previous value and current value

I would like to pivot the following data such that I can have a previous column and a current column based on the values in column rn. If there is only 1 record then the current and the previous values will be the same value.
(I am using ssms 2008)
CREATE TABLE #TEST1
(ACCT_ID INT, RN INT, LoadDate Date)
INSERT INtO #TEST1 VALUES (1, 1, '2016-12-21')
INSERT INtO #TEST1 VALUES (2, 1, NULL)
INSERT INtO #TEST1 VALUES (3, 1, '2017-10-06')
INSERT INtO #TEST1 VALUES (3, 2, NULL)
INSERT INtO #TEST1 VALUES (4, 1, '2016-12-21')
SELECT * FROM #TEST1
ACCT_ID RN LoadDate
1 1 2016-12-21
2 1 NULL
3 1 2017-10-06
3 2 NULL
4 1 2016-12-21
Based on the data above, I did a pivot table
SELECT ACCT_ID, [1] as Prev, [2] as Curr FROM
(
SELECT * fROM #TEST1 S
pivot( MAX(LoadDate) for RN IN ([1],[2]) ) U
)X
How can for example acct_id 1 and 4 have the same value in current as the previous value.
I guess you need something like this. It displays the Curr value if the Prev is empty.
select acct_id, curr, case when prev = ' ' then null else coalesce(prev, curr) end
from
(
SELECT acct_id,
max(CASE WHEN RN = 1 THEN loaddate END) as Curr,
max(CASE WHEN RN = 2 THEN coalesce(loaddate, ' ') END) as Prev
FROM #test1 t
GROUP BY acct_id
) t
demo
I'm not sure if pivot will work in this case, I apologize if you specifically require an answer using pivot.
Here I'm taking the MAX(RN) via subquery, then doing select statements for the previous and current LoadDate. The current date is the date provided by the MAX(RN). The previous date is the date of MAX(RN) - 1 if MAX(RN) is greater than 1, else it is the previous date where RN equals 1.
SELECT dT.ACCT_ID
,(SELECT T1.LoadDate FROM #TEST1 T1 WHERE T1.ACCT_ID = dT.ACCT_ID AND T1.RN = CASE WHEN dT.MaxRN = 1 THEN 1 ELSE dT.MaxRN - 1 END) [Prev]
,(SELECT T1.LoadDate FROM #TEST1 T1 WHERE T1.ACCT_ID = dT.ACCT_ID AND T1.RN = dT.MaxRN) [Curr]
FROM (
SELECT ACCT_ID
,MAX(RN) [MaxRN]
FROM #TEST1
GROUP BY ACCT_ID
) AS dT
I would use conditional aggregation:
select acct_id,
max(case when rn = 1 then loaddate end) as load_date,
max(case when rn = 2 then loaddate end) as load_date_prev
from #test1 t
group by acct_id;

SQL group by if values are close

Class| Value
-------------
A | 1
A | 2
A | 3
A | 10
B | 1
I am not sure whether it is practical to achieve this using SQL.
If the difference of values are less than 5 (or x), then group the rows (of course with the same Class)
Expected result
Class| ValueMin | ValueMax
---------------------------
A | 1 | 3
A | 10 | 10
B | 1 | 1
For fixed intervals, we can easily use "GROUP BY". But now the grouping is based on nearby row's value. So if the values are consecutive or very close, they will be "chained together".
Thank you very much
Assuming MSSQL
You are trying to group things by gaps between values. The easiest way to do this is to use the lag() function to find the gaps:
select class, min(value) as minvalue, max(value) as maxvalue
from (select class, value,
sum(IsNewGroup) over (partition by class order by value) as GroupId
from (select class, value,
(case when lag(value) over (partition by class order by value) > value - 5
then 0 else 1
end) as IsNewGroup
from t
) t
) t
group by class, groupid;
Note that this assumes SQL Server 2012 for the use of lag() and cumulative sum.
Update:
*This answer is incorrect*
Assuming the table you gave is called sd_test, the following query will give you the output you are expecting
In short, we need a way to find what was the value on the previous row. This is determined using a join on row ids. Then create a group to see if the difference is less than 5. and then it is just regular 'Group By'.
If your version of SQL Server supports windowing functions with partitioning the code would be much more readable.
SELECT
A.CLASS
,MIN(A.VALUE) AS MIN_VALUE
,MAX(A.VALUE) AS MAX_VALUE
FROM
(SELECT
ROW_NUMBER()OVER(PARTITION BY CLASS ORDER BY VALUE) AS ROW_ID
,CLASS
,VALUE
FROM SD_TEST) AS A
LEFT JOIN
(SELECT
ROW_NUMBER()OVER(PARTITION BY CLASS ORDER BY VALUE) AS ROW_ID
,CLASS
,VALUE
FROM SD_TEST) AS B
ON A.CLASS = B.CLASS AND A.ROW_ID=B.ROW_ID+1
GROUP BY A.CLASS,CASE WHEN ABS(COALESCE(B.VALUE,0)-A.VALUE)<5 THEN 1 ELSE 0 END
ORDER BY A.CLASS,cASE WHEN ABS(COALESCE(B.VALUE,0)-A.VALUE)<5 THEN 1 ELSE 0 END DESC
ps: I think the above is ANSI compliant. So should run in most SQL variants. Someone can correct me if it is not.
These give the correct result, using the fact that you must have the same number of group starts as ends and that they will both be in ascending order.
if object_id('tempdb..#temp') is not null drop table #temp
create table #temp (class char(1),Value int);
insert into #temp values ('A',1);
insert into #temp values ('A',2);
insert into #temp values ('A',3);
insert into #temp values ('A',10);
insert into #temp values ('A',13);
insert into #temp values ('A',14);
insert into #temp values ('b',7);
insert into #temp values ('b',8);
insert into #temp values ('b',9);
insert into #temp values ('b',12);
insert into #temp values ('b',22);
insert into #temp values ('b',26);
insert into #temp values ('b',67);
Method 1 Using CTE and row offsets
with cte as
(select distinct class,value,ROW_NUMBER() over ( partition by class order by value ) as R from #temp),
cte2 as
(
select
c1.class
,c1.value
,c2.R as PreviousRec
,c3.r as NextRec
from
cte c1
left join cte c2 on (c1.class = c2.class and c1.R= c2.R+1 and c1.Value < c2.value + 5)
left join cte c3 on (c1.class = c3.class and c1.R= c3.R-1 and c1.Value > c3.value - 5)
)
select
Starts.Class
,Starts.Value as StartValue
,Ends.Value as EndValue
from
(
select
class
,value
,row_number() over ( partition by class order by value ) as GroupNumber
from cte2
where PreviousRec is null) as Starts join
(
select
class
,value
,row_number() over ( partition by class order by value ) as GroupNumber
from cte2
where NextRec is null) as Ends on starts.class=ends.class and starts.GroupNumber = ends.GroupNumber
** Method 2 Inline views using not exists **
select
Starts.Class
,Starts.Value as StartValue
,Ends.Value as EndValue
from
(
select class,Value ,row_number() over ( partition by class order by value ) as GroupNumber
from
(select distinct class,value from #temp) as T
where not exists (select 1 from #temp where class=t.class and Value < t.Value and Value > t.Value -5 )
) Starts join
(
select class,Value ,row_number() over ( partition by class order by value ) as GroupNumber
from
(select distinct class,value from #temp) as T
where not exists (select 1 from #temp where class=t.class and Value > t.Value and Value < t.Value +5 )
) ends on starts.class=ends.class and starts.GroupNumber = ends.GroupNumber
In both methods I use a select distinct to begin because if you have a dulpicate entry at a group start or end things go awry without it.
Here is one way of getting the information you are after:
SELECT Under5.Class,
(
SELECT MIN(m2.Value)
FROM MyTable AS m2
WHERE m2.Value < 5
AND m2.Class = Under5.Class
) AS ValueMin,
(
SELECT MAX(m3.Value)
FROM MyTable AS m3
WHERE m3.Value < 5
AND m3.Class = Under5.Class
) AS ValueMax
FROM
(
SELECT DISTINCT m1.Class
FROM MyTable AS m1
WHERE m1.Value < 5
) AS Under5
UNION
SELECT Over4.Class,
(
SELECT MIN(m4.Value)
FROM MyTable AS m4
WHERE m4.Value >= 5
AND m4.Class = Over4.Class
) AS ValueMin,
(
SELECT Max(m5.Value)
FROM MyTable AS m5
WHERE m5.Value >= 5
AND m5.Class = Over4.Class
) AS ValueMax
FROM
(
SELECT DISTINCT m6.Class
FROM MyTable AS m6
WHERE m6.Value >= 5
) AS Over4

Accumulating in SQL

I have a query with results like ID, Value. What I want is to get the values in order of their ids and also calculate the accumulated value in another column. take a look at my simplified code:
declare #TempTable Table
(
ID int,
Value int
)
insert into #TempTable values
(1, 10),
(2, -15),
(3, 12),
(4, 18),
(5, 5)
select t1.ID, t1.Value, SUM(t2.Value) AccValue from #TempTable t1
inner join #TempTable t2 on t1.ID >= t2.ID
group by t1.ID, t1.Value
order by t1.ID
Result:
ID Value AccValue
1 10 10
2 -15 -5
3 12 7
4 18 25
5 5 30
What I have come up with, is to use inner join between the result and itself for that purpose. But for huge amount of data, it's clearly a low performance issue.
Is there any other alternative to do that?
In 2012 version, you can use:
SELECT
id,
Value,
AccValue = SUM(Value) OVER (ORDER BY ID
ROWS BETWEEN UNBOUNDED PRECEDING
AND CURRENT ROW)
FROM
#TempTable ;
For previous versions of SQL-Server, see my answer in this similar question: Recursive SQL- How can I get this table with a running total?, with a cursor solution.
Even better, follow the link to the great article by #Aaron Bertrand, that has a thorough test of various methods to calculate a running total: Best approaches for running totals – updated for SQL Server 2012
You can use recursion:
;WITH x AS
(
SELECT
[ID],
[Value],
bal=[Value]
FROM Table1
WHERE [ID] = 1
UNION ALL
SELECT
y.[ID],
y.[Value],
x.bal+(y.[Value]) as bal
FROM x INNER JOIN Table1 AS y
ON y.[ID] = x.[ID] + 1
)
SELECT
[ID],
[Value],
AccValue= bal
FROM x
order by ID
OPTION (MAXRECURSION 10000);
SQL FIDDLE
The generic SQL way to do this is with a correlated subquery (at least, I think that is the cleanest way):
select t.*,
(select sum(t2.value)
from #TempTable t2
where t2.ID <= t.ID
) AccValue
from #TempTable t
SQL Server 2012 has a cumulative sum function:
select t.*,
sum(t.value) over (order by t.id) as AccValue
from #TempTable t

Next/previous record based on current

I have a table which is not sorted by any of column. Is there any way to select next/previous record if I know only Id of current? (I'm using mssql)
Id Label Date
---------------------
1 label1 2011-01-10
7 label2 2011-01-15 -- how to get previous?
5 label3 2011-01-12 -- I know id of this record
10 label10 2011-01-25 -- how to get next?
12 label8 2011-01-13
2 label5 2011-01-29
Thanks in advance!
try this:
VALUES (1, 'label1', '2011-01-10'), (7, 'label2', '2011-01-15'),
(5, 'label3', '2011-01-12'), (10, 'label10', '2011-01-25'),
(12, 'label8', '2011-01-13'), (2, 'label5', '2011-01-29')
select * from table007;
Declare #inptID int=12;
;WITH CTE
as
(
select *, ROW_NUMBER() over (order by (select 0)) as rn
from table007
)
select *
from CTE
where rn in( select rn-1 from CTE where id = #inptID)
union all
select * from CTE where rn in(select rn + 1 from CTE where id = #inptID);
SQL Fiddle Demo
DEMO
If it is not sorted by any column, there is no definitive next or previous record. Data in SQL Server has no order, other than that specified by an ORDER BY clause.
If you really want the previous from the list you enclosed, here is a way.
declare #t table(Id int, Label varchar(10), Date date, s int identity(1,1))
insert #t (id, label, date)
values(1,'label1','2011-01-10'),(7,'label2','2011-01-15'),
(5,'label3','2011-01-12'),(10,'label10','2011-01-25'),
(12,'label8','2011-01-13'),(2,'label5','2011-01-29')
--select the data with a self join
select t1.id as previous_id, t2.id, t2.Label, t2.Date, t3.id, t3.id as next_id
from #t t1
right join
#t t2 on t1.s + 1 = t2.s
left join
#t t3 on t2.s = t3.s - 1