3 month rolling Avg - sql-server

Question: need help with query to show as a 3-month rolling average
This is what I currently have
SELECT
Date,
month(date) AS[Month],
year(date) AS[Year],
AVG(ALL Amount) OVER (PARTITION BY Date ORDER BY Date ASC) AS Average
FROM
FactFinance
SELECT
YEAR(Date) AS Year,
MONTH(Date) AS Month,
AVG(Amount) AS AvgAmt
FROM
FactFinance
GROUP BY
YEAR(Date),
MONTH(Date)
ORDER BY
Year,
Month;
GO;
SQL Statement 2
SQL Statement 1

2 things I would do to test this 1) pick a year to make the data more digestible 2) amend the second query to include a sum. The first query should be amended to pre-calculate the the monthly figures using a cte or a sub query (I have chosen to use a sub query) and you need to use the preceding.. clause to tell sql server over how many months you want
select month,year,
sum(amt) over (order by month,year ROWS BETWEEN 2 PRECEDING AND current row) sumamount,
avg(amt) over (order by month,year ROWS BETWEEN 2 PRECEDING AND current row) '3monthra'
from
(
SELECT month(date) as [Month],
year(date) as [Year],
sum(Amount) amt,
count(*) as cnt
FROM FactFinance
GROUP BY YEAR([Date]), MONTH([Date])
) s
where year = 2005
order by year, month
SELECT
YEAR(Date) AS Year,
MONTH(Date) AS Month,
sum(amount) as sumamt,
count(*) as cnt,
AVG(Amount) AS AvgAmt
FROM FactFinance
where YEAR(Date) = 2005
GROUP BY YEAR(Date), MONTH(Date)
ORDER BY Year, Month;
GO
month year sumamount 3monthra
----------- ----------- ---------------------- ----------------------
7 2005 11384884.51 11384884.51
8 2005 36016653.13 18008326.565
9 2005 58029544.31 19343181.4366667
10 2005 66734589.35 22244863.1166667
11 2005 79778854.28 26592951.4266667
12 2005 88791927.09 29597309.03
(6 row(s) affected)
Year Month sumamt cnt AvgAmt
----------- ----------- ---------------------- ----------- ----------------------
2005 7 11384884.51 1130 10075.1190353982
2005 8 24631768.62 1122 21953.4479679145
2005 9 22012891.18 1116 19724.8128853047
2005 10 20089929.55 1122 17905.463057041
2005 11 37676033.55 1124 33519.6028024911
2005 12 31025963.99 1126 27554.1420870338
(6 row(s) affected)
Note this is taken from aw2012 , hopefully your version has 2005.

Related

How to arrange output by months pivoted by year in SQL?

I have a single table of payments called PYMT, and am trying to wrap my head around using a PIVOT if possible to get a certain arrangement for an output and befuddled as how to do this. In the table are pymt_amount and pymt_date (other columns too, but not necessary). I wish to see the output to look like so:
PayMonth 2007 2008 2009 2010 2011
1 26044.12 82663.79 83583.17 35963.49 100865.94
2 60145.61 35245.06 19173.08 14417.98 21502.71
3 68138.88 88670.16 85319.66 40850.39 31595.43
4 228835.04 215258.84 157905.56 136551.46 166027.30
5 395877.88 348307.58 348506.09 363460.24 298488.22
6 618013.05 662869.88 522233.48 472174.95 385879.94
7 557751.27 363659.66 305363.68 304606.98 349173.75
8 355639.91 173107.60 266235.54 147731.54 251878.49
9 131440.63 173338.90 133869.36 140035.13 109595.83
10 168148.90 127356.25 114818.69 119082.52 139201.50
11 139543.35 138151.22 128667.58 137351.77 107807.27
12 142286.06 136670.64 116980.04 69609.22 85670.84
To get the first column of payment totals is easy - it's doing it for the other years that I can't figure out - I know how to do a basic PIVOT table.
The query for the first 2 columns is
SELECT DATEPART(MM, pymt_Date) AS PayMonth, SUM(pymt_Amount) AS [2007]
FROM PYMT
GROUP BY DATEPART(MM, pymt_Date) , DATEPART(YY, pymt_Date)
HAVING (DATEPART(YY, pymt_Date) = 2007)
ORDER BY PayMonth
How to add the other years (each payMonth is a sum of all payments for the month), but I don't wish to pivot by month (just the years by the month as I show in the output)
I could run a separate query in a cursor per month
Here's an example, but showing the grand total for the year (but it needs to be separated by month)
SELECT * FROM
(SELECT DATEPART(yy, pymt_Date) AS PayYear, pymt_Amount
FROM PYMT
) tbl1
PIVOT
(SUM(pymt_Amount)
FOR
PayYear in ([2007],[2008],[2009],[2010],[2011])
) tbl2
which yields
2007 2008 2009 2010 2011
2891864.70 2545299.58 2282655.93 1981835.67 2047687.22
As you can see - this isn't broken down by month rows
Any ideas?
You missed this DATEPART(month, pymt_Date) AS PayMonth in the tbl1 query.
The complete query should be
SELECT *
FROM
(
SELECT DATEPART(year, pymt_Date) AS PayYear,
DATEPART(month, pymt_Date) AS PayMonth,
pymt_Amount
FROM PYMT
) tbl1
PIVOT
(
SUM(pymt_Amount)
FOR PayYear in ([2007],[2008],[2009],[2010],[2011])
) tbl2

