(First question on StackOverflow, New on SQL with MSAccess. Please advise if I am missing anything or wrong format.)
I have two tables [Summary] and [Detail] with the layout as follow:
[Summary]
Driver ID
DateOfOperation
SalaryMonth
24
1/21/2023
2/1/2023
24
1/23/2023
2/1/2023
30
1/21/2023
2/1/2023
30
1/23/2023
2/1/2023
...Record Total:18734
[Detail]
Driver ID
DateOfOperation
WorkOrder
Points
SalaryMonth
24
1/21/2023
1
400
2/1/2023
24
1/21/2023
2
118
2/1/2023
24
1/21/2023
3
118
2/1/2023
24
1/21/2023
4
118
2/1/2023
30
1/21/2023
1
462
2/1/2023
30
1/21/2023
2
1264
2/1/2023
30
1/23/2023
1
924
2/1/2023
30
1/23/2023
2
1264
2/1/2023
24
1/21/2023
1
260
2/1/2023
24
1/21/2023
2
354
2/1/2023
24
1/21/2023
3
236
2/1/2023
24
1/21/2023
4
260
2/1/2023
24
1/21/2023
5
236
2/1/2023
24
1/21/2023
6
236
2/1/2023
24
1/21/2023
7
236
2/1/2023
24
1/21/2023
8
236
2/1/2023
24
1/21/2023
9
236
2/1/2023
...Record Total: 52838
I attempted to
count the total days in a period (eg.month) a driver work; &
Calculate total points of a driver got in a period.
Average points of a driver got in a period.
I ran the query with the SQL as follow. The query ran unusually long and the numbers on CountDateOfOperation and Month_points turned haywired like 1003922 days in a month.
SELECT Summary.[Driver ID], Count(Summary.DateOfOperation) AS CountDateOfOperation, Sum([Points]) AS Month_Points
FROM Summary, Detail
WHERE (((Summary.DateOfOperation) Between [Begin Date?] And [end date?]))
GROUP BY Summary.[Driver ID];
Expected result:
[Begin Date?] - 12/21/2022
[end date?] - 1/20/2023
Driver ID
CountDateOfOperation
Month_Points
SalaryMonth
24
19
18794
1/1/2023
30
25
26548
1/1/2023
...Record Total: 39
Actually result:
[Begin Date?] - 12/21/2022
[end date?] - 1/20/2023
Driver ID
CountDateOfOperation
Month_Points
SalaryMonth
24
1003922
293134356
1/1/2023
30
1320950
385703100
1/1/2023
...Record Total: 39
May anyone tell me what's wrong with the SQL and how to resolve this issue?
#################################
Thank you for your prompt reply (which scared me a bit...)
I used Access to link up the tables and the SQL turned out likes below:
SELECT Summary.[Driver ID], Count(Summary.DateOfOperation) AS CountDateOfOperation, Sum([Points]) AS Month_Points, Summary.SalaryMonth
FROM Drivers INNER JOIN (Summary INNER JOIN Detail ON (Summary.SalaryMonth = Detail.Salary_month) AND (Summary.DateOfOperation = Detail.[Date of Operation]) AND (Summary.[Driver ID] = Detail.[Driver ID])) ON (Drivers.[Driver ID] = Summary.[Driver ID]) AND (Drivers.[Driver ID] = Detail.[Driver ID])
WHERE (((Summary.DateOfOperation) Between [Begin Date?] And [end date?]))
GROUP BY Summary.[Driver ID], Summary.SalaryMonth;
The outcome is making a lot more sense, but still not accurate...
Actually result:
[Begin Date?] - 12/21/2022
[end date?] - 1/20/2023
Driver ID
CountDateOfOperation
Month_Points
SalaryMonth
24
80
18794
1/1/2023
30
50
26548
1/1/2023
...Record Total: 39
Just found that CountDateOfOperation is now counting Detail.WorkOrder instead of Summary.DateOfOperation.
Does anyone know what went wrong?
Thank you all.
I'm not sure why you need the Summary table. There's nothing in it that is not in Detail.
Test Data Fiddle
CREATE TABLE #detail ( Driver_ID INT
, DateOfOperation DATE
, WorkOrder INT
, Points INT
, SalaryMonth DATE);
INSERT INTO #detail
VALUES
( 24, '1/21/2023', 1, 400, '2/1/2023')
,( 24, '1/21/2023', 2, 118, '2/1/2023')
,( 24, '1/21/2023', 3, 118, '2/1/2023')
,( 24, '1/21/2023', 4, 118, '2/1/2023')
,( 30, '1/21/2023', 1, 462, '2/1/2023')
,( 30, '1/21/2023', 2, 1264, '2/1/2023')
,( 30, '1/23/2023', 1, 924, '2/1/2023')
,( 30, '1/23/2023', 2, 1264, '2/1/2023')
,( 24, '1/21/2023', 1, 260, '2/1/2023')
,( 24, '1/21/2023', 2, 354, '2/1/2023')
,( 24, '1/21/2023', 3, 236, '2/1/2023')
,( 24, '1/21/2023', 4, 260, '2/1/2023')
,( 24, '1/21/2023', 5, 236, '2/1/2023')
,( 24, '1/21/2023', 6, 236, '2/1/2023')
,( 24, '1/21/2023', 7, 236, '2/1/2023')
,( 24, '1/21/2023', 8, 236, '2/1/2023')
,( 24, '1/21/2023', 9, 236, '2/1/2023');
Query - I wrote this in SQL Server but I believe it should work in Access.
DECLARE #BeginDate DATE = '2023-01-01'
, #EndDate DATE = '2023-02-01'
SELECT Driver_ID
, COUNT(Distinct DateOfOperation) DaysWorked
, SUM(points) TotalPointsEarned
, SUM(Points)/COUNT(DISTINCT DateOfOperation) PointsPerWorkDay
, SalaryMonth
FROM #detail
WHERE DateOfOperation BETWEEN #BeginDate AND #EndDate
GROUP BY Driver_ID, SalaryMonth
Returns:
Driver_ID
DaysWorked
TotalPointsEarned
AvgPointsPerWorkDay
SalaryMonth
24
1
3044
3044
2023-02-01
30
2
3914
1957
2023-02-01
Related
I want last column 'Output' as stated in table. here the logic is :
for row 1 : sum of LBKUM & weekly_qty
for row 2 : sum of output of row 1 & weekly_qty of row 2
for row 3 : sum of output of row 2 & weekly_qty of row 3
for row 4 : sum of output of row 3 & weekly_qty of row 4
and so on.
Unlike other running total, I want LBKUM only once (in first row calculation) and in the next steps, output of previous row should be considered as shown in the description.
I hope this much would be sufficient.
Thanks in advance.
LOGSYS
MATNR
PLANT
LBKUM
QTY_WITHDRAWL
PLUS_QTY
WEEK
WEEKLY_QTY
Output
SAP
123456789
1234
1408
387
484
06
97
1505
SAP
123456789
1234
1408
1238
2080
07
842
2347
SAP
123456789
1234
1408
1826
1600
08
-226
2121
SAP
123456789
1234
1408
1786
1920
09
134
2255
SAP
123456789
1234
1408
1445
1120
10
-325
1930
SAP
123456789
1234
1408
1224
800
11
-424
1506
SAP
123456789
1234
1408
1299
1280
12
-19
1487
You want something like:
SELECT week,
FIRST_VALUE(lbkum) OVER (ORDER BY week)
+ SUM(weekly_qty) OVER (ORDER BY week) AS running_total
FROM table_name
Which, for the sample data:
CREATE TABLE table_name (LOGSYS, MATNR, PLANT, LBKUM, QTY_WITHDRAWL, PLUS_QTY, WEEK, WEEKLY_QTY, Output) AS
SELECT 'SAP', 123456789, 1234, 1408, 387, 484, 06, 97, 1505 FROM DUAL UNION ALL
SELECT 'SAP', 123456789, 1234, 1408, 1238, 2080, 07, 842, 2347 FROM DUAL UNION ALL
SELECT 'SAP', 123456789, 1234, 1408, 1826, 1600, 08, -226, 2121 FROM DUAL UNION ALL
SELECT 'SAP', 123456789, 1234, 1408, 1786, 1920, 09, 134, 2255 FROM DUAL UNION ALL
SELECT 'SAP', 123456789, 1234, 1408, 1445, 1120, 10, -325, 1930 FROM DUAL UNION ALL
SELECT 'SAP', 123456789, 1234, 1408, 1224, 800, 11, -424, 1506 FROM DUAL UNION ALL
SELECT 'SAP', 123456789, 1234, 1408, 1299, 1280, 12, -19, 1487 FROM DUAL;
Outputs:
WEEK
RUNNING_TOTAL
6
1505
7
2347
8
2121
9
2255
10
1930
11
1506
12
1487
fiddle
I am trying to find the average of last 5 days by day and product. Given below is how my Dataframe looks:
df=pd.DataFrame({
'day':['day_1','day_2','day_3','day_4','day_5','day_2','day_3','day_4','day_5','day_6','day_1'],
'product':['prod_a','prod_a','prod_a','prod_a','prod_a','prod_b','prod_b','prod_b','prod_b','prod_b','prod_b'],
'sale':[10,15,4,17,12,1,50,70,30,70,10]
})
To find the last 5 day average by day by product I did the below:
df_average = df.groupby(['day', 'product']).tail(5).groupby(['day', 'product']).mean()
Doing the above only returns back the actual value for that day for that product for that day and does not take the last 5 day average.
Expected output:
day, product, sale, last_5_average
day_1, prod_a , 10, 11.6
day_2, prod_a , 15, 12
day_3, prod_a , 4, 11
day_4, prod_a , 17, 14.5
day_5, prod_a , 12, 12
day_1, prod_b , 1, 44.2
day_2, prod_b , 50, 54
day_3, prod_b , 70, 55
day_4, prod_b , 30, 50
day_5, prod_b , 70, 60
day_6, prod_c , 50, 50
I hope this helps!
#original data frame
df=pd.DataFrame({
'day':['day_1','day_2','day_3','day_4','day_5','day_2','day_3','day_4','day_5','day_6','day_1'],
'product':['prod_a','prod_a','prod_a','prod_a','prod_a','prod_b','prod_b','prod_b','prod_b','prod_b','prod_b'],
'sale':[10,15,4,17,12,1,50,70,30,70,10]
})
#sort by product and day
df=df.sort_values(by=['product','day'])
#drop the sorted index
df=df.reset_index(drop=True)
#take rolling past 5 record's mean by product group
df['rolling_mean_sale']=df.groupby('product')['sale'].rolling(5).mean().reset_index()['sale']
I am getting an error on running this below query. How should I handle it?
update a
set pidate = case
when PromisedYear is not null
then dateadd(week, cast(PromisedWeek as int), dateadd(year, PromisedYear - 1900, 0))
else '2020-12-31'
end
FROM #CuringATPDualOutput1 a
Error:
Adding a value to a 'datetime' column caused an overflow.
When Promisedweek is less than 59, like 44 or 45, the query is working fine.
Only giving an error when it is 59.
Data:
44 2017
44 2017
44 2017
44 2017
44 2017
45 2017
45 2017
45 2017
45 2017
45 2017
46 2017
45 2017
45 2017
45 2017
45 2017
59 NULL
46 2017
59 NULL
45 2017
45 2017
46 2017
46 2017
46 2017
47 2017
47 2017
47 2017
48 2017
48 2017
48 2017
49 2017
49 2017
49 2017
50 2017
50 2017
50 2017
51 2017
51 2017
Here is your sample data and the query you posted. I even added some more rows of sample data that are greater than 59 and they work just fine. I suspect you have something else going on.
declare #Output table
(
PromisedWeek int
, PromisedYear int
, pidate datetime
)
insert #Output values
(44, 2017, null)
, (45, 2017, null)
, (46, 2017, null)
, (47, 2017, null)
, (48, 2017, null)
, (49, 2017, null)
, (50, 2017, null)
, (51, 2017, null)
, (59, NULL, null)
, (59, 2017, null)
, (379, 2017, null)
update #Output
set pidate = case
when PromisedYear is not null
then dateadd(week, cast(PromisedWeek as int), dateadd(year, PromisedYear - 1900, 0))
else '2020-12-31'
end
select * from #Output
In fact even your original code works with this data and will not throw an exception.
select dateadd(week, cast(PromisedWeek as int), dateadd(year, PromisedYear - 1900, 0))
from #Output
where PromisedWeek = 59
I am trying to develop a query to determine the amount of a drug that an individual has had for every day during a quarter. On some days, there are no drugs prescribed, for others, there may be overlap and I need a total amount (meaning, strength for each summed for a day). The number of drugs, strengths, daysupply etc. can vary. Here's some data:
create table #MemberInfo
(ProgramName varchar(255),
DateFilled datetime,
DaySupply integer,
MemberID varchar(255),
Strength integer,
Tradename varchar(255));
insert into #MemberInfo
Values ('InsureCo', '20130612', 30, 'MEM001', 10, 'Sedative')
, ('InsureCo', '20130429', 30, 'MEM001', 20, 'Sedative')
, ('InsureCo', '20130401', 30, 'MEM001', 20, 'Sedative')
, ('InsureCo', '20130529', 30, 'MEM001', 30, 'Sedative')
I really have no idea what the best approach might be to add up the amount of drugs taken on a given day during a quarter. I'd like to avoid using cursors if I can. I was thinking about creating a temp table with all the days for a quarter and then somehow joining those dates to every day a drug is taken (i.e., DateFilled + every subsequent day up to DaySupply). Once I get to the point where I have the dates and amounts for every drug in a quarter, I could group by day and get a sum of strength for each day. I also need to be able to get the average amount taken over a quarter.
Additional Requirements:
I have a start date and a number of days. I'd like to create a row
for each member for every day they have a prescription (and do the
same for all of their prescriptions). I would then sum the strength
of all the drugs for each day. If it helps any, all of the drugs
will be of the same class, and strength is going to be equivalent
doses, meaning that I can sum them up.
For reporting, I need to be able to count consecutive days that the
amount is greater than some cutoff (let's say 100). That's why I'm
trying to get amount per day.
Desired output
MemberID Date SumStrength
MEM001 2013-04-29 40
MEM001 2013-04-30 40
MEM001 2013-05-01 20
ETC FOR EVERY DAY FOR THIS MEMBER
MEM002 2013-04-01 60
MEM002 2013-04-02 40
ETC FOR EVERY DAY FOR THIS MEMBER
Just a simple group by I think.
create table #MemberInfo
(ProgramName varchar(255),
DateFilled datetime,
DaySupply integer,
MemberID varchar(255),
Strength integer,
Tradename varchar(255));
insert into #MemberInfo
Values ('InsureCo', '20130612', 30, 'MEM001', 10, 'Sedative')
, ('InsureCo', '20130429', 30, 'MEM001', 20, 'Sedative')
, ('InsureCo', '20130429', 30, 'MEM002', 25, 'Sedative')
, ('InsureCo', '20130515', 30, 'MEM002', 25, 'Sedative')
, ('InsureCo', '20130401', 30, 'MEM001', 20, 'Sedative')
, ('InsureCo', '20130529', 30, 'MEM001', 30, 'Sedative')
, ('InsureCo', '20130529', 30, 'MEM003', 35, 'Sedative')
, ('InsureCo', '20130529', 30, 'MEM003', 45, 'Sedative')
select memberid,datefilled,SUM(strength) as [Strength sum]
from #MemberInfo
where memberid = 'MEM003' -- or whatever, could be a parameter
group by memberid,DateFilled
order by Memberid,DateFilled
drop table #MemberInfo
Here is an example of how it can be done building a Calendar using a CTE and doing the aggregates using OVER(PARTION BY)
Query:
-- Declare a Start and End Date required to build a calendar
DECLARE #StartDate DATETIME = '2013-01-01'
DECLARE #EndDate DATETIME = '2015-01-01'
-- Build out a Day/Quarter Calendar
;WITH Calendar ([Date], [Quarter]) AS (
SELECT #StartDate, 1
UNION ALL
SELECT [Date] + 1, (DATEDIFF(m, #StartDate, [Date] + 1) / 3) + 1
FROM Calendar
WHERE [Date] + 1 < #EndDate
)
-- Build Result Set
SELECT ProgramName,
DateFilled,
DaySupply,
MemberID,
Strength,
Quarter,
SUM(Strength) OVER(PARTITION BY ProgramName, DaySupply, MemberID, Quarter) AS QuarterlyTotal,
AVG(Strength) OVER(PARTITION BY ProgramName, DaySupply, MemberID, Quarter) AS QuarterlyAverage
FROM #MemberInfo MI
JOIN Calendar C ON MI.DateFilled = C.[Date]
ORDER BY MemberID, DateFilled
OPTION (MAXRECURSION 0)
Test Data:
create table #MemberInfo
(ProgramName varchar(255),
DateFilled datetime,
DaySupply integer,
MemberID varchar(255),
Strength integer,
Tradename varchar(255));
INSERT INTO #MemberInfo
Values
--MEM001
--Q1
('InsureCo', '20130112', 30, 'MEM001', 10, 'Sedative')
,('InsureCo', '20130129', 30, 'MEM001', 20, 'Sedative')
,('InsureCo', '20130401', 30, 'MEM001', 20, 'Sedative')
--Q2
,('InsureCo', '20130529', 30, 'MEM001', 30, 'Sedative')
,('InsureCo', '20130429', 30, 'MEM001', 20, 'Sedative')
,('InsureCo', '20130401', 30, 'MEM001', 20, 'Sedative')
--Q3
,('InsureCo', '20130829', 30, 'MEM001', 30, 'Sedative')
--MEM002
--Q1
,('InsureCo', '20130112', 30, 'MEM002', 10, 'Sedative')
,('InsureCo', '20130129', 30, 'MEM002', 20, 'Sedative')
,('InsureCo', '20130401', 30, 'MEM002', 20, 'Sedative')
--Q2
,('InsureCo', '20130529', 30, 'MEM002', 30, 'Sedative')
,('InsureCo', '20130429', 30, 'MEM002', 20, 'Sedative')
,('InsureCo', '20130401', 30, 'MEM002', 20, 'Sedative')
--Q3
,('InsureCo', '20130829', 30, 'MEM002', 30, 'Sedative')
--Q4
,('InsureCo', '20131129', 30, 'MEM002', 30, 'Sedative')
Result:
ProgramName DateFilled DaySupply MemberID Strength Quarter QuarterlyTotal QuarterlyAverage
InsureCo 2013-01-12 30 MEM001 10 1 30 15
InsureCo 2013-01-29 30 MEM001 20 1 30 15
InsureCo 2013-04-01 30 MEM001 20 2 90 22
InsureCo 2013-04-01 30 MEM001 20 2 90 22
InsureCo 2013-04-29 30 MEM001 20 2 90 22
InsureCo 2013-05-29 30 MEM001 30 2 90 22
InsureCo 2013-08-29 30 MEM001 30 3 30 30
InsureCo 2013-01-12 30 MEM002 10 1 30 15
InsureCo 2013-01-29 30 MEM002 20 1 30 15
InsureCo 2013-04-01 30 MEM002 20 2 90 22
InsureCo 2013-04-01 30 MEM002 20 2 90 22
InsureCo 2013-04-29 30 MEM002 20 2 90 22
InsureCo 2013-05-29 30 MEM002 30 2 90 22
InsureCo 2013-08-29 30 MEM002 30 3 30 30
InsureCo 2013-11-29 30 MEM002 30 4 30 30
I've done some playing around tonight and I'm much closer:
Some data:
create TABLE dateranges (drug VARCHAR(5), date_begin DATETIME, numdays integer,strength integer)
INSERT into dateranges values ('DrugA', '2010-01-01', 5, 10);
INSERT into dateranges values ('DrugB', '2008-02-27', 10, 20);
INSERT into dateranges values ('DrugC', '2010-04-26', 3, 20);
INSERT into dateranges values ('DrugD', '2000-02-01', 5, 30);
A CTE:
WITH cte (id, d, s)
AS (SELECT tbl.drug AS id
,tbl.date_begin AS d
,tbl.strength AS s
FROM dateranges tbl
WHERE DATEDIFF(DAY, tbl.date_begin, tbl.date_begin+numdays-1) <= 100
UNION ALL
SELECT tbl.drug AS id
,DATEADD(DAY, 1, cte.d) AS d
,tbl.strength as s
FROM cte
INNER JOIN dateranges tbl
ON cte.id = tbl.drug
WHERE cte.d < tbl.date_begin+numdays-1)
SELECT id AS drug
,d AS dates
,s AS strength
FROM cte
ORDER BY id, d, s
Results:
DRUG DATES STRENGTH
DrugA January, 01 2010 00:00:00+0000 10
DrugA January, 02 2010 00:00:00+0000 10
DrugA January, 03 2010 00:00:00+0000 10
DrugA January, 04 2010 00:00:00+0000 10
DrugA January, 05 2010 00:00:00+0000 10
DrugB February, 27 2008 00:00:00+0000 20
DrugB February, 28 2008 00:00:00+0000 20
DrugB February, 29 2008 00:00:00+0000 20
DrugB March, 01 2008 00:00:00+0000 20
DrugB March, 02 2008 00:00:00+0000 20
DrugB March, 03 2008 00:00:00+0000 20
DrugB March, 04 2008 00:00:00+0000 20
DrugB March, 05 2008 00:00:00+0000 20
DrugB March, 06 2008 00:00:00+0000 20
DrugB March, 07 2008 00:00:00+0000 20
DrugC April, 26 2010 00:00:00+0000 20
DrugC April, 27 2010 00:00:00+0000 20
DrugC April, 28 2010 00:00:00+0000 20
DrugD February, 01 2000 00:00:00+0000 30
DrugD February, 02 2000 00:00:00+0000 30
DrugD February, 03 2000 00:00:00+0000 30
DrugD February, 04 2000 00:00:00+0000 30
DrugD February, 05 2000 00:00:00+0000 30
From here, I plan on grouping by drug, date, strength (summing strength). I should be able to toss those results into a temp table, and then count the number of days over the threshold I mentioned.
Closed. This question does not meet Stack Overflow guidelines. It is not currently accepting answers.
Questions must demonstrate a minimal understanding of the problem being solved. Tell us what you've tried to do, why it didn't work, and how it should work. See also: Stack Overflow question checklist
Closed 9 years ago.
Improve this question
I have a table with data in my database as follows:
year month sales mom qoq yoy
----------------
2010 1 80 - - -
2010 2 61 -23.75% - -
2010 3 81 32.79% - -
2010 4 94 16.05% -
2010 5 77 -18.09% -
2010 6 75 -2.60% -
2010 7 58 -22.67% -
2010 8 74 27.59% -
2010 9 98 32.43% -
2010 10 97 -1.02% -
2010 11 94 -3.09% -
2010 12 63 -32.98% -
2011 1 61 -3.17% -23.75%
2011 2 79 29.51% 29.51%
2011 3 84 6.33% 3.70%
2011 4 100 19.05% 6.38%
2011 5 78 -22.00% 1.30%
2011 6 99 26.92% 32.00%
2011 7 78 -21.21% 34.48%
2011 8 63 -19.23% -14.86%
2011 9 66 4.76% -32.65%
2011 10 77 16.67% -20.62%
2011 11 93 20.78% -1.06%
2011 12 94 1.08% 49.21%
I want to calculate monthly, quarterly and yearly sales percentage change for each month period (each row), as shown in last 3 columns above.
How can i achieve this in one result set using T-SQL? I'm using SQL Server 2008 R2. Thanks.
This query works only in MSSQL2012, but it's plan and exec time are much better
SELECT
year,
month,
sales,
(sales - LAG(sales, 1) over (ORDER BY year, month)) / LAG(sales, 1) over (ORDER BY year, month)*100 AS mom,
(sales - LAG(sales, 4) over (ORDER BY year, month)) / LAG(sales, 4) over (ORDER BY year, month)*100 AS qoq,
(sales - LAG(sales, 12) over (ORDER BY year, month)) / LAG(sales, 12) over (ORDER BY year, month)*100 AS yoy
FROM #tab
It would be nice to use window function with LAG here, but it works only in MSSQL2012. So I use row_number
declare #tab table (year int, month int, sales money)
insert into #tab values
(2010, 1, 80 ),(2010, 2, 61 ),(2010, 3, 81 ),
(2010, 4, 94 ),(2010, 5, 77 ),(2010, 6, 75 ),
(2010, 7, 58 ),(2010, 8, 74 ),(2010, 9, 98 ),
(2010, 10, 97 ),(2010, 11, 94 ),(2010, 12, 63 ),
(2011, 1, 61 ),(2011, 2, 79 ),(2011, 3, 84 ),
(2011, 4, 100),(2011, 5, 78 ),(2011, 6, 99 ),
(2011, 7, 78 ),(2011, 8, 63 ),(2011, 9, 66 ),
(2011, 10, 77 ),(2011, 11, 93 ),(2011, 12, 94 );
with cte as (
select
row_number() over (order by year, month) rn,
year,
month,
sales
from #tab
)
select
year,
month,
sales,
round((sales-(select sales from cte where cte.rn=t1.rn-1))/(select sales from cte where cte.rn=t1.rn-1)*100.0,2) as mom,
round((sales-(select sales from cte where cte.rn=t1.rn-4))/(select sales from cte where cte.rn=t1.rn-4)*100.0,2) as qoq,
round((sales-(select sales from cte where cte.rn=t1.rn-12))/(select sales from cte where cte.rn=t1.rn-12)*100.0,2) as yoy
from cte as t1