Calculate Average of each month in SQL Server - sql

I'm having two tables
Promo
ID | Name
---+---------
1 Front
2 Middle
3 Back
PromoRate
ID | Date | Rate | PromoID
---+------------+------+----------
1 2020-01-01 100 1
2 2020-01-02 200 1
3 2020-02-03 300 1
4 2020-02-01 150 2
5 2020-01-02 250 2
6 2020-03-03 350 2
7 2020-03-01 200 3
8 2020-01-02 400 3
9 2020-01-03 600 3
I want to calculate average. Something like this
Name | Avg(Jan) | Avg(Feb) | Avg(Mar)
-------+----------+----------+----------
Front 150 300 NULL
Middle 250 150 350
Back 500 NULL 200
Like pivot or something.
Please help me. Thanks in advance
What I've tried:
SELECT * FROM
(
SELECT P.ID, P.Name, AVG(Rate) AS 'AVG' FROM PromoRate PR
INNER JOIN Promo P ON P.ID = PR.ProfileID
WHERE MONTH(Date) = MONTH(GETDATE())
GROUP BY P.Name, P.ID
) M1
LEFT JOIN
(
SELECT P.ID, P.Name, AVG(Rate) AS 'AVG' FROM PromoRate PR
INNER JOIN Promo P ON P.ID = PR.ProfileID
WHERE MONTH(Date) = MONTH(GETDATE()) + 1
GROUP BY P.Name, P.ID
) M2 ON M1.ID = M2.ID
LEFT JOIN
(
SELECT P.ID, P.Name, AVG(Rate) AS 'AVG' FROM PromoRate PR
INNER JOIN Promo P ON P.ID = PR.ProfileID
WHERE MONTH(Date) = MONTH(GETDATE()) + 2
GROUP BY P.Name, P.ID
) M3 ON M2.ID = M3.ID
But it's not working as expected

You need to group on Month(Date), I'd also use DateAdd rather than month(GetDate()) + 2. You can then use the one query rather than joining on 3 queries as you're going to run into trouble when you cross the year line, or if you have multiple years. E.g. running it in November will also return the results for January that year and next year.
If this is part of a stored procedure I'd also recommend creating an #startdate and #enddate variable and setting those up first.
declare #start datetime
declare #end datetime
select #start = convert(date,GETDATE()), #end = convert(date, DATEADD(month, 2, GETDATE()))
SELECT P.ID, P.Name, AVG(Rate) AS 'AVG' FROM PromoRate PR
INNER JOIN Promo P ON P.ID = PR.ProfileID
WHERE Convert(date, Date) > #start and convert(date, date) < #end
GROUP BY P.Name, P.ID, MONTH(Date)
ETA: You also need to return your month so that you can populate your pivot table.

Related

MsSQL LEFT JOIN with double GROUP BY

