Filling missing months when calculating year to date - sql

I have a table cumulative year todate
year month qty_ytd
2017 01 20
2017 02 30
2018 01 50
I need to fill gabs missing months in the same year till december:
Result as example:
year month qty_ytd
2017 01 20
2017 02 30
2017 03 30
.....
2017 07 30
2017 12 30
2018 01 50
2018 02 50
....
2018 12 50
How to do it? I did'nt figure out how to fill the missing months?

You can use cross join to generate the rows and cross apply to get the data:
select y.y, v.m, t.qty_ytd
from (select distinct year from t) y cross join
(values (1), (2), (3), (4), . . . (12)) v(m) outer apply
(select top (1) t.*
from t
where t.year = y.year and
t.month <= y.m
order by t.m desc
) t;
Assuming qty_ytd is non-decreasing, it might be more performant to use window functions:
select y.y, v.m,
max(t.qty_ytd) over (partition by y.y order by v.m) as qty_ytd
from (select distinct year from t) y cross join
(values (1), (2), (3), (4), . . . (12)) v(m) left join
t
on t.year = y.year and
t.month = v.m;

Another option is to compute delta, add dummy zero deltas, restore running total. I've changed source data to show more common case
create table #t
(
year int,
month int,
qty_ytd int
);
insert #t(year, month, qty_ytd )
values
(2017, 01, 20),
(2017, 02, 30),
(2018, 04, 50) -- note month
;
select distinct year, month, sum(delta) over(partition by year order by month)
from (
-- real delta
select year, month, delta = qty_ytd - isnull(lag(qty_ytd) over (partition by year order by month),0)
from #t
union all
-- tally dummy delta
select top(24) 2017 + (n-1)/12, n%12 + 1 , 0
from
( select row_number() over(order by a.n) n
from
(values (1),(2),(3),(4),(5),(6),(7),(8),(9),(10)) a(n),
(values (1),(2),(3),(4),(5),(6),(7),(8),(9),(10)) b(n)
) c
)d
order by year, month;

Related

Compare value of current row to average of all previous rows

I'm looking to compare the value of each date to the average value of all previous dates and calculate the percent change. For example, in the source table below, I would want to compare the value of 100 from December 2022 to the average of November, October, and September ((75+60+75)/3) to bring back the 0.43 change.
Source Table
Date
Value
December 2022
100
November 2022
75
October 2022
60
September 2022
75
Desired Output
Date
Value
Comparison
December 2022
100
0.43
November 2022
75
0.11
October 2022
60
-0.20
September 2022
75
-
You need a windowed AVG with an OVER clause using the appropriate range of rows (ORDER BY [Date] ASC ROWS BETWEEN UNBOUNDED PRECEDING AND 1 PRECEDING):
Test data:
SELECT *
INTO Data
FROM (VALUES
(20221201, 100),
(20221101, 75),
(20221001, 60),
(20220901, 75)
) v ([Date], [Value])
Statement:
SELECT [Date], [Value], ([Value] - [Average]) * 1.00 / [Average] AS [Comparison]
FROM (
SELECT
*,
[Average] = AVG([Value]) OVER (ORDER BY [Date] ASC ROWS BETWEEN UNBOUNDED PRECEDING AND 1 PRECEDING)
FROM Data
) t
ORDER BY [Date] DESC
Result (without rounding):
Date
Value
Comparison
20221201
100
0.4285714285714
20221101
75
0.1194029850746
20221001
60
-0.2000000000000
20220901
75
null
drop table #t
select *
into #t
from
(
VALUES (1,N'December 2022', 100.0)
, (2,N'November 2022', 75.0)
, (3,N'October 2022', 60.0)
, (4,N'September 2022', 75.0)
) t (sort, col1,col2)
select col2, (col2 - AVG(col2) OVER(ORDER BY sort DESC ROWS between UNBOUNDED PRECEDING and 1 PRECEDING)) / AVG(col2) OVER(ORDER BY sort DESC ROWS between UNBOUNDED PRECEDING and 1 PRECEDING)
, AVG(col2) OVER(ORDER BY sort DESC)
from #t
order by sort
Something like this. watch out for 0 values though