Snowflake- Calculate day of Quarter

Snowflake has the simply function Quarter(timestamp()) which returns current quarter, but wondering how to do day of QTR , all tutorials reference Postgres/ sql server.
Goal - create a date table, and show what day of the quarter it is for the next 20 years.
SELECT column1::timestamp as d,
DATE_TRUNC('QUARTER',d) as q,
DATEDIFF('day',q, d) as doq
FROM VALUES ('2019-10-30'),('2019-10-01');
gives 0 for the first day of the quarter, so if you need that to be 1 you can +1 that datadiff.
D Q DOQ
2019-10-30 00:00:00.000 2019-10-01 00:00:00.000 29
2019-10-01 00:00:00.000 2019-10-01 00:00:00.000 0
[Edit:] After re-reading your goal of a 20 year table, here is some code I have used in snowflake in the past to just that:
CREATE OR REPLACE TABLE twenty_years_of_days(date) AS
SELECT DATEADD(day, rn, CURRENT_DATE) as date,
DATE_TRUNC('QUARTER',date) as quarter,
DATEDIFF('day',quarter, date) as doq
FROM (
SELECT row_number() over(order by 1) as rn
FROM TABLE(GENERATOR(rowCount => 365*20)) v
);

7 day average between date range

I am using SQL Server to solve the following question:
My table T1 has the following data:
Date Id Name Rent Number
01/01/2019 1 A 100 10
01/02/2019 1 A 200 30
01/03/2019 1 A 300 40
.
.
.
12/31/2019 1 A 150 25
The data exists for different combinations of Id and Name. I am trying to find average Rent and Number for 7 days:
Final Output
Date Id Name Rent Number
01/01/2019 - 01/07/2019 1 A Avg(rent for 7 days) Avg(Number for 7 days)
01/08/2019 - 01/14/2019 1 A Avg(rent for 7 days) Avg(Number for 7 days)
The final data should be grouped by Id and Name
My code:
SELECT min(date), Id, Name,
AVG(Rent) as Rent,
AVG(Number) Number,
AVG(AVG(Rent)) OVER (ORDER BY min(date) ROWS BETWEEN 6 PRECEDING AND CURRENT ROW) as AvgRent,
AVG(AVG(Number)) OVER (ORDER BY min(date) ROWS BETWEEN 6 PRECEDING AND CURRENT ROW) as AvgNumber
FROM T1
WHERE date >= '2019-01-01'
AND date < '2019-12-31'
GROUP BY Id, Name
My output has only row.
You need to group the dates by week which you can do by finding the difference between '01/01/2019' and your Date column using the DATEDIFF function and then divide it by 7. Since both the dividend and the divisor are integers the quotient will be an integer as well with the effect of group your dates into weeks.
SELECT MIN(Date) AS [Start Date]
, MAX(Date) AS [End Date]
, Id
, Name
, AVG(Rent) AS [Avg Rent]
, AVG(Number) AS [Avg Number]
FROM T1
GROUP BY DATEDIFF(DAY, '01/01/2019', Date) / 7;