I am trying currently using MsSQL for a product shipping DB. I have spent a long time trying to write a SQL query to get the amount of products going to each delivery location in an area, per date, in a 4 day period beginning today.
In an area means that there is another location that is parent to that location.
The tables concerned are Products and Locations and are structured as follows
Products
ProductID DeliveryDate DestinationID
1 2018-10-05 1
2 2018-10-05 2
3 2018-10-05 3
4 2018-10-06 1
5 2018-10-06 5
Locations
LocationID OwnerID
1 4
2 4
3 4
4 Null
5 6
6 Null
The output desired is as follows
DeliveryDate Destination ProductCount
2018-10-04 1 Null
2018-10-04 2 Null
2018-10-04 3 Null
2018-10-05 1 1
2018-10-05 2 1
2018-10-05 3 1
2018-10-06 1 1
2018-10-06 2 Null
2018-10-06 3 Null
2018-10-07 1 Null
2018-10-07 2 Null
2018-10-07 3 Null
What I have tried so far is this
DECLARE #startdate DATETIME
,#enddate DATETIME
SET #startdate = convert(varchar, '2018-10-04 00:00:00', 102)
SET #enddate = convert(varchar, '2018-10-07 00:00:00', 102)
;WITH DateArray
AS (
SELECT #startdate DateVal
UNION ALL
SELECT DateVal + 1
FROM DateArray
WHERE DateVal + 1 <= #enddate
)
SELECT * FROM
(SELECT da.DateVal AS DeliveryDate
FROM DateArray da) a
LEFT JOIN
(SELECT ISNULL(COUNT(p.ProductID),0) AS ProductCount,
DeliveryDate,
ISNULL(p.DestinationID,'') AS Destination
FROM Product p
AND p.DestinationID IN(SELECT LocationID FROM Locations WHERE OwnerID = 4)
GROUP BY p.DestinationID, p.DeliveryDate) AS b
ON b.DeliveryDate = a.DeliveryDate
The result ensures that all dates are present even if the ProductCount is null, however, not every Destination is shown if count is null. Shown below:
DeliveryDate Destination ProductCount
2018-10-04 Null Null
2018-10-05 1 1
2018-10-05 2 1
2018-10-05 3 1
2018-10-06 1 1
2018-10-07 Null Null
I have spent two days stubbornly trying to figure this out with many online SQL resources and scouring StackOverFlow but with no luck.
I recommend creating a numbers table or a calendar table, but you recursive query suffices for small sets.
DECLARE
#startdate DATETIME = '2018-10-04 00:00:00',
#enddate DATETIME = '2018-10-07 00:00:00';
WITH
DateArray AS
(
SELECT #startdate DateVal
UNION ALL
SELECT DateVal + 1 FROM DateArray WHERE DateVal + 1 <= #enddate
)
SELECT
DateArray.DateVal AS DeliveryDate,
Locations.LocationID AS Destination,
COUNT(Products.ProductID) AS ProductCount
FROM
DateArray
CROSS JOIN
(
SELECT * FROM Locations WHERE OwnerID = 4
)
Locations
LEFT JOIN
Products
ON Products.DeliveryDate = DateArray.DateVal
AND Products.DestinationID = Locations.LocationID
GROUP BY
DateArray.DateVal,
Locations.LocationID
Or...
SELECT
DateArray.DateVal AS DeliveryDate,
Locations.LocationID AS Destination,
Products.ProductCount AS ProductCount
FROM
DateArray
CROSS JOIN
(
SELECT * FROM Locations WHERE OwnerID = 4
)
Locations
LEFT JOIN
(
SELECT DeliveryDate, DestinationID, COUNT(*) AS ProductCount
FROM Products
GROUP BY DeliveryDate, DestinationID
)
Products
ON Products.DeliveryDate = DateArray.DateVal
AND Products.DestinationID = Locations.LocationID

calculate multiple total sales of months