Sum of last 12 months

I have a table with 3 columns (Year, Month, Value) like this in Sql Server :
Year
Month
Value
ValueOfLastTwelveMonths
2021
1
30
30
2021
2
24
54 (30 + 24)
2021
5
26
80 (54+26)
2021
11
12
92 (80+12)
2022
1
25
87 (SUM of values from 1 2022 TO 2 2021)
2022
2
40
103 (SUM of values from 2 2022 TO 3 2021)
2022
4
20
123 (SUM of values from 4 2022 TO 5 2021)
I need a SQL request to calculate ValueOfLastTwelveMonths.
SELECT Year,
       Month,
Value,
SUM (Value) OVER (PARTITION BY Year, Month)
FROM MyTable
This is much easier if you have a row for each month and year, and then (if needed) you can filter the NULL rows out. The reason it's easier is because then you know how many rows you need to look back at: 11.
If you make a dataset of the years and months, you can then LEFT JOIN to your data, aggregate, and then finally filter the data out:
SELECT *
INTO dbo.YourTable
FROM (VALUES(2021,1,30),
(2021,2,24),
(2021,5,26),
(2021,11,12),
(2022,1,25),
(2022,2,40),
(2022,4,20))V(Year,Month,Value);
GO
WITH YearMonth AS(
SELECT YT.Year,
V.Month
FROM (SELECT DISTINCT Year
FROM dbo.YourTable) YT
CROSS APPLY (VALUES(1),(2),(3),(4),(5),(6),(7),(8),(9),(10),(11),(12))V(Month)),
RunningTotal AS(
SELECT YM.Year,
YM.Month,
YT.Value,
SUM(YT.Value) OVER (ORDER BY YM.Year, YM.Month
ROWS BETWEEN 11 PRECEDING AND CURRENT ROW) AS Last12Months
FROM YearMonth YM
LEFT JOIN dbo.YourTable YT ON YM.Year = YT.Year
AND YM.Month = YT.Month)
SELECT Year,
Month,
Value,
Last12Months
FROM RunningTotal
WHERE Value IS NOT NULL;
GO
DROP TABLE dbo.YourTable;

Create a year/quarter table in SQL without a loop

