I need to create a dataset that calculates values using results from the previous row, but only the first row contains an actual value.
This is my dataset:
DROP TABLE IF EXISTS #tmp
CREATE TABLE #tmp (
Date DATE
, Month INT
, Increment FLOAT
, Results FLOAT
)
INSERT INTO #tmp(Date, Month, Increment, Results)
VALUES
('7/1/2022', 0, 0.0027347877960046, 0.00439631056653702)
, ('7/1/2022', 1, 0.0332610867687839, NULL)
, ('7/1/2022', 2, 0.0541567096339919, NULL)
, ('7/1/2022', 3, 0.0534245249728661, NULL)
, ('7/1/2022', 4, 0.0497604938051764, NULL)
, ('7/1/2022', 5, 0.0448266874224477, NULL)
, ('7/1/2022', 6, 0.0637221774467554, NULL)
, ('7/1/2022', 7, 0.0953341922962425, NULL)
, ('7/1/2022', 8, 0.117940928214655, NULL)
, ('7/1/2022', 9, 0.0955895317176205, NULL)
, ('6/1/2022', 0, 0.0027347877960046, 0.00439631056653702)
, ('6/1/2022', 1, 0.0332610867687839, 0.00752724387918406)
, ('6/1/2022', 2, 0.0541567096339919, NULL)
, ('6/1/2022', 3, 0.0534245249728661, NULL)
, ('6/1/2022', 4, 0.0497604938051764, NULL)
, ('6/1/2022', 5, 0.0448266874224477, NULL)
, ('6/1/2022', 6, 0.0637221774467554, NULL)
, ('6/1/2022', 7, 0.0953341922962425, NULL)
, ('6/1/2022', 8, 0.117940928214655, NULL)
, ('6/1/2022', 9, 0.0955895317176205, NULL)
I need the Results = Previous Results + Increment
Date Month Increment Results
6/1/2022 0 0.002734788 0.004396311
6/1/2022 1 0.033261087 0.007527244
6/1/2022 2 0.05415671 0.061683954
6/1/2022 3 0.053424525 0.115108478
6/1/2022 4 0.049760494 0.164868972
6/1/2022 5 0.044826687 0.20969566
6/1/2022 6 0.063722177 0.273417837
6/1/2022 7 0.095334192 0.368752029
6/1/2022 8 0.117940928 0.486692958
6/1/2022 9 0.095589532 0.582282489
7/1/2022 0 0.002734788 0.004396311
7/1/2022 1 0.033261087 0.037657397
7/1/2022 2 0.05415671 0.091814107
7/1/2022 3 0.053424525 0.145238632
7/1/2022 4 0.049760494 0.194999126
7/1/2022 5 0.044826687 0.239825813
7/1/2022 6 0.063722177 0.303547991
7/1/2022 7 0.095334192 0.398882183
7/1/2022 8 0.117940928 0.516823111
7/1/2022 9 0.095589532 0.612412643
What's the best way to go about this?
Seems like you could use a cumulative SUM here. As, however, you need to use the Results from Month 0 and ignore the value of Increment for Month 0, you also need to use conditional aggregation:
SELECT *,
SUM(CASE MONTH WHEN 0 THEN Results END) OVER () +
SUM(CASE WHEN Month > 0 THEN Increment END) OVER (ORDER BY Month ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) AS Results2
FROM #tmp;
db<>fiddle
I found a solution using #larnu's row between method. Needed to split the data in two tables and union them together. Table one contains the actual Results column and table 2 contains the incrementally calculated Results.
DROP TABLE IF EXISTS #tmp;
CREATE TABLE #tmp (
Date DATE
, Month INT
, Increment FLOAT
, Results FLOAT
)
;
INSERT INTO #tmp(Date, Month, Increment, Results)
VALUES
('7/1/2022', 0, 0.0027347877960046, 0.00439631056653702)
, ('7/1/2022', 1, 0.0332610867687839, NULL)
, ('7/1/2022', 2, 0.0541567096339919, NULL)
, ('7/1/2022', 3, 0.0534245249728661, NULL)
, ('7/1/2022', 4, 0.0497604938051764, NULL)
, ('7/1/2022', 5, 0.0448266874224477, NULL)
, ('7/1/2022', 6, 0.0637221774467554, NULL)
, ('7/1/2022', 7, 0.0953341922962425, NULL)
, ('7/1/2022', 8, 0.117940928214655, NULL)
, ('7/1/2022', 9, 0.0955895317176205, NULL)
, ('6/1/2022', 0, 0.0027347877960046, 0.00439631056653702)
, ('6/1/2022', 1, 0.0332610867687839, 0.00752724387918406)
, ('6/1/2022', 2, 0.0541567096339919, NULL)
, ('6/1/2022', 3, 0.0534245249728661, NULL)
, ('6/1/2022', 4, 0.0497604938051764, NULL)
, ('6/1/2022', 5, 0.0448266874224477, NULL)
, ('6/1/2022', 6, 0.0637221774467554, NULL)
, ('6/1/2022', 7, 0.0953341922962425, NULL)
, ('6/1/2022', 8, 0.117940928214655, NULL)
, ('6/1/2022', 9, 0.0955895317176205, NULL)
;
WITH mt AS (
SELECT
Date
, MAX(Month) AS Month
FROM #tmp
WHERE Results IS NOT NULL
GROUP BY Date
),
results AS (
SELECT
t.*
, SUM(CASE WHEN t.Results IS NOT NULL THEN t.Results END) OVER(PARTITION BY t.Date) +
SUM(CASE WHEN t.Results IS NULL THEN t.Increment END) OVER (PARTITION BY t.Date ORDER BY t.Date, t.Month ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) AS Results2
FROM #tmp t
INNER JOIN mt mt ON t.Date = mt.Date AND t.Month >= mt.Month
)
SELECT
t.Date
, t.Month
, t.Increment
, t.Results
FROM #tmp t
LEFT JOIN mt mt ON t.Date = mt.Date
WHERE t.Month <= mt.Month
UNION ALL
SELECT
r.Date
, r.Month
, r.Increment
, r.Results2 AS Results
FROM results r
WHERE r.results2 IS NOT NULL
ORDER BY Date, Month
Related
I am working on a query to find all orders that have had all the necessary modifications completed and the work item where the last modification was complete. The table that keeps track of all work done on the order can have multiple entries per item. And no, I unfortunately do not have the ability to modify the schema.
Orders Table:
OrderId(int), CustomerId(int), OrderDate(DateTime)
1, 58, '2021-01-01'
2, 75, '2021-01-01'
3, 78, '2021-01-01'
4, 50, '2021-01-01'
Work Table:
WorkId(int), OrderId(int), Mod1Completed(bit), Mod2Completed(bit), Mod3Completed(bit), ModDate (DateTime), ModBy (int)
1005, 1, 0, 1, 1, '2021-02-01', 685
1006, 1, 1, 1, 0, '2021-02-03', 875
1007, 2, 0, 1, 0, '2021-02-01', 211
1008, 3, 1, 1, 1, '2021-01-15', 669
Sample output:
1006, 1, 1, 1, 0, '2021-02-03', 875
1008, 3, 1, 1, 1, '2021-01-15', 669
I have the following query that I think is correct (still testing) but it seems clunky and am trying to improve it. For what it's worth, the Work table is regularly purged and would not have massive amounts of data (most likely < 100 rows at any particular time)
WITH AnySuccessful AS(
SELECT * FROM Work WITH (NOLOCK) WHERE Mod1Completed = 1
OR Mod2Completed = 1
OR Mod3Completed = 1
),
SuccessfulCount AS(
SELECT OrderId,
MAX(s.ModDate) AS ModDate ,
Max(CAST(Mod1Completed as int)) + Max(CAST(Mod2Completed as int)) + Max(CAST(Mod3Completed as int)) AS Successes
FROM AnySuccessful s
GROUP BY OrderId
),
AllSuccessful AS(
Select S.OrderId, WorkID, sc.Successes From AnySuccessful S
Inner Join SuccessfulCount sc on s.OrderId=sc.OrderId and s.ModDate = sc.ModDate AND sc.Successes=3
)
Select w.* from Work w
inner join AllSuccessful ASF on W.OrderId = ASF.OrderId AND W.WorkId = ASF.WorkId
SQLFiddle
This might be easier to follow.. assuming the workid and moddate are always in order
select OrderId
, max(WorkId) as WorkId
, max(convert(int, Mod1Completed)) as Mod1Completed
, max(convert(int, Mod2Completed)) as Mod2Completed
, max(convert(int, Mod3Completed)) as Mod3Completed
, max(ModDate) as ModDate
from Work
group by OrderId
having max(convert(int, Mod1Completed)) + max(convert(int, Mod2Completed)) + max(convert(int, Mod3Completed)) = 3
I have 2 tables #Claims and #ClaimsActivity:
Query:
declare #Claims table (ClaimID int)
insert into #Claims
values (6070), (6080)
declare #ClaimsActivity table
(
Activityid int,
ClaimID int,
Activity int,
ActivityDate datetime,
ClaimStatus int
)
insert into #ClaimsActivity
values (1, 6070, 0, '2017-11-05 20:23:16.640', 0),
(3, 6070, 6, '2017-11-06 13:50:28.203', 0),
(4, 6070, 9, '2017-11-07 13:39:28.410', 0),
(5, 6070, 10, '2017-11-07 13:40:49.980', 0),
(7, 6070, 8, '2017-11-07 15:46:18.367', 1),
(8, 6070, 8, '2017-11-07 16:50:49.543', 1),
(9, 6070, 9, '2017-11-07 16:50:54.733', 0),
(10, 6070, 4, '2017-11-07 16:55:22.135', 0),
(11, 6070, 6, '2017-11-08 18:32:15.101', 0),
(12, 6080, 0, '2017-11-12 11:15:17.199', 0),
(13, 6080, 8, '2017-11-13 09:12:23.203', 1)
select *
from #Claims
select *
from #ClaimsActivity
order by ActivityDate
I need to add 2 columns based on data in #ClaimsActivity: IsReopened and DateReopened
The logic is:
If the last ClaimStatus (based on ActivityDate) = 1 then IsReopened = 0
But if the last ClaimStatus = 0 then it need to go and check whether one of the Activity is = 9 (Claim Reopened)
and if one of the Activity = 9 then IsReopened should = 1 and DateReopened should be the last date when it was reopened
I brought column StatusOfClaim, but I also need IsReopened and DateReopened
select
Claimid,
isnull((select top 1
case when al.ClaimStatus = 1
then 'Closed'
else 'Open'
end
from
#ClaimsActivity al
where
C.ClaimID = al.ClaimID
order by
al.ActivityDate desc), 'Open') as 'Status of Claim',
NULL as 'isReopen',
NULL as 'DateReopened'
from
#Claims c
Desired output should be like this:
There are many different ways you can accomplish this, but here is an example using CROSS APPLY and OUTER APPLY:
SELECT
ClaimID,
CASE WHEN tmp.IsOpen = 1 THEN 'Open' ELSE 'Closed' END AS 'Status of Claim',
CASE WHEN tmp.IsOpen = 1 AND lastReopen.Activityid IS NOT NULL THEN 1 ELSE 0 END AS 'isReopen',
lastReopen.ActivityDate AS 'DateReopened'
FROM #Claims c
CROSS APPLY (
SELECT ISNULL((
SELECT TOP 1 CASE WHEN al.ClaimStatus = 1 THEN 0 ELSE 1 END
FROM #ClaimsActivity al
WHERE c.ClaimID = al.ClaimID
ORDER BY al.ActivityDate DESC
), 1) AS IsOpen
) tmp
OUTER APPLY (
SELECT TOP 1
al.Activityid,
al.ActivityDate
FROM #ClaimsActivity al
WHERE c.ClaimID = al.ClaimID AND al.Activity = 9
ORDER BY al.ActivityDate DESC
) lastReopen
The CROSS APPLY is just used to produce a column that tells us whether a claim is open or closed, and we can reuse this throughout the rest of the query.
The OUTER APPLY is used to grab to the last "reopen" activity for each claim, of which you want the date.
I can't attest to the performance of this query, but this should at least give you the correct results.
I have a recursive query that is working as intended for calculating weighted average cost for inventory calculation. My problem is that I need multiple weighted average from the same query grouped by different columns. I know I can solve the issue by calculating it multiple times, one for each key-column. But because of query performance considerations, I want it to be traversed once. Sometimes I have 1M+ rows.
I have simplified the data and replaced weighted average to a simple sum to make my problem more easy to follow.
How can I get the result below using recursive cte? Remember that I have to use a recursive query to calculate weighted average cost. I am on sql server 2016.
Example data (Id is also the sort order. The Id and Key is unique together.)
Id Key1 Key2 Key3 Value
1 1 1 1 10
2 1 1 1 10
3 1 2 1 10
4 2 2 1 10
5 1 2 1 10
6 1 1 2 10
7 1 1 1 10
8 3 3 1 10
Expected result
Id Key1 Key2 Key3 Value Key1Sum Key2Sum Key3Sum
1 1 1 1 10 10 10 10
2 1 1 1 10 20 20 20
3 1 2 1 10 30 10 30
4 2 2 1 10 10 20 40
5 1 2 1 10 40 30 50
6 1 1 2 10 50 30 10
7 1 1 1 10 60 40 60
8 3 3 1 10 10 10 70
EDIT
After some well deserved criticism I have to be much better in how I make a question.
Here is an example and why I need a recursive query. In the example I get the result for Key1, but I need it for Key2 and Key3 as well in the same query. I know that I can repeat the same query three times, but that is not preferable.
DECLARE #InventoryItem AS TABLE (
IntentoryItemId INT NULL,
InventoryOrder INT,
Key1 INT NULL,
Key2 INT NULL,
Key3 INT NULL,
Quantity NUMERIC(22,9) NOT NULL,
Price NUMERIC(16,9) NOT NULL
);
INSERT INTO #InventoryItem (
IntentoryItemId,
InventoryOrder,
Key1,
Key2,
Key3,
Quantity,
Price
)
VALUES
(1, NULL, 1, 1, 1, 10, 1),
(2, NULL, 1, 1, 1, 10, 2),
(3, NULL, 1, 2, 1, 10, 2),
(4, NULL, 2, 2, 1, 10, 1),
(5, NULL, 1, 2, 1, 10, 5),
(6, NULL, 1, 1, 2, 10, 3),
(7, NULL, 1, 1, 1, 10, 3),
(8, NULL, 3, 3, 1, 10, 1);
--The steps below will give me the cost "grouped" by Key1
WITH Key1RowNumber AS (
SELECT
IntentoryItemId,
ROW_NUMBER() OVER (PARTITION BY Key1 ORDER BY IntentoryItemId) AS RowNumber
FROM #InventoryItem
)
UPDATE #InventoryItem
SET InventoryOrder = Key1RowNumber.RowNumber
FROM #InventoryItem InventoryItem
INNER JOIN Key1RowNumber
ON Key1RowNumber.IntentoryItemId = InventoryItem.IntentoryItemId;
WITH cte AS (
SELECT
IntentoryItemId,
InventoryOrder,
Key1,
Quantity,
Price,
CONVERT(NUMERIC(22,9), InventoryItem.Quantity) AS CurrentQuantity,
CONVERT(NUMERIC(22,9), (InventoryItem.Quantity * InventoryItem.Price) / NULLIF(InventoryItem.Quantity, 0)) AS AvgPrice
FROM #InventoryItem InventoryItem
WHERE InventoryItem.InventoryOrder = 1
UNION ALL
SELECT
Sub.IntentoryItemId,
Sub.InventoryOrder,
Sub.Key1,
Sub.Quantity,
Sub.Price,
CONVERT(NUMERIC(22,9), Main.CurrentQuantity + Sub.Quantity) AS CurrentQuantity,
CONVERT(NUMERIC(22,9),
((Main.CurrentQuantity) * Main.AvgPrice + Sub.Quantity * Sub.price)
/
NULLIF((Main.CurrentQuantity) + Sub.Quantity, 0)
) AS AvgPrice
FROM CTE Main
INNER JOIN #InventoryItem Sub
ON Main.Key1 = Sub.Key1
AND Sub.InventoryOrder = main.InventoryOrder + 1
)
SELECT cte.IntentoryItemId, cte.AvgPrice
FROM cte
ORDER BY IntentoryItemId
Why you will want to calculate on 1M+ rows ?
Secondly I think your db design is wrong ? key1 ,key2,key3 should have been unpivoted and one column called Keys and 1 more column to identify each key group.
It will be clear to you in below example.
If I am able to optimize my query then I can think of calculating many rows else I try to limit number of rows.
Also if possible you can think of keeping calculated column of Avg Price.i.e. when table is populated then you can calculate and store it.
First let us know, if output is correct or not.
DECLARE #InventoryItem AS TABLE (
IntentoryItemId INT NULL,
InventoryOrder INT,
Key1 INT NULL,
Key2 INT NULL,
Key3 INT NULL,
Quantity NUMERIC(22,9) NOT NULL,
Price NUMERIC(16,9) NOT NULL
);
INSERT INTO #InventoryItem (
IntentoryItemId,
InventoryOrder,
Key1,
Key2,
Key3,
Quantity,
Price
)
VALUES
(1, NULL, 1, 1, 1, 10, 1),
(2, NULL, 1, 1, 1, 10, 2),
(3, NULL, 1, 2, 1, 10, 2),
(4, NULL, 2, 2, 1, 10, 1),
(5, NULL, 1, 2, 1, 10, 5),
(6, NULL, 1, 1, 2, 10, 3),
(7, NULL, 1, 1, 1, 10, 3),
(8, NULL, 3, 3, 1, 10, 1);
--select * from #InventoryItem
--return
;with cte as
(
select *
, ROW_NUMBER() OVER (PARTITION BY Key1 ORDER BY IntentoryItemId) AS rn1
, ROW_NUMBER() OVER (PARTITION BY Key2 ORDER BY IntentoryItemId) AS rn2
, ROW_NUMBER() OVER (PARTITION BY Key3 ORDER BY IntentoryItemId) AS rn3
from #InventoryItem
)
,cte1 AS (
SELECT
IntentoryItemId,
Key1 keys,
Quantity,
Price
,rn1
,rn1 rn
,1 pk
FROM cte c
union ALL
SELECT
IntentoryItemId,
Key2 keys,
Quantity,
Price
,rn1
,rn2 rn
,2 pk
FROM cte c
union ALL
SELECT
IntentoryItemId,
Key3 keys,
Quantity,
Price
,rn1
,rn3 rn
,3 pk
FROM cte c
)
, cte2 AS (
SELECT
IntentoryItemId,
rn,
Keys,
Quantity,
Price,
CONVERT(NUMERIC(22,9), InventoryItem.Quantity) AS CurrentQuantity,
CONVERT(NUMERIC(22,9), (InventoryItem.Quantity * InventoryItem.Price)) a,
CONVERT(NUMERIC(22,9), InventoryItem.Price) b,
CONVERT(NUMERIC(22,9), (InventoryItem.Quantity * InventoryItem.Price) / NULLIF(InventoryItem.Quantity, 0)) AS AvgPrice
,pk
FROM cte1 InventoryItem
WHERE InventoryItem.rn = 1
UNION ALL
SELECT
Sub.IntentoryItemId,
sub.rn,
Sub.Keys,
Sub.Quantity,
Sub.Price,
CONVERT(NUMERIC(22,9), Main.CurrentQuantity + Sub.Quantity) AS CurrentQuantity,
CONVERT(NUMERIC(22,9),Main.CurrentQuantity * Main.AvgPrice),
CONVERT(NUMERIC(22,9),Sub.Quantity * Sub.price),
CONVERT(NUMERIC(22,9),
((Main.CurrentQuantity * Main.AvgPrice) + (Sub.Quantity * Sub.price))
/
NULLIF(((Main.CurrentQuantity) + Sub.Quantity), 0)
) AS AvgPrice
,sub.pk
FROM CTE2 Main
INNER JOIN cte1 Sub
ON Main.Keys = Sub.Keys and main.pk=sub.pk
AND Sub.rn = main.rn + 1
--and Sub.InventoryOrder<=2
)
select *
,(select AvgPrice from cte2 c1 where pk=2 and c1.IntentoryItemId=c.IntentoryItemId ) AvgPrice2
,(select AvgPrice from cte2 c1 where pk=2 and c1.IntentoryItemId=c.IntentoryItemId ) AvgPrice3
from cte2 c
where pk=1
ORDER BY pk,rn
Alternate Solution (for Sql 2012+) and many thanks to Jason,
SELECT *
,CONVERT(NUMERIC(22,9),avg((Quantity * Price) / NULLIF(Quantity, 0))
OVER(PARTITION BY Key1 ORDER by IntentoryItemId ROWS UNBOUNDED PRECEDING))AvgKey1Price
,CONVERT(NUMERIC(22,9),avg((Quantity * Price) / NULLIF(Quantity, 0))
OVER(PARTITION BY Key2 ORDER by IntentoryItemId ROWS UNBOUNDED PRECEDING))AvgKey2Price
,CONVERT(NUMERIC(22,9),avg((Quantity * Price) / NULLIF(Quantity, 0))
OVER(PARTITION BY Key3 ORDER by IntentoryItemId ROWS UNBOUNDED PRECEDING))AvgKey3Price
from #InventoryItem
order by IntentoryItemId
Here's how to do it in SQL Server 2012 & later...
IF OBJECT_ID('tempdb..#TestData', 'U') IS NOT NULL
DROP TABLE #TestData;
CREATE TABLE #TestData (
Id INT,
Key1 INT,
Key2 INT,
Key3 INT,
[Value] INT
);
INSERT #TestData(Id, Key1, Key2, Key3, Value) VALUES
(1, 1, 1, 1, 10),
(2, 1, 1, 1, 10),
(3, 1, 2, 1, 10),
(4, 2, 2, 1, 10),
(5, 1, 2, 1, 10),
(6, 1, 1, 2, 10),
(7, 1, 1, 1, 10),
(8, 3, 3, 1, 10);
--=============================================================
SELECT
td.Id, td.Key1, td.Key2, td.Key3, td.Value,
Key1Sum = SUM(td.[Value]) OVER (PARTITION BY td.Key1 ORDER BY td.Id ROWS UNBOUNDED PRECEDING),
Key2Sum = SUM(td.[Value]) OVER (PARTITION BY td.Key2 ORDER BY td.Id ROWS UNBOUNDED PRECEDING),
Key3Sum = SUM(td.[Value]) OVER (PARTITION BY td.Key3 ORDER BY td.Id ROWS UNBOUNDED PRECEDING)
FROM
#TestData td
ORDER BY
td.Id;
results...
Id Key1 Key2 Key3 Value Key1Sum Key2Sum Key3Sum
----------- ----------- ----------- ----------- ----------- ----------- ----------- -----------
1 1 1 1 10 10 10 10
2 1 1 1 10 20 20 20
3 1 2 1 10 30 10 30
4 2 2 1 10 10 20 40
5 1 2 1 10 40 30 50
6 1 1 2 10 50 30 10
7 1 1 1 10 60 40 60
8 3 3 1 10 10 10 70
I am new to SQL Server and have a question regarding summing over a calculated row with a conditional statement.
My data is organized as follows:
ID S_DATE END_DATE MNum CHG DateCHG
---------------------------------------------
1 1/26/2001 2/26/2001 7 NULL 1
1 2/27/2001 3/27/2001 8 1 1
1 3/28/2001 1/9/2003 9 1 21
1 1/10/2003 3/2/2004 11 2 14
1 3/3/2004 10/14/2004 10 -1 7
1 10/15/2004 6/22/2005 9 -1 8
1 6/23/2005 3/9/2008 8 -1 33
1 3/10/2008 1899-12-30 0 NULL -1299
2 9/23/1993 9/11/2000 3 NULL 84
2 1/1/1999 12/31/1998 3 0 -1
2 9/12/2000 11/13/2001 2 -1 14
2 11/14/2001 1899-12-30 0 NULL -1223
DateCHG is equal to the number of months between S_DATE & End_Date. I would like to find the SUM of CHG for each ID where the CHG occurs within 3 months of previous date.
Here is my current code (NOTE: Column headers are different from data above for formatting purposes. Also I cannot write to this database so only in Query format)
SELECT
*,
CASE
WHEN MratingNum = 0 OR
LAG(MratingNum) OVER (OVER BY MAST_ISSU_NUM, RATG_DATETIME) = 0 OR
MAST_ISSU_NUM <> LAG(MAST_ISSU_NUM) OVER (ORDER BY MAST_ISSU_NUM, RATG_DATETIME) --OR
--LAG(MratingNum) OVER (ORDER BY MAST_ISSU_NUM, RATG_DATETIME) < 12 OR --By Credit Rating
--LAG(MratingNum) OVER (ORDER BY MAST_ISSU_NUM, RATG_DATETIME) < 18
THEN NULL
ELSE CAST(MratingNum AS INT) - LAG(MratingNum) OVER (ORDER BY MAST_ISSU_NUM, RATG_DATETIME)
END AS CHG,
DATEDIFF(month, RATG_DATETIME, RATG_END_DATETIME) AS DateCHG
FROM
MOODYS_DRD.dbo.DEBT_RATG AS t1
LEFT JOIN
sandbox.dbo.RatingMap AS t2 ON t1.RATG_TXT = t2.MratingValue
WHERE
RATG_TYP_CD = 'LT'
ORDER BY
MAST_ISSU_NUM, RATG_DATETIME
So for example the output would look something like this:
ID S_DATE .... SumCHG
1 1/26/2001.... NULL
1 2/27/2001.... NULL
1 3/28/2001.... 2
1 1/10/2003.... NULL
1 3/3/2004 .... NULL
I'm assuming the best approach is to calculate a rolling sum of DateCHG where it is less than 3 and then SUM the CHG column? Thanks all!
EDIT: This is fairly complex so let me try another way of asking the question. For each record I want to look back and find the SUM of CHG within 3 months of the S_DATE. For 3/28/2001, this would include 2/01 and 1/01. The MNum went from 7 to 9 so the SUM of CHG would be 2. However from 3/04, there were no changes in the past 3 months so return NULL. I obviously want to do this per ID so don't want to overlap 3 months from ID 2 to 1. Hope this makes more sense now?
t0 and t are used for setting up the data.
with t0
as ( select *
from ( values ( 1, '1/26/2001', '2/26/2001', 7, null, 1),
( 1, '2/27/2001', '3/27/2001', 8, 1, 1),
( 1, '3/28/2001', '1/9/2003', 9, 1, 21),
( 1, '1/10/2003', '3/2/2004', 11, 2, 14),
( 1, '3/3/2004', '10/14/2004', 10, -1, 7),
( 1, '10/15/2004', '6/22/2005', 9, -1, 8),
( 1, '6/23/2005', '3/9/2008', 8, -1, 33),
( 1, '3/10/2008', '1899-12-30', 0, null, -1299),
( 2, '9/23/1993', '9/11/2000', 3, null, 84),
( 2, '1/1/1999', '12/31/1998', 3, 0, -1),
( 2, '9/12/2000', '11/13/2001', 2, -1, 14),
( 2, '11/14/2001', '1899-12-30', 0, null, -1223) ) t ( ID, S_DATE, END_DATE, MNum, CHG, DateCHG )
),
t as ( select t0.ID ,
cast(t0.S_DATE as date) S_DATE ,
cast(t0.END_DATE as date) END_DATE ,
t0.MNum ,
t0.CHG ,
t0.DateCHG
from t0
)
select case when Cnt >= 3 then p.CHG
end SumCHG,
*
from t
outer apply ( select sum(u.CHG) CHG ,
count(*) Cnt
from t u
where u.ID = t.ID
and u.S_DATE between dateadd(month, -3,
t.S_DATE)
and t.S_DATE
) p
order by t.ID ,
t.S_DATE;
Use CTE for your tables,
;with t as (
SELECT
*,
CASE
WHEN MratingNum = 0 OR
LAG(MratingNum) OVER (OVER BY MAST_ISSU_NUM, RATG_DATETIME) = 0 OR
MAST_ISSU_NUM <> LAG(MAST_ISSU_NUM) OVER (ORDER BY MAST_ISSU_NUM, RATG_DATETIME) --OR
--LAG(MratingNum) OVER (ORDER BY MAST_ISSU_NUM, RATG_DATETIME) < 12 OR --By Credit Rating
--LAG(MratingNum) OVER (ORDER BY MAST_ISSU_NUM, RATG_DATETIME) < 18
THEN NULL
ELSE CAST(MratingNum AS INT) - LAG(MratingNum) OVER (ORDER BY MAST_ISSU_NUM, RATG_DATETIME)
END AS CHG,
DATEDIFF(month, RATG_DATETIME, RATG_END_DATETIME) AS DateCHG
FROM
MOODYS_DRD.dbo.DEBT_RATG AS t1
LEFT JOIN
sandbox.dbo.RatingMap AS t2 ON t1.RATG_TXT = t2.MratingValue
WHERE
RATG_TYP_CD = 'LT'
)
select case when Cnt >= 3 then p.CHG
end SumCHG,
*
from t
outer apply ( select sum(u.CHG) CHG ,
count(*) Cnt
from t u
where u.ID = t.ID
and u.S_DATE between dateadd(month, -3,
t.S_DATE)
and t.S_DATE
) p
order by t.ID ,
t.S_DATE;
I have a table with three columns, ID, Date, Value. I want to rank the rows such that, within an ID, the Ranking goes up with each date where Value is at least X, otherwise, Ranking stays the same.
Given ID, Date, and Values like these
1, 6/1, 8
1, 6/2, 12
1, 6/3, 14
1, 6/4, 9
1, 6/5, 11
I would like to return a ranking based on values of at least 10, such that I would have ID, Date, Value, and Rank like this:
1, 6/1, 8, 0
1, 6/2, 12, 1
1, 6/3, 14, 2
1, 6/4, 9, 2
1, 6/5, 11, 3
In other words, the ranking increases each time the value exceeds a threshhold, otherwise it stays the same.
What I have tried is
SELECT T1.*, X.Ranking FROM TABLE T1
LEFT JOIN ( SELECT *, DENSE_RANK( ) OVER ( PARTITION BY T2.ID ORDER BY T2.DATE ) Ranking
FROM TABLE T2 WHERE T2.VALUE >= 10 ) X
ON T1.ID = T2.ID AND T1.Date = T2.Date
This almost works. It gets me output like
1, 6/1, 8, NULL
1, 6/2, 12, 1
1, 6/3, 14, 2
1, 6/4, 9, NULL
1, 6/5, 11, 3
Then, I want to turn the first NULL into a 0, and the second into a 2.
I turned the above query into a cte and tried
SELECT T1.*, CASE WHEN T1.Ranking IS NULL THEN ISNULL( (
SELECT MAX( T2.Ranking )
FROM cte T2 WHERE T1.ID = T2.ID AND T1.Date > T2.Date, 0 )
ELSE T1.Ranking END NewRanking
FROM cte T1
This looks like it would work, but my table has 200,000 rows and the query ran for 25 minutes... So, I'm looking for something a little more out of the box than the SELECT MAX.
You are using SQL Server 2012, so you can do a cumulative sum:
select t.*,
sum(case when value >= 10 then 1 else 0 end) over
(partition by id order by date) as ranking
from table t;
EDIT: This actually does not work. In spirit it fetches the previous LAG value and increment it, but this is not how LAG works... it would be 'recursive' in essence which results in a 'my_rank' is undefined syntax error. Better solution is the accepted answer based on a cumulative sum.
If you have SQL Server 2012 (you didn't tag your question), you can do something like:
SELECT
LAG(my_rank, 1, 0) OVER (ORDER BY DATE)
+ CASE WHEN VALUE >= 10 THEN 1 ELSE 0 END AS my_rank
FROM T1