My desired output is shown below.I have tried to achieve it like this
*new_card_total = total sales
SELECT DATENAME(MONTH, cl.nc_timestamp) as MonthName,
COUNT(*) as new_card_qty,
ISNULL(sum(cl.nc_deposit),0) as new_card_total
FROM dbfastshosted.dbo.fh_mf_new_card_logs cl
INNER JOIN dbfastshosted.dbo.fh_sales_map m
on cl.nc_log_id = m.nc_log_id
INNER JOIN dbfastshosted.dbo.fh_sales_logs sl
on m.sales_id = sl.sales_id
INNER JOIN dbfastsconfigdataref.dbo.cdf_terminal_user_account h
on cl.created_user_id = h.terminal_user_id
INNER JOIN dbfastsconfigdataref.dbo.cdf_terminal t
on h.terminal_id = t.terminal_id
INNER JOIN dbfastsconfigdataref.dbo.cdf_cuid c
on cl.cu_id = c.cu_id
INNER JOIN dbfastsconfigdataref.dbo.cdf_card_role cr
on cr.id = c.card_role_id
INNER JOIN dbfastshosted.dbo.fh_mf_top_up_logs tl
on tl.tu_log_id = m.tu_log_id
WHERE YEAR(cl.nc_timestamp)= 2017
and cl.currency_id = 1
and (cr_log_id is null or cr_log_id = 0)
and top_up_status = 1
GROUP BY DATENAME(MONTH,cl.nc_timestamp), DATEPART(MONTH, cl.nc_timestamp)
union all
SELECT DATENAME(MONTH, cl.nc_timestamp) as MonthName,
COUNT(*) as new_card_qty,
ISNULL(sum(cl.nc_deposit),0) as new_card_total
FROM dbfastshosted.dbo.fh_mf_new_card_logs cl
INNER JOIN dbfastshosted.dbo.fh_sales_map m
on cl.nc_log_id = m.nc_log_id
INNER JOIN dbfastshosted.dbo.fh_sales_logs sl
on m.sales_id = sl.sales_id
INNER JOIN dbfastsconfigdataref.dbo.cdf_terminal_user_account h
on cl.created_user_id = h.terminal_user_id
INNER JOIN dbfastsconfigdataref.dbo.cdf_terminal t
on h.terminal_id = t.terminal_id
INNER JOIN dbfastsconfigdataref.dbo.cdf_cuid c
on cl.cu_id = c.cu_id
INNER JOIN dbfastsconfigdataref.dbo.cdf_card_role cr
on cr.id = c.card_role_id
INNER JOIN dbfastshosted.dbo.fh_trans tr
on tr.trans_id = m.trans_id
WHERE YEAR(cl.nc_timestamp)= 2017
and cl.currency_id = 1
and (cr_log_id is null or cr_log_id = 0)
GROUP BY DATENAME(MONTH,cl.nc_timestamp), DATEPART(MONTH, cl.nc_timestamp)
with output :
monthname new_card_qty new_card_value
-----------------------------------------------------------
jan 100 1000
feb 200 2000
march 300 3000
march 400 5000
april 500 6000
april 500 8000
and i would like to have output like this:
monthname new_card_qty new_card_total
-----------------------------------------------------------
jan 100 1000
feb 200 2000
march 700 8000
april 1000 13000
I have tried many ways but couldnt make it.Can you please take a look on this? I really need help. Thanks!
It can be achieved by Subquery and group by as below:
SELECT MonthName, SUM(new_card_qty), SUM(new_card_value)
FROM
(SELECT DATENAME(MONTH, cl.nc_timestamp) as MonthName, COUNT(*) as new_card_qty, ISNULL(sum(cl.nc_deposit),0) as new_card_total
FROM dbfastshosted.dbo.fh_mf_new_card_logs cl ........
union all
SELECT DATENAME(MONTH, cl.nc_timestamp) as MonthName, COUNT(*) as new_card_qty, ISNULL(sum(cl.nc_deposit),0) as new_card_total
FROM dbfastshosted.dbo.fh_mf_new_card_logs cl ......) AS A
GROUP BY MonthName

Select SUM by each year in SQL query

