SQL Server - Running Total with Carry Forward - sql-server

Needs some help on the following:
Table #Data contains the Opening and Closing Stock for a product over 5 days
Table #BackData contains some post dated transactions
How can i Update the table #Data with a Running Total including a carry forward
CREATE TABLE #Data (
Prod VARCHAR(20)
,SDate DATE
,OStock INT
,CStock INT
)
CREATE TABLE #BackData (
Prod VARCHAR(20)
,SDate DATE
,CStock INT
)
INSERT INTO #Data
SELECT 'p1', '2016-06-06', 10, 10
UNION ALL
SELECT 'p1', '2016-06-07', 10, 14
UNION ALL
SELECT 'p1', '2016-06-08', 14, 13
UNION ALL
SELECT 'p1', '2016-06-09', 13, 13
UNION ALL
SELECT 'p1', '2016-06-10', 13, 11
INSERT INTO #BackData
SELECT 'p1', '2016-06-06', 2
UNION ALL
SELECT 'p1', '2016-06-07', 4
UNION ALL
SELECT 'p1', '2016-06-09', -1
UNION ALL
SELECT 'p1', '2016-06-10', -2
DROP TABLE #Data
DROP TABLE #BackData
Desired Output :
Prod| SDate |OStock |CStock|
p1 |2016-06-06 |10 |12 |
p1 |2016-06-07 |12 |16 |
p1 |2016-06-08 |16 |16 |
p1 |2016-06-09 |16 |15 |
p1 |2016-06-10 |15 |13 |
EDIT
This is what i had managed to write before i got the answer, using two updates because the actual table had too many columns to use in a single query.
UPDATE D
SET D.CStock = FL.NewCStock
FROM #Data D
INNER JOIN (
SELECT DT.Prod
,DT.SDate
,SUM(IIF(RwNm = 1, DT.CStock, 0) + ISNULL(BD.CStock, 0)) OVER (
PARTITION BY DT.Prod ORDER BY DT.SDate ROWS UNBOUNDED PRECEDING
) NewCStock
FROM (
SELECT Prod
,SDate
,CStock
,ROW_NUMBER() OVER (
PARTITION BY Prod ORDER BY SDate
) AS RwNm
FROM #Data
) DT
LEFT JOIN #BackData BD ON DT.Prod = DT.Prod
AND BD.SDate = DT.SDate
) FL ON D.Prod = FL.Prod
AND D.SDate = FL.SDate
UPDATE D
SET D.OStock = PV.NewOStock
FROM #Data D
INNER JOIN (
SELECT Prod
,SDate
,ISNULL(LAG(CStock) OVER (
PARTITION BY Prod ORDER BY SDate
), CStock) AS NewOStock
FROM #Data
) PV ON D.Prod = PV.Prod
AND D.SDate = PV.SDate

You can use the following query to UPDATE:
;WITH ToUpdate AS (
SELECT d1.OStock, d1.CStock,
COALESCE(LAG(d2.CStock2) OVER (PARTITION BY d2.Prod
ORDER BY d2.SDate),
d1.OStock) AS OStock2,
d2.CStock2
FROM #Data AS d1
JOIN (
SELECT d.Prod, d.SDate, d.OStock, d.CStock,
COALESCE(t.newCStock,
LAG(t.newCStock) OVER (PARTITION BY d.Prod
ORDER BY d.SDate)) AS CStock2
FROM #Data AS d
LEFT JOIN (
SELECT bd.Prod, bd.SDate,
drn.CStock + SUM(bd.CStock) OVER (PARTITION BY bd.Prod
ORDER BY bd.SDate) AS newCStock
FROM #BackData AS bd
INNER JOIN (
SELECT Prod, CStock,
ROW_NUMBER() OVER (PARTITION BY Prod ORDER BY SDate) AS rn
FROM #Data
) AS drn ON bd.Prod = drn.Prod AND drn.rn = 1
) AS t ON t.Prod = d.Prod AND t.SDate = d.SDate
) AS d2 ON d1.Prod = d2.Prod AND d1.SDate= d2.SDate
)
UPDATE ToUpdate
SET OStock = OStock2,
CStock = CStock2
This looks awfully convoluted, but I couldn't think of anything simpler.
Demo here