T-SQL Get Records for this year grouped by month

I have a table of data which looks like this
ID CreatedDate
A123 2015-01-01
B124 2016-01-02
A125 2016-01-03
A126 2016-01-04
What I would like to do is group by month (as text) for this year only. I have some up with the following query but it returns data from all years not just this one:
Select Count(ID), DateName(month,createddate) from table
Where (DatePart(year,createddate)=datepart(year,getdate())
Group by DateName(month,createddate)
This returns
Count CreatedDate
4 January
Instead of
Count CreatedDate
3 January
Where have I gone wrong? I'm sure it's something to do with converting the date to month where it goes wrong
Just tested your code:
;WITH [table] AS (
SELECT *
FROM (VALUES
('A123', '2015-01-01'),
('B124', '2016-01-02'),
('A125', '2016-01-03'),
('A126', '2016-01-04')
) as t(ID, CreatedDate)
)
SELECT COUNT(ID),
DATENAME(month,CreatedDate)
FROM [table]
WHERE DATEPART(year,CreatedDate)=DATEPART(year,getdate())
GROUP BY DATENAME(month,CreatedDate)
Output was
3 January
I removed ( near WHERE
select count(id) as Count,
case when month(createddate)=1 THEN 'Januray' END as CreatedDate
from [table]
--where year(createddate)=2016 optional if you only want the 2016 count
group by month(createddate),year(createdDate)

SELECT top 5 SUMs (one per customer) for each month in range

I have a query that pulls out month/year totals for customers, and add the ntile ranking. If I were to be able to pull out the max subtotal for ntile 1, 2, 3, 4, and 5, I would ALMOST get what I'm after, but I do not know how to proceed.
For example, the result I want would look something like:
Month Year CustomerCode SubTotal ntile
1 2012 CCC 131.45 1
1 2012 CCC 342.95 2
1 2012 ELITE 643.92 3
1 2012 CCC 1454.05 4
1 2012 CCC 12971.78 5
2 2012 CCC 135.99 1
2 2012 CCI 370.47 2
2 2012 NOC 766.84 3
2 2012 ELITE 1428.26 4
2 2012 VBC 5073.20 5
3 2012 CCC 119.02 1
3 2012 CCC 323.78 2
3 2012 HUCC 759.66 3
3 2012 ELITE 1402.95 4
3 2012 CCC 7964.20 5
EXCEPT - I would expect ranking to be different customers like for month 2, but my base query isn't giving me that result - and I obviously don't know how to get it in T-SQL on SQL SERVER 2005 - in fact I'm not sure what I'm getting.
My next option is to pull a DataTable in C# and do some gymnastics to get there, but there has to be an easier way :)
My base query is
SELECT
i.DateOrdered
,LTRIM(STR(DATEPART(MONTH,i.DateOrdered))) AS [Month]
,LTRIM(STR(YEAR(i.Dateordered))) AS [Year]
,c.CustomerCode
,SUM(i.Jobprice) AS Subtotal
,NTILE(5) OVER(ORDER BY SUM(i.JobPrice)) AS [ntile]
FROM Invoices i
JOIN
Customers c
ON i.CustomerID = c.ID
WHERE i.DateOrdered >= '1/1/2012'
AND i.DateOrdered <= '9/30/2012'
GROUP BY YEAR(i.DateOrdered), MONTH(i.DateOrdered), i.DateOrdered, c.CustomerCode
ORDER BY LTRIM(STR(DATEPART(MONTH,i.DateOrdered))),
TRIM(STR(YEAR(i.Dateordered))),
SUM(i.JobPrice), c.CustomerCode ASC
I'd really appreciate help getting this right.
Thanks in advance
Cliff
If I read you correctly, what you are after is
For each month in the range,
Show 5 customers who have the greatest SUMs in that month
And against each customer, show the corresponding SUM.
In that case, this SQL Fiddle creates a sample table and runs the query that gives you the output described above. If you wanted to see what's in the created tables, just do simple SELECTs on the right panel.
The query is:
; WITH G as -- grouped by month and customer
(
SELECT DATEADD(D,1-DAY(i.DateOrdered),i.DateOrdered) [Month],
c.CustomerCode,
SUM(i.Jobprice) Subtotal
FROM Invoices i
JOIN Customers c ON i.CustomerID = c.ID
WHERE i.DateOrdered >= '1/1/2012' AND i.DateOrdered <= '9/30/2012'
GROUP BY DATEADD(D,1-DAY(i.DateOrdered),i.DateOrdered), c.CustomerCode
)
SELECT MONTH([Month]) [Month],
YEAR([Month]) [Year],
CustomerCode,
SubTotal,
Rnk [Rank]
FROM
(
SELECT *, RANK() OVER (partition by [Month] order by Subtotal desc) Rnk
FROM G
) X
WHERE Rnk <= 5
ORDER BY Month, Rnk
To explain, the first part (WITH block) is just a fancy way of writing a subquery, that GROUPs the data by month and Customer. The expression DATEADD(D,1-DAY(i.DateOrdered),i.DateOrdered) turns every date into the FIRST day of that month, so that the data can be easily grouped by month. The next subquery written in traditional form adds a RANK column within each month by the subtotal, which is finally SELECTed to give the top 5*.
Note that RANK allows for equal rankings, which may end up showing 6 customers for a month, if 3 of them are ranked equally at position 4. If that is not what you want, then you can change the word RANK to ROW_NUMBER which will randomly tie-break between equal Subtotals.
The query needs to be modified to only get the month and year dateparts. The issue you are having with the same customer showing multiple times in the same month is due to the inclusion of i.DateOrdered in the select and group by clauses.
The following query should give you what you need. Also, I suspect it is a typo on the next to last line of the query, but tsql doesn't have a TRIM() function only LTRIM and RTRIM.
SELECT
LTRIM(STR(DATEPART(MONTH,i.DateOrdered))) AS [Month]
,LTRIM(STR(YEAR(i.Dateordered))) AS [Year]
,c.CustomerCode
,SUM(i.Jobprice) AS Subtotal
,NTILE(5) OVER(ORDER BY SUM(i.JobPrice)) AS [ntile]
FROM Invoices i
JOIN
Customers c
ON i.CustomerID = c.ID
WHERE i.DateOrdered >= '1/1/2012'
AND i.DateOrdered <= '9/30/2012'
GROUP BY YEAR(i.DateOrdered), MONTH(i.DateOrdered), c.CustomerCode
ORDER BY LTRIM(STR(DATEPART(MONTH,i.DateOrdered))),
LTRIM(STR(YEAR(i.Dateordered))),
SUM(i.JobPrice), c.CustomerCode ASC
This gives these results
Month Year CustomerCode Subtotal ntile
1 2012 ELITE 643.92 2
1 2012 CCC 14900.23 5
2 2012 CCC 135.99 1
2 2012 CCI 370.47 1
2 2012 NOC 766.84 3
2 2012 ELITE 1428.26 4
2 2012 VBC 5073.20 4
3 2012 HUCC 759.66 2
3 2012 ELITE 1402.95 3
3 2012 CCC 8407.00 5
Try this:
declare #tab table
(
[month] int,
[year] int,
CustomerCode varchar(20),
SubTotal float
)
insert into #tab
select
1,2012,'ccc',131.45 union all
select
1,2012,'ccc',343.45 union all
select
1,2012,'ELITE',643.92 union all
select
2,2012,'ccc',131.45 union all
select
2,2012,'ccc',343.45 union all
select
2,2012,'ELITE',643.92 union all
select
3,2012,'ccc',131.45 union all
select
3,2012,'ccc',343.45 union all
select
3,2012,'ELITE',643.92
;with cte as
(
select NTILE(3) OVER(partition by [month] ORDER BY [month]) AS [ntile],* from #tab
)
select * from cte
Even in your base query you need to add partition by, so that you will get correct output.
I can't see how to solve this problem without double ranking:
You need to get the largest sums per customer & month.
You then need, for every month, to retrieve the top five of the found sums.
Here's how I would approach this:
;
WITH MaxSubtotals AS (
SELECT DISTINCT
CustomerID,
MonthDate = DATEADD(MONTH, DATEDIFF(MONTH, 0, DateOrdered), 0),
Subtotal = MAX(SUM(JobPrice)) OVER (
PARTITION BY Customer, DATEADD(MONTH, DATEDIFF(MONTH, 0, DateOrdered), 0)
ORDER BY SUM(JobPrice)
)
FROM Invoices
GROUP BY
CustomerID,
DateOrdered
),
TotalsRanked AS (
SELECT
CustomerID,
MonthDate,
Subtotal,
Ranking = ROW_NUMBER() OVER (PARTITION BY MonthDate ORDER BY Subtotal DESC)
FROM MaxDailyTotals
)
SELECT
Month = MONTH(i.MonthDate),
Year = YEAR(i.MonthDate),
c.CustomerCode,
i.Subtotal,
i.Ranking
FROM TotalsRanked i
INNER JOIN Customers ON i.CustomerID = c.ID
WHERE i.Ranking <= 5
;
The first CTE, MaxSubtotals, determines the maximum subtotals per customer & month. Involving DISTINCT and a window aggregating function, it is essentially a "shortcut" for the following two-step query:
SELECT
CustomerID,
MonthDate,
Subtotal = MAX(Subtotal)
FROM (
SELECT
CustomerID,
MonthDate = DATEADD(MONTH, DATEDIFF(MONTH, 0, DateOrdered), 0),
Subtotal = SUM(JobPrice)
FROM Invoices
GROUP BY
CustomerID,
DateOrdered
) s
GROUP BY
CustomerID,
MonthDate
The other CTE, TotalsRanked, simply adds ranking numbers for the found susbtotals, partitioning by customer and month. As a final step, you only need to limit the rows to those that have rankings not greater than 5 (or whatever you might choose another time).
Note that using ROW_NUMBER() to rank the rows in this case guarantees that you'll get no more than 5 rows with the Ranking <= 5 filter. If there were two or more rows with the same subtotal, the would get distinct rankings, and in the end you might end up with an output like this:
Month Year CustomerCode Subtotal Ranking
----- ---- ------------ -------- -------
1 2012 CCC 1500.00 1
1 2012 ELITE 1400.00 2
1 2012 NOC 900.00 3
1 2012 VBC 700.00 4
1 2012 HUCC 700.00 5
-- 1 2012 ABC 690.00 6 -- not returned
-- 1 2012 ... ... ...
Even though there might be other customers with Subtotals of 700.00 for the same month, they wouldn't be returned, because they would be assigned rankings after 5.
You could use RANK() instead of ROW_NUMBER() to account for that. But note that you might end up with more than 5 rows per month then, with an output like this:
Month Year CustomerCode Subtotal Ranking
----- ---- ------------ -------- -------
1 2012 CCC 1500.00 1
1 2012 ELITE 1400.00 2
1 2012 NOC 900.00 3
1 2012 VBC 700.00 4
1 2012 HUCC 700.00 4
1 2012 ABC 700.00 4
-- 1 2012 DEF 690.00 7 -- not returned
-- 1 2012 ... ... ...
Customers with subtotals less than 700.00 wouldn't make it to the output because they would have rankings starting with 7, which would correspond to the ranking of the first under-700.00 sum if ranked by ROW_NUMBER().
And there's another option, DENSE_RANK(). You might want to use it if you want up to 5 distinct sums per month in your output. With DENSE_RANK() your output might contain even more rows per month than it would have with RANK(), but the number of distinct subtotals would be exactly 5 (or fewer if the original dataset can't provide you with 5). That is, your output might then look like this:
Month Year CustomerCode Subtotal Ranking
----- ---- ------------ -------- -------
1 2012 CCC 1500.00 1
1 2012 ELITE 1400.00 2
1 2012 NOC 900.00 3
1 2012 VBC 700.00 4
1 2012 HUCC 700.00 4
1 2012 ABC 700.00 4
1 2012 DEF 650.00 5
1 2012 GHI 650.00 5
1 2012 JKL 650.00 5
-- 1 2012 MNO 600.00 5 -- not returned
-- 1 2012 ... ... ...
Like RANK(), the DENSE_RANK() function assigns same rankings to identical values, but, unlike RANK(), it doesn't produce gaps in the ranking sequence.
References:
OVER Clause (Transact-SQL)
Ranking Functions (Transact-SQL)

Resources