I have these two tables:
Expense table
id expense expense_date product_id
1 10 2012-01-03 1
2 10 2014-02-01 2
3 10 2014-02-03 1
4 10 2012-07-03 1
Product table
product_id product_name purchase_date
1 car 2010-02-01
2 bike 2014-03-01
I would like to achieve the result to something like this by summing the expenses grouping by product_id where the the expense_date is between the purchase_date to it's next year:
Year Expense Product_name
1 0 car 2010-02-01 to 2011-02-01
2 10 car 2011-02-01 to 2012-02-01
3 10 car 2012-02-01 to 2013-02-01
4 0 car 2013-02-01 to 2014-02-01
5 10 car 2014-02-01 to 2015-02-01
1 10 bike 2014-03-01 to 2015-03-01
CREATE VIEW dbo.vwMaxExpenseDate
AS
SELECT product_id, MAX(expense_date) AS 'max_expense_date'
FROM Expense
GROUP BY product_id
DECLARE #PossibleYearRange TABLE
(
product_id INT,
YearStart DATETIME,
YearEnd DATETIME
);
WITH CTE
AS
(
SELECT p.product_id, max_expense_date, purchase_date, 1 As Number
FROM Product p
LEFT JOIN vwMaxExpenseDate e
ON p.product_id = e.product_id
UNION ALL
SELECT product_id, max_expense_date, purchase_date, Number + 1
FROM CTE
WHERE Number <= (YEAR(max_expense_date) - YEAR(purchase_date))
)
INSERT INTO #PossibleYearRange
(
product_id,
YearStart,
YearEnd
)
SELECT product_id,
CONVERT(DATETIME, CONVERT(VARCHAR(20), YEAR(purchase_date) + Number - 1) + '-'
+ CONVERT(VARCHAR(20), MONTH(purchase_date)) + '-'
+ CONVERT(VARCHAR(20), DAY(purchase_date))) AS 'YearStart',
CONVERT(DATETIME, CONVERT(VARCHAR(20), YEAR(purchase_date) + Number) + '-'
+ CONVERT(VARCHAR(20), MONTH(purchase_date)) + '-'
+ CONVERT(VARCHAR(20), DAY(purchase_date))) AS 'YearEnd'
FROM CTE
ORDER BY product_id ASC, NUMBER ASC
SELECT MAX(p.product_id) AS product_id, MAX(product_name) AS product_name, YearStart, YearEnd, COALESCE(SUM(expense), 0) AS TotalExpensePerYear
FROM #PossibleYearRange p
LEFT JOIN Expense e
ON p.product_id = e.product_id AND
expense_date BETWEEN YearStart AND YearEnd
INNER JOIN Product d
ON p.product_id = d.product_id
GROUP BY YearStart, YearEnd
ORDER BY MAX(p.product_id) ASC
Hope this helps! Thanks.
declare #BeginsAt as datetime
declare #numOf as int
set #BeginsAt = (select min(purchase_date) from Products)
set #BeginsAt = dateadd(year,datediff(year,0,#BeginsAt),0) -- force to 1st of Year
set #numOf = (year(getdate()) - year(#BeginsAt))+1
;with YearRange (id, StartAt, StopAt)
as (
select 1 as id, #BeginsAt, dateadd(Year,1,#BeginsAt)
union all
select (id + 1) , dateadd(Year,1,StartAt) , dateadd(Year,1,StopAt)
from YearRange
where (id + 1) <= #numOf
)
select
y.id
, coalesce(e.expense,0) expense
, p.product_name
, y.startAt
, dateadd(day,-1,y.StopAt)
from YearRange Y
left join Products P on Y.StopAt between P.purchase_date AND (select max(StopAt) from YearRange)
left join Expenses E on E.expense_date >= Y.StartAt and E.expense_date < Y.StopAt
and E.product_id = P.product_id
See this SQLFiddle demo

SQL Server : calculate monthly total sales incl empty months