You can rebuild values in #Data table with the help of recursive CTE:
;WITH cte AS (
SELECT top 1 d.Prod,
d.SDate,
d.OStock,
d.OStock + b.CStock as CStock
FROM #Data d
LEFT JOIN #BackData b
ON b.Prod = d.Prod and b.SDate = d.SDate
ORDER BY d.SDate ASC
UNION ALL
SELECT c.Prod,
DATEADD(day,1,c.SDate),
c.CStock,
c.CStock + ISNULL(b.CStock,0)
FROM cte c
INNER JOIN #Data d
ON d.Prod = c.Prod AND d.SDate = DATEADD(day,1,c.SDate)
OUTER APPLY (SELECT CStock FROM #BackData b WHERE b.Prod = d.Prod and b.SDate = d.SDate) as b
)
SELECT *
FROM cte
Output:
Prod SDate OStock CStock
-------------------- ---------- ----------- -----------
p1 2016-06-06 10 12
p1 2016-06-07 12 16
p1 2016-06-08 16 16
p1 2016-06-09 16 15
p1 2016-06-10 15 13
To update #Data:
UPDATE d
SET OStock = c.OStock, CStock = c.CStock
FROM #Data d
INNER JOIN cte c
ON c.Prod = d.Prod AND c.SDate = d.SDate

Shouldn't the result be like :
Prod SDate OStock CStock
p1 2016-06-06 10 12
p1 2016-06-07 12 20 (#Data CStock 14 + #BakData 2 + 4)
p1 2016-06-08 20 19 (#Data CStock 13 + #BakData 2 + 4)
p1 2016-06-09 19 18 (#Data CStock 13 + #BakData 2 + 4 - 1)
p1 2016-06-10 18 14 (#Data CStock 11 + #BakData 2 + 4 - 1 -2)
This query will produce the above result
update d
set OStock = d.OStock + a.OAdj,
CStock = d.CStock + a.CAdj
from #Data d
cross apply
(
select OAdj = sum(case when Oflag = 1 then x.CStock else 0 end),
CAdj = sum(x.CStock)
from
(
select *, Oflag = case when x.SDate = d.SDate then 0 else 1 end
from #BackData x
where x.Prod = d.Prod
and x.SDate <= d.SDate
) x
) a

Based on your expected output, it seems that you are re-calculating the daily Opening / Closing balance based on the figure from 2016-06-06
Here is a solution that will gives you your expected output.
; with
cte as
(
select Prod, SDate, OStock, CStock,
rn = row_number() over (partition by Prod order by SDate)
from #Data
),
adj as
(
select Prod, SDate, CStock
from cte
where rn = 1
union all
select Prod, SDate, CStock
from #BackData
)
update d
set OStock = coalesce(o.OStock, d.OStock),
CStock = c.CStock
from #Data d
cross apply
(
select OStock = sum(x.CStock)
from adj x
where x.Prod = d.Prod
and x.SDate < d.SDate
) o
cross apply
(
select CStock = sum(x.CStock)
from adj x
where x.Prod = d.Prod
and x.SDate <= d.SDate
) c
Result :
p1 2016-06-06 10 12
p1 2016-06-07 12 16
p1 2016-06-08 16 16
p1 2016-06-09 16 15
p1 2016-06-10 15 13

Related

Recursive cte with values from 2 tables

I need some help with this please.
I would like to create a recursive query with values from a table for the anchor, multiplied by a coefficient from another table.
Let me be more explicit :
Tables structure and filling :
create table T
(
Site varchar(10) primary key,
Price money,
Year int
);
create table B
( Site varchar(10),
Coeff float,
Year int
);
insert into T values /* Each Site appears only once here */
('A', 125.10, 2003),
('B', 78.10, 2002),
('C', 23.34, 2001)
insert into B values /* Each (Site,Year) appears only once here */
('A', 12, 2003),
('A', 0.111, 2004),
('B', 0.322, 2002),
('B', 0.333, 2003),
('C', 0.555, 2001),
('C', 0.666, 2002)
My recursive formula is :
Price (n) = Price (n-1)* Coeff(n-1)
(where n is the year)
Here is my last attempt :
;WITH cte
AS (SELECT T.Site, T.Year, T.Price as RootPrice FROM T
UNION ALL
SELECT T.Site, T.Year, CAST(cte.RootPrice * B.Coeff AS MONEY) AS PriceYear
FROM T INNER JOIN cte ON T.Site = cte.Site AND T.Year = cte.Year INNER JOIN B ON cte.Year = B.Year AND cte.Site = B.Site)
SELECT * FROM cte
This cte is running endlessly. What am I missing ?
Edit :
Output needed :
Site | Price | Year
---------------------------
A | 1501.2 | 2003
A | 166.78 | 2004
B | 25.15 | 2002
B | 8.37 | 2003
C | 12.95 | 2001
C | 8.63 | 2002
This produces the output you want:
;WITH CTE AS
(
SELECT
Site = T.Site,
Year = T.Year,
Price = CONVERT(MONEY, T.Price * B.Coeff)
FROM
T AS T
INNER JOIN B AS B ON
T.Site = B.Site AND
T.Year = B.Year
UNION ALL
SELECT
Site = C.Site,
Year = C.Year + 1,
Price = CONVERT(MONEY, C.Price * B.Coeff)
FROM
CTE AS C
INNER JOIN B AS B ON
C.Site = B.Site AND
C.Year + 1 = B.Year
)
SELECT
*
FROM
CTE AS C
ORDER BY
C.Site,
C.Year
The problem with your solution is that the anchor doesn't start with the correct price, you should multiply the price with coeff on the anchor. Keep in mind that the anchor is the first set of the resulting CTE and it is included in it.
So your anchor:
SELECT
T.Site,
T.Year,
T.Price as RootPrice
FROM
T
Should start with the correct price for that year:
SELECT
Site = T.Site,
Year = T.Year,
Price = CONVERT(MONEY, T.Price * B.Coeff)
FROM
T AS T
INNER JOIN B AS B ON
T.Site = B.Site AND
T.Year = B.Year
And remove the T reference on the recursive set, since you don't need it anymore.
If you also want to see the root prices, you can tamper a little with the recursive join expression:
;WITH CTE AS
(
SELECT
Site = T.Site,
Year = T.Year,
Price = T.Price,
IsRoot = 1
FROM
#T AS T
UNION ALL
SELECT
Site = C.Site,
Year = CASE WHEN C.IsRoot = 1 THEN C.Year ELSE C.Year + 1 END,
Price = CONVERT(MONEY, C.Price * B.Coeff),
IsRoot = 0
FROM
CTE AS C
INNER JOIN #B AS B ON C.Site = B.Site
WHERE
(C.IsRoot = 1 AND C.Year = B.Year) OR
(C.IsRoot = 0 AND C.Year + 1 = B.Year)
)
SELECT
*
FROM
CTE AS C
ORDER BY
C.Site,
C.Year,
C.IsRoot DESC
/*
Results:
Site Year Price IsRoot
---------- ----------- --------------------- -----------
A 2003 125,10 1
A 2003 1501,20 0
A 2004 166,6332 0
B 2002 78,10 1
B 2002 25,1482 0
B 2003 8,3744 0
C 2001 23,34 1
C 2001 12,9537 0
C 2002 8,6272 0
*/

Get currency exchange rate in SQL

Using SQL Server, I have...
AccDocumentItems Table:
SLId DL1Id DL2Id Debit Credit CurId ExchangeRate Cnt
------------------------------------------------------------------------------
S1 D1 D4 2000 0 1 1000 2
S1 D1 D4 0 6000 1 1500 4
S1 D1 D4 6000 0 1 1200 5
S2 D2 D4 4000 0 2 1000 4
S2 D2 D4 0 2000 2 1000 2
S2 D2 D4 3000 0 2 1500 2
I am trying to write a query in sql that group by CurId then substract Cnt column that has debit greater than 0 from Cnt Column that has Debit 0,for example row 1 to 3 has CurId 1 and row 1 has Debit greater than zero,then we have (2-4+5)=3 and row 4 to 6 has CurId 2 then we have (4-2+2)=4 finally I want sum of them 3*Last ExchangeRate in ExchangeRates Table, and 4*Last ExchangeRate in ExchangeRates Table, then sum of (3*Last ExchangeRate+4*Last ExchangeRate).
ExchangeRates Table:
CurrencyId EffectiveDate ExchangeRate
------------------------------------------------
1 2016-1-1 1000
2 2016-1-2 1200
2 2016-1-3 2000
1 2016-1-4 1500
result=(3*Last ExchangeRate+4*Last ExchangeRate)-((sum of Debit)-(sum
of Credit))
in this example Last ExchangeRate for CurrencyId 1 is 1500, and Last ExchangeRate for CurrencyId 2 is 2000 finally I want this result
result=(3*1500+4*2000)-(15000-8000)
This solution is based on your calculations and works only with 2 CurId's.
declare #cur_1 int = 1
declare #cur_2 int = 2
;with AccDocumentItems as (
select
*
from
(values
('S1','D1','D4',2000,0,1,1000,2)
,('S1','D1','D4',0,6000,1,1500,4)
,('S1','D1','D4',6000,0,1,1200,5)
,('S2','D2','D4',4000,0,2,1000,4)
,('S2','D2','D4',0,2000,2,1000,2)
,('S2','D2','D4',3000,0,2,1500,2)
) t (SLId, DL1Id, DL2Id, Debit, Credit, CurId, ExRate, Cnt)
)
, ExchangeRates as (
select
*
from
(values
(1,'2016-1-1',1000)
,(2,'2016-1-2',1200)
,(2,'2016-1-3',2000)
,(1,'2016-1-4',1500)
) t (CurrencyId, EffectiveDate, ParityRate)
)
select
sum(t.cnt * q.ParityRate) - sum(diff)
from
(
select
CurId, diff = sum(Debit) - sum(Credit), cnt = sum(cnt * case when Debit = 0 then -1 else 1 end)
from
AccDocumentItems
where
CurId in (#cur_1, #cur_2)
group by CurId
) t
join (
select
top 1 with ties *
from
ExchangeRates
order by row_number() over (partition by CurrencyId order by EffectiveDate desc)
) q on t.CurId = q.CurrencyId
This solution works for All CurId's.
Declare #Result int
Declare #DeCr int
Declare #Cur int
Set #DeCr = (
Select SUM(Debit)-SUM(Credit)
From dbo.AccDocumentItems
)
Set #Cur =
(
Select Sum(ParityRate * Zarib)
From
(
Select ExchangeRates.CurrencyId as cuur,ParityRate
From
(
Select Max(EffectiveDate) As MaxDate,CurrencyId
From dbo.ExchangeRates
Group by CurrencyId
)As Table1
left join
dbo.ExchangeRates
ON table1.currencyid = ExchangeRates.CurrencyId
And Table1.MaxDate = EffectiveDate
) As Table3
inner join
(
Select Curid
, sum(cnt*signs) As Zarib
From
(
Select CurId
, Cnt
, Case
When Debit = 0 Then -1
Else 1
End As Signs
From dbo.AccDocumentItems
) As Table2
Group by Curid
) As Table4
On Table3.cuur = Table4.Curid
)
Select #Cur - #DeCr
Declare #Result int
Declare #DeCr int
Declare #Cur int
Set #DeCr = (
Select SUM(Debit)-SUM(Credit)
From dbo.AccDocumentItems
)
Set #Cur =
(
Select Sum(ParityRate * Zarib)
From
(
Select ExchangeRates.CurrencyId as cuur,ParityRate
From
(
Select Max(EffectiveDate) As MaxDate,CurrencyId
From dbo.ExchangeRates
Group by CurrencyId
)As Table1
left join
dbo.ExchangeRates
ON table1.currencyid = ExchangeRates.CurrencyId
And Table1.MaxDate = EffectiveDate
) As Table3
inner join
(
Select Curid
, sum(cnt*signs) As Zarib
From
(
Select CurId
, Cnt
, Case
When Debit = 0 Then -1
Else 1
End As Signs
From dbo.AccDocumentItems
) As Table2
Group by Curid
) As Table4
On Table3.cuur = Table4.Curid
)
set #result = #Cur - #DeCr
Select #result

SQL alternative to inner join

I have a table which has data that appears as:
Staion Date Temperature
A 2015-07-31 8
B 2015-07-31 6
C 2015-07-31 8
A 2003-02-21 4
B 2003-02-21 7
C 2003-02-21 7
For each date I need to create arrays so that it has the following combination:
c1 = (A + B)/2, c2 = (A + B + C)/3 and c3 = (B + C)/2
Right I am doing three different inner join on the table itself and doing a final inner join to achieve the following as result:
Date c1 c2 c3
2015-07-31 7 7.33 7
2003-02-21 5.5 6 7
Is there a cleaner way to do this?
No need for a JOIN, you could simply use a GROUP BY and an aggregation function:
WITH CTE AS
(
SELECT [Date],
MIN(CASE WHEN Staion = 'A' THEN Temperature END) A,
MIN(CASE WHEN Staion = 'B' THEN Temperature END) B,
MIN(CASE WHEN Staion = 'C' THEN Temperature END) C
FROM dbo.YourTable
GROUP BY [date]
)
SELECT [Date],
(A+B)/2 c1,
(A+B+C)/3 c2,
(B+C)/2 c3
FROM CTE;
SUM Function is very useful in such cases:
SELECT
c1 = SUM(CASE WHEN Staion IN ('A', 'B') THEN Temperature ELSE 0 END) / 2,
c2 = SUM(Temperature) / 3,
c3 = SUM(CASE WHEN Staion IN ('B', 'C') THEN Temperature ELSE 0 END) / 2,
[Date]
FROM dbo.Table
GROUP BY [Date]
You can do it with just two joins and almost literally the formulas you've provided:
declare #t table (Station char(1) not null,[Date] date not null, Temperature int not null)
insert into #t(Station,[Date],Temperature) values
('A','20150731',8),
('B','20150731',6),
('C','20150731',8),
('A','20030221',4),
('B','20030221',7),
('C','20030221',7)
select
B.[Date],
c1 = (A.Temperature + B.Temperature)/2.0,
c2 = (A.Temperature + B.Temperature + C.Temperature)/3.0,
c3 = (B.Temperature + C.Temperature)/2.0
from
#t B
inner join
#t A
on
B.[Date] = A.[Date]
inner join
#t C
on
B.[Date] = C.[Date]
where
A.Station = 'A' and
B.Station = 'B' and
C.Station = 'C'
Result:
Date c1 c2 c3
---------- --------------- ----------- ----------
2015-07-31 7.000000 7.333333 7.000000
2003-02-21 5.500000 6.000000 7.000000
You can use pivot and calculation on pivoted data as below:
select [Date], c1 = (A+B)/2.0, c2 = (A+B+C)/3.0, C3 = (B+C)/2.0 from
( select * from #yourstation ) s
pivot (max(temparature) for station in ([A], [B], [C])) p
Your input table:
create table #yourStation (station char(1), date date, Temparature int)
insert into #yourStation (station, date, Temparature) values
('A','2015-07-31', 8 )
,('B','2015-07-31', 6 )
,('C','2015-07-31', 8 )
,('A','2003-02-21', 4 )
,('B','2003-02-21', 7 )
,('C','2003-02-21', 7 )

How to create a View showing rows from splitted data

With a table as:
Name Num Value
----------------------
Peter 10 10
Mary 10,15 5,10
John 5,20 10,20
How can I get a result as the follow table with a View ?
Name Num Value
------------------
Peter 10 10
Mary 10 5
Mary 15 10
John 5 10
John 20 20
Notice Mary and John have multiple data (comma-delimited).
I've a function to do the split of a cell and returns a table but just for a specific row and cell, hos to iterate from all table ?
Aditional Info:
SELECT NAM.NAME, Data AS NUM, VAL.VALUE
FROM dbo.Split((SELECT NUM FROM t1 WHERE LineNum = 2), ','))
CROSS APPLY (
SELECT Id As Id1, Data AS VALUE
FROM dbo.Split((SELECT VALUE FROM t1 WHERE LineNum = 2), ','))
) AS VAL
CROSS APPLY (
SELECT NAME FROM t1 WHERE LineNum = 2
) AS NAM
WHERE Id = Id1
Note: LineNum is row number from t1
From Second Row of t1 (Mary | 10,15 | 5,10)
Previous Function returns table as:
Name Num Value
------------------
Mary 10 5
Mary 15 10
Split Function from a cell (10,15) returns:
Id Data
-----------
1 10
2 15
===============================================
My Split Function:
CREATE FUNCTION [dbo].[Split]
(
#String NVARCHAR(4000),
#Delimiter NCHAR(1)
)
RETURNS TABLE
AS
RETURN
(
WITH Split(stpos,endpos)
AS(
SELECT 0 AS stpos, CHARINDEX(#Delimiter,#String) AS endpos
UNION ALL
SELECT endpos+1, CHARINDEX(#Delimiter,#String,endpos+1)
FROM Split
WHERE endpos > 0
)
SELECT 'Id' = ROW_NUMBER() OVER (ORDER BY (SELECT 1)),
'Data' = SUBSTRING(#String,stpos,COALESCE(NULLIF(endpos,0),LEN(#String)+1)-stpos)
FROM Split
)
Declare #YourTable table (Name varchar(50), Num varchar(50), Value varchar(50))
Insert into #YourTable values
('Peter','10', '10'),
('Mary','10,15','5,10'),
('John','5,20', '10,20')
Select A.Name
,B.*
From #YourTable A
Cross Apply (
Select Num = A.RetVal
,Value = B.RetVal
From (
Select RetSeq = Row_Number() over (Order By (Select null))
,RetVal = LTrim(RTrim(B.i.value('(./text())[1]', 'varchar(max)')))
From (Select x = Cast('<x>'+ Replace(A.Num,',','</x><x>')+'</x>' as xml).query('.')) as A
Cross Apply x.nodes('x') AS B(i)
) A
Join (
Select RetSeq = Row_Number() over (Order By (Select null))
,RetVal = LTrim(RTrim(B.i.value('(./text())[1]', 'varchar(max)')))
From (Select x = Cast('<x>'+ Replace(A.Value,',','</x><x>')+'</x>' as xml).query('.')) as A
Cross Apply x.nodes('x') AS B(i)
) B on A.RetSeq=B.RetSeq
) B
Returns
Name Num Value
Peter 10 10
Mary 10 5
Mary 15 10
John 5 10
John 20 20
Edit - Another option with a UDF
Select A.Name
,B.*
From #YourTable A
Cross Apply (
Select Num = A.RetVal
,Value = B.RetVal
From [dbo].[udf-Str-Parse-8K](A.Num,',') A
Join [dbo].[udf-Str-Parse-8K](A.Value,',') B
on A.RetSeq=B.RetSeq
) B
The fastest UDF
CREATE FUNCTION [dbo].[udf-Str-Parse-8K] (#String varchar(max),#Delimiter varchar(10))
Returns Table
As
Return (
with cte1(N) As (Select 1 From (Values(1),(1),(1),(1),(1),(1),(1),(1),(1),(1)) N(N)),
cte2(N) As (Select Top (IsNull(DataLength(#String),0)) Row_Number() over (Order By (Select NULL)) From (Select N=1 From cte1 a,cte1 b,cte1 c,cte1 d) A ),
cte3(N) As (Select 1 Union All Select t.N+DataLength(#Delimiter) From cte2 t Where Substring(#String,t.N,DataLength(#Delimiter)) = #Delimiter),
cte4(N,L) As (Select S.N,IsNull(NullIf(CharIndex(#Delimiter,#String,s.N),0)-S.N,8000) From cte3 S)
Select RetSeq = Row_Number() over (Order By A.N)
,RetVal = Substring(#String, A.N, A.L)
From cte4 A
);
--Much faster than str-Parse, but limited to 8K
--Select * from [dbo].[udf-Str-Parse-8K]('Dog,Cat,House,Car',',')

Get the difference between top and last value in result set

My table looks something like
deal_id test_value run_date
820117648 1.2 2014-03-31
820117648 1.33 2014-04-30
820117648 1.33 2014-05-30
820117648 1.26 2014-06-30
820117648 1.11 2014-07-31
820117648 0.58 2014-09-30
820117648 1.64 2014-10-31
820117648 0.64 2014-11-28
820117648 3.65 2014-12-31
820117648 3.8 2015-03-11
820117649 0.64 2014-09-31
820117649 0.23 2014-10-31
820117649 0.64 2014-11-28
820117649 3.65 2014-12-31
820117649 3.8 2015-03-11
SELECT deal_id,test_value,run_date FROM ems.cdotests
WHERE run_date >= Dateadd(month, -4 Getdate())
I am trying to grab the last 4 run_date record from the whole set and then
I need to find the difference between the testValue on the first rundate of my select criteria and the last rundate i.e for deal_id 820117649 3.8 - 0.23 = 3.57 and for deal_id 820117648 it should be 3.8 -1.64 = 2.16
This is the large table with multiple deal id and several run_date associated with each dealid and may have data for the last 15 years or so
Any suggestion would be really helpfull
Try this solution. It will not be bound to particular deal_id.
DECLARE #t TABLE ( ID INT, V MONEY, D DATE )
INSERT INTO #t
VALUES ( 1, 1, '20140101' ),
( 1, 4, '20140102' ),
( 1, 2, '20140103' ),
( 1, 7, '20140104' ),
( 2, 5, '20140104' ),
( 2, 8, '20140110' ),
( 2, 11, '20140105' );
WITH cte
AS ( SELECT ID ,
D ,
V ,
MIN(D) OVER ( PARTITION BY ID ORDER BY D ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING ) AS Min ,
MAX(D) OVER ( PARTITION BY ID ORDER BY D ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING ) AS Max
FROM #t
)
SELECT c1.ID ,
c2.V - c1.V AS V
FROM cte c1
JOIN cte c2 ON c1.ID = c2.ID AND c1.D < c2.D
WHERE ( c1.D = c1.MIN OR c1.D = c1.MAX ) AND ( c2.D = c2.MIN OR c2.D = c2.MAX )
Output:
ID V
1 6.00
2 3.00
You have changed you question. For latest 6 month, add filter CTE above main CTE:
;WITH filter
AS ( SELECT ID ,
D ,
V ,
ROW_NUMBER() OVER ( PARTITION BY ID ORDER BY D DESC ) AS RN
FROM #t
),
cte
AS ( SELECT ID ,
D ,
V ,
MIN(D) OVER ( PARTITION BY ID ORDER BY D ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING ) AS Min ,
MAX(D) OVER ( PARTITION BY ID ORDER BY D ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING ) AS Max
FROM filter
WHERE RN <= 6
)
SELECT c1.ID ,
c2.V - c1.V AS V
FROM cte c1
JOIN cte c2 ON c1.ID = c2.ID AND c1.D < c2.D
WHERE ( c1.D = c1.MIN OR c1.D = c1.MAX )
AND ( c2.D = c2.MIN OR c2.D = c2.MAX )
AND (c2.V - c1.V > 5)
You can use window functions
WITH C AS(
SELECT ROW_NUMBER() OVER (PARTITION BY deal_Id ORDER BY deal_Id) Rn
,deal_Id
,test_value
,run_date
FROM ems.cdotests
)
SELECT id1 deal_id, t2 - t1 diff
FROM (SELECT c1.deal_id id1
,c1.test_value t1
,c2.deal_id id2
, c2.test_value t2
FROM C c1
INNER JOIN C c2 ON c1.deal_id = c2.deal_id
WHERE c1.Rn = 1
AND c2.Rn = (SELECT TOP 1 MAX(Rn) FROM C GROUP BY deal_id)
) t
So late to answer, but a way is this:
;with t as (
select *,
row_number() over (partition by deal_id order by run_date desc) seq
from ems.cdotests
)
select
t1.deal_id, t1.run_date run_date1, t1.test_value test_value1,
t2.run_date run_date4, t2.test_value test_value4,
t1.test_value - t2.test_value diff
from t t1
join t t2
on t1.deal_id = t2.deal_id
where t1.seq = 1
and t2.seq = 4
-- as an extra condition of those should have at least 5 records:
and exists (select 1 from t ti where ti.deal_id = t1.deal_id and ti.seq > 5)
[SQL Fiddle]

Resources