CCP months QUART YEARS GTS
---- ------ ----- ----- ---
CCP1 1 1 2015 5
CCP1 2 1 2015 6
CCP1 3 1 2015
Another alternative is using stacked/cascaded CTEs:
;WITH CteFirstQtr AS(
SELECT g.*,
result = b.BASELINE * g.GTS
FROM gts g
INNER JOIN BASELINE b
ON b.CCP = g.CCP
AND b.YEARS = g.YEARS
AND b.QUART = g.QUART
),
CteSecondQtr AS(
SELECT g.*,
result = t.result * g.gts
FROM gts g
CROSS APPLY(
SELECT SUM(result)
FROM CteFirstQtr
WHERE
CCP = g.CCP
AND Years = g.Years
AND QUART = 1
)t(result)
WHERE g.QUART = 2
),
CteThirdQtr AS(
SELECT g.*,
result = t.result * g.gts
FROM gts g
CROSS APPLY(
SELECT SUM(result)
FROM CteSecondQtr
WHERE
CCP = g.CCP
AND Years = g.Years
AND QUART = 2
)t(result)
WHERE g.QUART = 3
),
CteFourthQtr AS(
SELECT g.*,
result = t.result * g.gts
FROM gts g
CROSS APPLY(
SELECT SUM(result)
FROM CteThirdQtr
WHERE
CCP = g.CCP
AND Years = g.Years
AND QUART = 3
)t(result)
WHERE g.QUART = 4
)
SELECT * FROM CteFirstQtr UNION ALL
SELECT * FROM CteSecondQtr UNION ALL
SELECT * FROM CteThirdQtr UNION ALL
SELECT * FROM CteFourthQtr
No recursion, no loops.
DECLARE @BASELINE TABLE(CCP char(4), BASELINE numeric(22,6), YEARS int ,QUART int)
DECLARE @gts TABLE (CCP char(4),months int,QUART int,YEARS int,GTS numeric(22,6))
insert into @baseline
SELECT 'CCP1' AS CCP,5 AS BASELINE, 2015 AS YEARS,1 AS QUART
insert into @gts (CCP,months,QUART,YEARS,GTS)
SELECT 'CCP1' AS CCP,1 AS months,1 AS QUART ,2015 AS YEARS, 5 AS GTS UNION
SELECT 'CCP1' AS CCP,2 AS months,1 AS QUART ,2015 AS YEARS, 6 AS GTS UNION
SELECT 'CCP1' AS CCP,3 AS months,1 AS QUART ,2015 AS YEARS, 7 AS GTS UNION
SELECT 'CCP1' AS CCP,4 AS months,2 AS QUART ,2015 AS YEARS, 4 AS GTS UNION
SELECT 'CCP1' AS CCP,5 AS months,2 AS QUART ,2015 AS YEARS, 2 AS GTS UNION
SELECT 'CCP1' AS CCP,6 AS months,2 AS QUART ,2015 AS YEARS, 2 AS GTS UNION
SELECT 'CCP1' AS CCP,7 AS months,3 AS QUART ,2015 AS YEARS, 3 AS GTS UNION
SELECT 'CCP1' AS CCP,8 AS months,3 AS QUART ,2015 AS YEARS, 2 AS GTS UNION
SELECT 'CCP1' AS CCP,9 AS months,3 AS QUART ,2015 AS YEARS, 1 AS GTS UNION
SELECT 'CCP1' AS CCP,10 AS months,4 AS QUART ,2015 AS YEARS, 2 AS GTS UNION
SELECT 'CCP1' AS CCP,11 AS months,4 AS QUART ,2015 AS YEARS, 3 AS GTS UNION
SELECT 'CCP1' AS CCP,12 AS months,4 AS QUART ,2015 AS YEARS, 4 AS GTS
DECLARE @Summary TABLE (CCP char(4),[DATE] DATE,SumGTS numeric(22,6),AllPriorGTS numeric(22,6),[Multiplier] numeric(22,6))
INSERT INTO @Summary(CCP,[DATE],SumGTS)
SELECT
CCP,DATEADD(MONTH,(QUART-1)*3,DATEADD(YEAR,YEARS-2000,'01/01/2000')) [DATE]
, SUM(GTS) [SumGTS]
FROM @gts
GROUP BY CCP,DATEADD(MONTH,(QUART-1)*3,DATEADD(YEAR,YEARS-2000,'01/01/2000'))
UPDATE s
SET AllPriorGTS=
(
SELECT EXP(SUM(LOG(s1.SumGTS)))
FROM @Summary s1
WHERE s1.[Date]<s.[DATE]
)
from @Summary s
UPDATE s
SET [Multiplier]=SumGTS*ISNULL(AllPriorGTS,1) * bl.BASELINE
from @Summary s
INNER JOIN @baseline bl ON bl.CCP = s.CCP
SELECT
g.*,g.GTS*ISNULL(s.[Multiplier],bl.BASELINE) [RESULTS]
FROM @gts g
LEFT JOIN @Summary s ON DATEADD(MONTH,3,s.DATE)=DATEADD(MONTH,(g.QUART-1)*3,DATEADD(YEAR,g.YEARS-2000,'01/01/2000'))
LEFT JOIN @baseline bl ON bl.CCP = g.CCP
Without a CTE. I'd suggest possible working with the months and years in an actual date format. I might become a bit easier to manage the transition of the Years 2015 vs 2016 becomes a simple DATEADD(MM,-1,dateCol) as opposed to figuring out when to subtract or add a year. I haven't implemented in this solution, but could help if you needed it. It could lead to a pretty effective indexing strategy to speed the query up if you have a large data set. I believer it would also be possible to implement this with LAG as well.
create table #tmp_BASELINE (
CCP char(4),
BASELINE numeric(22,6),
YEARS int ,
QUART int)
create table #tmp_gts (
CCP char(4),
months int,
QUART int,
YEARS int,
GTS numeric(22,6)
)
insert into #tmp_BASELINE
SELECT 'CCP1' AS CCP,10 AS BASELINE, 2015 AS YEARS,1 AS QUART
insert into #tmp_gts
SELECT 'CCP1' AS CCP,1 AS months,1 AS QUART ,2015 AS YEARS, 50 AS GTS UNION
SELECT 'CCP1' AS CCP,2 AS months,1 AS QUART ,2015 AS YEARS, 52 AS GTS UNION
SELECT 'CCP1' AS CCP,3 AS months,1 AS QUART ,2015 AS YEARS, 57 AS GTS UNION
SELECT 'CCP1' AS CCP,4 AS months,2 AS QUART ,2015 AS YEARS, 59 AS GTS UNION
SELECT 'CCP1' AS CCP,5 AS months,2 AS QUART ,2015 AS YEARS, 61 AS GTS UNION
SELECT 'CCP1' AS CCP,6 AS months,2 AS QUART ,2015 AS YEARS, 65 AS GTS UNION
SELECT 'CCP1' AS CCP,7 AS months,3 AS QUART ,2015 AS YEARS, 69 AS GTS UNION
SELECT 'CCP1' AS CCP,8 AS months,3 AS QUART ,2015 AS YEARS, 73 AS GTS UNION
SELECT 'CCP1' AS CCP,9 AS months,3 AS QUART ,2015 AS YEARS, 78 AS GTS UNION
SELECT 'CCP1' AS CCP,10 AS months,4 AS QUART ,2015 AS YEARS, 84 AS GTS UNION
SELECT 'CCP1' AS CCP,11 AS months,4 AS QUART ,2015 AS YEARS, 90 AS GTS UNION
SELECT 'CCP1' AS CCP,12 AS months,4 AS QUART ,2015 AS YEARS, 95 AS GTS
SELECT * FROM #tmp_BASELINE
SELECT CCP,
Months,
QUART,
YEARS,
GTS,
SUM(GTS) OVER (PARTITION BY QUART) as QTRGTS,
COALESCE((SELECT DISTINCT SUM(PGT.GTS) OVER (PARTITION BY QUART) FROM #tmp_gts as PGT WHERE GTS.YEARS = PGT.YEARS AND PGT.QUART = GTS.QUART-1),(SELECT TOP 1 BaseLine FROM #tmp_BASELINE)) as Modifier,
GTS * COALESCE((SELECT DISTINCT SUM(PGT.GTS) OVER (PARTITION BY QUART) FROM #tmp_gts as PGT WHERE GTS.YEARS = PGT.YEARS AND PGT.QUART = GTS.QUART-1),(SELECT TOP 1 BaseLine FROM #tmp_BASELINE)) as GTSxModifier
FROM #tmp_gts as GTS
Im not sure if the question is how do the over()
logic or perform a recursive cte in 2012 or just calculate in 2012 without recursive cte.
Looks like you were trying to create the PRODUCT()
agreggation equivalent. But as I put in my comment
The function 'Exp' is not a valid windowing function, and cannot be used with the OVER clause.
So I did my version without recursive cte for my answer. I include one aditional year on the sample data with different baseline
gts
for each Quart
Quart
Quart
doing the dummy PRODUCT()
aggregatedSQL Fiddle Demo
WITH gtsTotal as (
SELECT [CCP], [Year], [QUART], SUM([GTS]) as sumGts
FROM gts
GROUP BY [CCP], [Year], [QUART]
),
newBase as (
SELECT g.[CCP], g.[YEAR], b.[BASELINE], 1 as sQuart, b.[BASELINE] as [TotalBase]
FROM gtsTotal g
INNER JOIN baseline b
on g.[Year] = b.[YEARS]
and g.[CCP] = b.[CCP]
WHERE g.[QUART] <= 1
UNION ALL
SELECT g.[CCP], g.[YEAR], b.[BASELINE], MAX(g.[QUART]) + 1 as sQuart, (Exp(Sum(Log(sumGts))) * b.[BASELINE]) as [TotalBase]
FROM gtsTotal g
INNER JOIN baseline b
on g.[Year] = b.[YEARS]
and g.[CCP] = b.[CCP]
WHERE g.[QUART] <= 1
GROUP BY g.[CCP], g.[YEAR], b.[BASELINE]
UNION ALL
SELECT g.[CCP], g.[YEAR], b.[BASELINE], MAX(g.[QUART]) + 1 as sQuart, (Exp(Sum(Log(sumGts))) * b.[BASELINE]) as [TotalBase]
FROM gtsTotal g
INNER JOIN baseline b
on g.[Year] = b.[YEARS]
and g.[CCP] = b.[CCP]
WHERE g.[QUART] <= 2
GROUP BY g.[CCP], g.[YEAR], b.[BASELINE]
UNION ALL
SELECT g.[CCP], g.[YEAR], b.[BASELINE], MAX(g.[QUART]) + 1 as sQuart, (Exp(Sum(Log(sumGts))) * b.[BASELINE]) as [TotalBase]
FROM gtsTotal g
INNER JOIN baseline b
on g.[Year] = b.[YEARS]
and g.[CCP] = b.[CCP]
WHERE g.[QUART] <= 3
GROUP BY g.[CCP], g.[YEAR], b.[BASELINE]
)
SELECT g.CCP, g.months, g.QUART, g.Year, CEILING(g.GTS * n.TotalBase)
FROM newBase n
INNER JOIN gts g
ON n.CCP = g.CCP
AND n.[Year] = g.[Year]
AND n.[sQuart] = g.[QUART]
order by g.[Year], n.sQuart
Output
| CCP | months | QUART | Year | Result|
|------|--------|-------|------|-------|
| CCP1 | 1 | 1 | 2015 | 25 |
| CCP1 | 2 | 1 | 2015 | 30 |
| CCP1 | 3 | 1 | 2015 | 35 |
| CCP1 | 4 | 2 | 2015 | 360 |
| CCP1 | 5 | 2 | 2015 | 180 |
| CCP1 | 6 | 2 | 2015 | 180 |
| CCP1 | 7 | 3 | 2015 | 2160 |
| CCP1 | 8 | 3 | 2015 | 1440 |
| CCP1 | 9 | 3 | 2015 | 720 |
| CCP1 | 10 | 4 | 2015 | 8640 |
| CCP1 | 11 | 4 | 2015 | 12960 |
| CCP1 | 12 | 4 | 2015 | 17280 |
Another method that uses the EXP(SUM(LOG()))
trick and only window functions for the running total (no recursive CTEs or cursors).
Tested at dbfiddle.uk:
WITH
ct AS
( SELECT
ccp, years, quart,
q2 = round(exp(coalesce(sum(log(sum(gts)))
OVER (PARTITION BY ccp
ORDER BY years, quart
ROWS BETWEEN UNBOUNDED PRECEDING
AND 1 PRECEDING)
, 0))
, 2) -- round appropriately to your requirements
FROM gts
GROUP BY ccp, years, quart
)
SELECT
g.*,
result = g.gts * b.baseline * ct.q2,
baseline = b.baseline * ct.q2
FROM ct
JOIN gts AS g
ON ct.ccp = g.ccp
AND ct.years = g.years
AND ct.quart = g.quart
CROSS APPLY
( SELECT TOP (1) b.baseline
FROM baseline AS b
WHERE b.ccp = ct.ccp
ORDER BY b.years, b.quart
) AS b
;
How it works:
(CREATE
tables and INSERT
skipped)
1, lets group by ccp, year and quart and calculate the sums:
select ccp, years, quart, q1 = sum(gts) from gts group by ccp, years, quart ; GO
ccp | years | quart | q1 :--- | ----: | ----: | :-------- CCP1 | 2015 | 1 | 18.000000 CCP1 | 2015 | 2 | 8.000000 CCP1 | 2015 | 3 | 6.000000 CCP1 | 2015 | 4 | 9.000000 CCP1 | 2016 | 1 | 12.000000
EXP(LOG(SUM())
trick to calculate the running multiplications of these sums. We use BETWEEEN .. AND -1 PRECEDING
in the window to skip the current values, as these values are only used for the baselines of the next quart.LOG()
and EXP()
. You can experiment with using either ROUND()
or casting to NUMERIC
:with ct as ( select ccp, years, quart, q1 = sum(gts) from gts group by ccp, years, quart ) select ccp, years, quart, -- months, gts, q1, q2 = round(exp(coalesce(sum(log(q1)) OVER (PARTITION BY ccp ORDER BY Years, Quart ROWS BETWEEN UNBOUNDED PRECEDING AND 1 PRECEDING),0)),2) from ct ; GO
ccp | years | quart | q2 :--- | ----: | ----: | ---: CCP1 | 2015 | 1 | 1 CCP1 | 2015 | 2 | 18 CCP1 | 2015 | 3 | 144 CCP1 | 2015 | 4 | 864 CCP1 | 2016 | 1 | 7776
gts
so we can multiply each value with the calculated q2
(which gives us the baseline).CROSS APPLY
is merely to get the base baseline for each ccp.numeric(22,6)
instead of rounding to 2 decimal places. The results are the same with the sample but they may differ if the numbers are bigger or not integer:with ct as ( select ccp, years, quart, q2 = cast(exp(coalesce(sum(log(sum(gts))) OVER (PARTITION BY ccp ORDER BY years, quart ROWS BETWEEN UNBOUNDED PRECEDING AND 1 PRECEDING) , 0.0)) as numeric(22,6)) -- round appropriately to your requirements from gts group by ccp, years, quart ) select g.*, result = g.gts * b.baseline * ct.q2, baseline = b.baseline * ct.q2 from ct join gts as g on ct.ccp = g.ccp and ct.years = g.years and ct.quart = g.quart cross apply ( select top (1) baseline from baseline as b where b.ccp = ct.ccp order by years, quart ) as b ; GO
CCP | months | QUART | YEARS | GTS | result | baseline :--- | -----: | ----: | ----: | :------- | :------------ | :----------- CCP1 | 1 | 1 | 2015 | 5.000000 | 25.000000 | 5.000000 CCP1 | 2 | 1 | 2015 | 6.000000 | 30.000000 | 5.000000 CCP1 | 3 | 1 | 2015 | 7.000000 | 35.000000 | 5.000000 CCP1 | 4 | 2 | 2015 | 4.000000 | 360.000000 | 90.000000 CCP1 | 5 | 2 | 2015 | 2.000000 | 180.000000 | 90.000000 CCP1 | 6 | 2 | 2015 | 2.000000 | 180.000000 | 90.000000 CCP1 | 7 | 3 | 2015 | 3.000000 | 2160.000000 | 720.000000 CCP1 | 8 | 3 | 2015 | 2.000000 | 1440.000000 | 720.000000 CCP1 | 9 | 3 | 2015 | 1.000000 | 720.000000 | 720.000000 CCP1 | 10 | 4 | 2015 | 2.000000 | 8640.000000 | 4320.000000 CCP1 | 11 | 4 | 2015 | 3.000000 | 12960.000000 | 4320.000000 CCP1 | 12 | 4 | 2015 | 4.000000 | 17280.000000 | 4320.000000 CCP1 | 1 | 1 | 2016 | 8.000000 | 311040.000000 | 38880.000000 CCP1 | 2 | 1 | 2016 | 1.000000 | 38880.000000 | 38880.000000 CCP1 | 3 | 1 | 2016 | 3.000000 | 116640.000000 | 38880.000000
Following solution assumes there are always 3 rows per quarter (only the last quarter might be partial), single SELECT, no recursion :-)
WITH sumQuart AS
(
SELECT *,
CASE
WHEN ROW_NUMBER() -- for the 1st month in a quarter
OVER (PARTITION BY CCP, Years, Quart
ORDER BY months) = 1
-- return the sum of all GTS of this quarter
THEN SUM(GTS) OVER (PARTITION BY CCP, Years, Quart)
ELSE NULL -- other months
END AS sumGTS
FROM gts
)
,cte AS
(
SELECT
sq.*,
COALESCE(b.Baseline, -- 1st quarter
-- product of all previous quarters
CASE
WHEN MIN(ABS(sumGTS)) -- any zeros?
OVER (PARTITION BY sq.CCP ORDER BY sq.Years, sq.Quart, sq.Months
ROWS BETWEEN UNBOUNDED PRECEDING AND 3 PRECEDING) = 0
THEN 0
ELSE -- product
EXP(SUM(LOG(NULLIF(ABS(COALESCE(b.Baseline,1) * sumGTS),0)))
OVER (PARTITION BY sq.CCP ORDER BY sq.Years, sq.Quart, sq.Months
ROWS BETWEEN UNBOUNDED PRECEDING AND 3 PRECEDING)) -- product
-- odd number of negative values -> negative result
* CASE WHEN COUNT(CASE WHEN sumGTS < 0 THEN 1 END)
OVER (PARTITION BY sq.CCP ORDER BY sq.Years, sq.Quart, sq.Months
ROWS BETWEEN UNBOUNDED PRECEDING AND 3 PRECEDING) % 2 = 0 THEN 1 ELSE -1 END
END) AS newBaseline
FROM sumQuart AS sq
LEFT JOIN BASELINE AS b
ON B.CCP = sq.CCP
AND b.Quart = sq.Quart
AND b.Years = sq.Years
)
SELECT
CCP, months, Quart, Years, GTS,
round(newBaseline * GTS,2),
round(newBaseline,2)
FROM cte
See Fiddle
EDIT: Added logic to handle values <= 0 Fiddle