I'm trying to calculate the total sales of a product in a month, but I would like it to include any "empty" months (with no sales) and only select the latest 12 months.
This is my code so far.
declare
#ProductNo int
set #ProductNo = 1234
SELECT
YEAR(o.OrderDate) as 'Year', MONTH(o.OrderDate) as 'Month', sum(Amount) as 'Units sold',[ProductNo]
FROM [OrderLine] ol
inner join [Order] o on ol.OrderNo = o.OrderNo
where ProductNo = #ProductNo
Group by ProductNo, YEAR(o.OrderDate), Month(o.OrderDate)
Order by ProductNo, YEAR(o.OrderDate), Month(o.OrderDate)
This returns
Year Month Units sold
2011 6 2
2011 10 1
2011 11 1
2012 2 1
But I would like it to return.
Year Month Units sold
2011 4 0
2011 5 0
2011 6 2
2011 7 0
2011 8 0
2011 9 0
2011 10 1
2011 11 1
2011 12 0
2012 1 0
2012 2 2
2012 3 0
I'm using SQL Server 2008 R2 Sp1
I've done before I know that you have calendar table. I've used master.dbo.spt_values to generate last twelve consecutive months (including current).
declare #ProductNo int
set #ProductNo = 1234
select MONTH(d.date), YEAR(d.date), isnull(t.amnt, 0) as [Units sold] from (
SELECT
YEAR(o.OrderDate) as 'Year',
MONTH(o.OrderDate) as 'Month',
sum(Amount) as amnt,
[ProductNo]
FROM [OrderLine] ol
inner join [Order] o on ol.OrderNo = o.OrderNo
where ProductNo = #ProductNo
group by ProductNo, YEAR(o.OrderDate), Month(o.OrderDate)
) t
right join (
select dateadd(mm, -number, getdate()) as date
from master.dbo.spt_values
where type = 'p' and number < 12
) d on year(d.date) = t.[year] and month(d.date) = t.[month]
order by YEAR(d.date), MONTH(d.date)
Try:
;with CTE as
(select 0 months_ago union all
select months_ago - 1 months_ago from CTE where months_ago > -11),
month_list as
(select dateadd(MONTH,
months_ago,
dateadd(DAY,
1-datepart(DAY,getdate()),
cast(GETDATE() as DATE))) month_start
from cte)
SELECT YEAR(ml.start_date) as 'Year',
MONTH(ml.start_date) as 'Month',
sum(Amount) as 'Units sold',[ProductNo]
FROM month_list ml
left join [Order] o
on o.OrderDate >= ml.start_date and
o.OrderDate < dateadd(MONTH, 1, ml.start_date)
left join [OrderLine] ol
on ol.OrderNo = o.OrderNo and ProductNo = #ProductNo
Group by ProductNo, YEAR(ml.start_date), Month(ml.start_date)
Order by ProductNo, YEAR(ml.start_date), Month(ml.start_date)

SQL - Comparing and Grouping Data on multiple rows

I'm trying to query my database to find which products sold less in October than in either November or December.
I thought something like below would do it but I have a feeling the sub query will be returning the mininimum quantity for the whole database rather than for the specific product.
There must be some way of doing this using GROUP BY but I cant figure it out.
SELECT Category, Product
FROM Sales
WHERE SaleQuantity < (SELECT MIN(SaleQuantity)
FROM Sales
WHERE MonthNumber > 10)
AND MonthNumber = 10
Data looks like:
Category Product MonthNumber SaleQuantity
---------- ----------- ------------- -----------
11 14 10 210
11 14 11 200
11 14 12 390
15 12 10 55
15 12 11 24
17 12 12 129
19 10 10 12
Thanks.
try something like this
SELECT Category,
Product,
SUM( s.SaleQuantity ) AS saleOcotber,
SUM( ISNULL( son.SaleQuantity, 0 ) ) AS saleNovember,
SUM( ISNULL( sod.SaleQuantity, 0 ) ) AS saleDecember
FROM Sales s
LEFT OUTER JOIN Sales son ON son.Category = s.Category
AND son.Product = s.Product
AND son.MonthNumber = 11
LEFT OUTER JOIN Sales sod ON sod.Category = s.Category
AND sod.Product = s.Product
AND sod.MonthNumber = 11
WHERE s.MonthNumber = 10
GROUP BY Category,Product
WHERE SUM( s.SaleQuantity ) < SUM( ISNULL( son.SaleQuantity, 0 ) )
OR SUM( s.SaleQuantity ) < SUM( ISNULL( sod.SaleQuantity, 0 ) )
I have no tested this select but i think it will do the job if there is something not clear
please ask
Best Regards,
Iordan
PS. I presume you are using some version of MSSQL if not try to rewrite it by yourself int SQL you are using
Your table already appears to be summarised by Category, Product and MonthNumber, for SalesQuantity. If so, try this:
select distinct Category, Product
from Sales s11_12
where MonthNumber in (11,12) and
not exists (select null
from Sales s10
where s10.Category = s11_12.Category and
s10.Product = s11_12.Product and
s10.SalesQuantity >= s11_12.SalesQuantity)