I have a start year and an end year, say 2017 and 2019 for example.
I'd like to create a table with columns year and quarter (eg, 1, 2, 3, 4) between my stated startYear and endYear, and have quarter for the final, endYear, to stop at 2 (it's always forward looking).
Sample desired output below.
year quarter
2017 1
2017 2
2017 3
2017 4
2018 1
2018 2
2018 3
2018 4
2019 1
2019 2
Seems like it should be simple, nothing occurs to me except somewhat clunky methods relying on a loop or UNION or simply inserting values manually into the table.
Just another option... an ad-hoc tally table in concert with a Cross Join
Example
Declare #Y1 int = 2017
Declare #Y2 int = 2019
Select *
From ( Select Top (#Y2-#Y1+1) Year=#Y1-1+Row_Number() Over (Order By (Select NULL)) From master..spt_values n1 ) A
Cross Join (values (1),(2),(3),(4)) B([Quarter])
Returns
Year Quarter
2017 1
2017 2
2017 3
2017 4
2018 1
2018 2
2018 3
2018 4
2019 1
2019 2
2019 3
2019 4
Use a recursive CTE:
with yq as (
select 2017 as yyyy, 1 as qq
union all
select (case when qq = 4 then yyyy + 1 else yyyy end),
(case when qq = 4 then 1 else qq + 1 end)
from yq
where yyyy < 2019 or yyyy = 2019 and qq < 2
)
select *
from yq;
If the table will have more than 100 rows, you will also need option (maxrecursion 0).
Here is a db<>fiddle.
This solution is very similar to the one by John, but it doesn't depend on a system table.
Declare #Y1 int = 2017;
Declare #Y2 int = 2019;
WITH
E(n) AS(
SELECT n FROM (VALUES(0),(0),(0),(0),(0),(0),(0),(0),(0),(0))E(n)
),
E2(n) AS(
SELECT a.n FROM E a, E b
),
E4(n) AS(
SELECT a.n FROM E2 a, E2 b
),
cteYears([Year]) AS(
SELECT TOP (#Y2-#Y1+1)
ROW_NUMBER() OVER(ORDER BY (SELECT NULL)) + #Y1 - 1 AS [Year]
FROM E4
)
SELECT [Year], [Quarter]
FROM cteYears
CROSS JOIN (VALUES (1),(2),(3),(4)) Q([Quarter]);
Let me to propose a recursve query for you:
WITH prepare AS
(
SELECT tbl.year
FROM (VALUES (2017) ) AS tbl(year) -- for example, start year is 2k17
UNION ALL
SELECT year + 1
FROM prepare
WHERE year < 2030 -- and last year is 2030
)
SELECT
year, quarter
FROM prepare
CROSS JOIN ( VALUES (1), (2), (3), (4) ) AS tbl (quarter)

How to get all month names and need to show month data

Pnum Fdate description
==== ========== ===========
1024 2018-02-17 A
1024 2018-05-17 B
1024 2018-05-17 C
1024 2018-09-17 D
MY table PW have fields looks like this.
--> I want to show the result as
**Month Name Description**
January -
February A
March -
April -
May B
June -
July -
August C
September D
October -
November -
December -
Please help me how to achive this.
Join with a list of month names, there is only twelve of them:
SELECT monthname, description
FROM (VALUES
(1, 'January'),
(2, 'February'),
(3, 'March'),
(4, 'April'),
(5, 'May'),
(6, 'June'),
(7, 'July'),
(8, 'August'),
(9, 'September'),
(10, 'October'),
(11, 'November'),
(12, 'December')
) AS va(monthnumber, monthname)
LEFT JOIN yourdata ON DATEPART(MONTH, fdate) = va.monthnumber
ORDER BY monthnumber
Try this
;WITH CTE(Pnum, Fdate,description)
AS
(
SELect 1024,'2018-02-17','A' union all
SELect 1024,'2018-05-17','B' union all
SELect 1024,'2018-08-17','C' union all
SELect 1024,'2018-09-17','D'
)
SELECT MonthNames,ISNULL([Description],'-') AS [Description]
FROM CTE RIGHT JOIN
(
SELECT DATENAME(MONTH,DATEADD(MONTH,number-datepart(month,GETDATE()),GETDATE())) as MonthNames
FROM MASTER.DBO.spt_values
WHERE TYPE ='P'
AND number BETWEEN 1 AND 12
) dt
ON dt.MonthNames=DATENAME(MONTH,Fdate)
Result
MonthNames Description
--------------------------
January -
February A
March -
April -
May B
June -
July -
August C
September D
October -
November -
December -
You can try below
DEMO
with cte1 as (
select cast('2018-01-01' as date) dt
union all
select dateadd(month, 1, dt)
from cte1
where dateadd(month, 1, dt) < cast('2018-12-31' as date)
)
select DateName(month,dt),coalesce(Description,'-') as Description from cte1 a left join yourtable b
on month(a.dt)=month(b.Fdate)
This solution will allow an index on Fdate to be used (MONTH(column) will force a scan every time).
DECLARE #year int = 2018;
;WITH m AS
(
SELECT m = 1 UNION ALL SELECT m + 1 FROM m WHERE m < 12
),
months(b,e) AS
(
SELECT b = DATEFROMPARTS(#year, m, 1)
FROM m
)
SELECT DATENAME(MONTH, m.b), PW.Description
FROM months AS m
LEFT OUTER JOIN dbo.PW
ON PW.Fdate >= m.b AND PW.Fdate < DATEADD(MONTH, 1, m.b)
ORDER BY m.b;
Try the following query:
SELECT MONTHNAME(fdate), description FROM table
For more reference go through
https://dev.mysql.com/doc/refman/8.0/en/date-and-time-functions.html#function_month

How to write where clause in pivot statement in SQL Server

I need to get records based on project and year
SELECT
*
FROM
(SELECT
Source,
val,
month
FROM
tbl_OrganicResult
CROSS APPLY
(VALUES ('visitors', visitors),
('UniqueVisitors', UniqueVisitors),
('ReturnVisitors', ReturnVisitors)) cs (Source, val)) A
PIVOT (Max(val)
FOR month IN ([jun], [jul)) pv
I need the records with where condition like
select *
from tbl_OrganicResult
where project = 'Homeocare'
Sample data
ProjectName Month Year visitors UniqueVisitors ReturnVisitors
Homeocare Jun 2015 400 33 22
Homeocare Jul 2015 100 10 8
debug test. Aug 2015 15222 122 120
debug test. Jun. 2015 1500 150 15
debug test. Jul 2015 1400 140 14
I'm getting records like this which is not correct, I mean I'm not getting project wise.
How to add where condition to get like where project - 'homeocare' ?
Getting output like this
Source jun jul
ReturnVisitors 8 120
UniqueVisitors 10 122
Visitors 100 15222
I need records like this
Source jun jul
ReturnVisitors. 22 8
UniqueVisitors. 33 10
Visitors 400 100
Sorry for my mistake of giving wrong data (earlier). Here always getting last 2 records and not used where condition to get records project and year wise.
The where clause should go after the cross apply like this:
SELECT
*
FROM
(
SELECT
Source,
val,
Year,
month
FROM
tbl_OrganicResult
CROSS APPLY
(VALUES ('visitors', visitors),
('UniqueVisitors', UniqueVisitors),
('ReturnVisitors', ReturnVisitors)) cs (Source, val)
WHERE ProjectName = 'Homeocare'
) A
PIVOT ( Max(val) FOR Month IN ([jun], [jul]) ) pv
Unless you only have data for one year in your table this query will get you the max value for a month from any year where you have data recorded for that month, for example if you had 100 visitors in June 15 and 200 visitors in June 14 then the value for June 14 would be selected. This might not be what you want.
I would consider doing something like this instead:
SELECT
Source, [Jun-2015], [Jul-2015]
FROM
(
SELECT
Source,
Val,
MonthYear = CONCAT(Month,'-',Year)
FROM
tbl_OrganicResult
CROSS APPLY
(VALUES ('Visitors', visitors),
('UniqueVisitors', UniqueVisitors),
('ReturnVisitors', ReturnVisitors)) cs (Source, Val)
WHERE ProjectName = 'Homeocare'
) A
PIVOT ( MAX(Val) FOR MonthYear IN ([Jun-2015], [Jul-2015]) ) pv ;
in your case you have three months but you are looking for july in place of june and August in place of july just add alias names for those columns
declare #table table (ProjectName varchar(20), Month varchar(20), Year varchar(20), visitors INT, UniqueVisitors INT, ReturnVisitors INT)
insert into #table (ProjectName,Month,Year,visitors,UniqueVisitors,ReturnVisitors)values
('Homeocare' , 'Jun' , 2015 , 400 , 33 , 22),
('Homeocare' , 'Jul' , 2015 , 100 , 10 , 8),
('debug test', 'Aug' , 2015 , 15222 , 122 , 120 )
SELECT source,JUN as Jul,jul as aug FROM (SELECT Source,
val,
month
FROM #table
CROSS apply (VALUES ('visitors',visitors),
('UniqueVisitors',UniqueVisitors),
('ReturnVisitors',ReturnVisitors)) cs (Source, val)) A
PIVOT (max(val)
FOR month IN ([jun],[Jul],
[Aug])) pv