I'm selecting year, month and net sales from invoice table. The problem is that if there's no data under specific month, there will be no rows for that month. Can you help me? Net sales should be zero if there is not any data.
SELECT
DATEPART(year, date) as 'year',
DATEPART(month, date) as 'month',
SUM(netsales) as netsales
FROM invoice
WHERE
date >= '2015-01-01'
AND date <= '2016-12-31'
GROUP BY
DATEPART(year, date),
DATEPART(month, date)
Thanks in advance.
You need a calendar table and left join
;with calendar as
(
select cast('2015-01-01' as date) as dates -- start date
union all
select dateadd(mm,1,dates) from cte where dates < '2016-12-31' -- end date
)
SELECT
DATEPART(year, c.dates) as 'year',
DATEPART(month, c.dates) as 'month',
SUM(netsales) as netsales
FROM calendar C left join invoice i on c.dates = cast(i.[date] as date)
GROUP BY
DATEPART(year, date),
DATEPART(month, date)
I have generates dates on the fly using Recursive CTE, but I will always suggest to create a calendar table physically and use it in such queries
Related
Perform a monthly distinct beneficiary count between 1/1/2018 -12/31/2020 using Microsoft SQL Server.
Below is my code but I have to change it for every month, is there any way to group by each month from 2018 to 2020 with 2 different date fields?
SELECT COUNT(distinct BEN_ID)
FROM LDS_2017andbeyond
WHERE
[DTE_FIRST_SVC] between '2018-01-01' and '2018-01-31'
AND
[DTE_LAST_SVC] between '2018-01-01' and '2018-01-31'
One simple way to group dates on the same month is the EOMONTH function. It'll return the last day of the month for a date.
SELECT
FORMAT(EOMONTH([DTE_FIRST_SVC]), 'yyyy-MM') AS MONTH_FIRST_SVC
, FORMAT(EOMONTH([DTE_LAST_SVC]), 'yyyy-MM') AS MONTH_LAST_SVC
, COUNT(DISTINCT BEN_ID) AS TOTAL_UNIQUE_BEN_ID
FROM LDS_2017andbeyond
WHERE [DTE_FIRST_SVC] BETWEEN '2018-01-01' AND '2020-12-31'
AND [DTE_LAST_SVC] BETWEEN '2018-01-01' AND '2020-12-31'
GROUP BY EOMONTH([DTE_FIRST_SVC]), EOMONTH([DTE_LAST_SVC])
ORDER BY MONTH_FIRST_SVC DESC, MONTH_LAST_SVC DESC
One solution would be to use a recursive CTE. You start with an anchor query and union it to itself with a DATEADD function in the recursive portion. This solution will give you every month month, even if the count is 0, as opposed to just grouping on the data, which will omit any months that arn't present.
Something like:
WITH CTE_Date AS (
SELECT CAST('01/01/2018' AS DATE) AS GroupMonth -- Start Date. Set as far back as necessary. Can use a DATEADD() to make dynamic.
UNION ALL
SELECT DATEADD(month, 1, GroupMonth) AS GroupMonth
FROM CTE_Date
WHERE DATEADD(month, 1, GroupMonth) < '12/31/2020' -- End Date. Remove the where to go to current.
)
SELECT
COUNT(distinct BEN_ID),
CAST(MONTH(d.GroupMonth) AS VARCHAR(2)) + '-' + CAST(YEAR(d.GroupMonth) AS VARCHAR(4)) AS Dt
FROM
LDS_2017andbeyond lds
LEFT OUTER JOIN CTE_Date d ON
MONTH(lds.[DTE_FIRST_SVC]) = MONTH(d.GroupMonth)
AND
YEAR(lds.[DTE_LAST_SVC]) = YEAR(d.GroupMonth)
GROUP BY
CAST(MONTH(d.GroupMonth) AS VARCHAR(2)) + '-' + CAST(YEAR(d.GroupMonth) AS VARCHAR(4))
I have following code in which it presently gives month wise Total Sales for current year, I need to get total sales from last month of previous year to current month of this year.
My query is as follows:
;WITH mcte AS (
SELECT DATEADD(year, -1, getdate()) as MONTH_NAME
UNION ALL
SELECT DATEADD(MONTH,1,MONTH_NAME)
FROM mcte
WHERE DATEPART(MONTH,MONTH_NAME) < 12),octe AS(
SELECT (DATENAME (MONTH, DATEADD ( MONTH, DATEPART(MONTH, OI.CreatedDate), -1) )) AS MONTH_NAME,
SUM (OI.ItemQty * OI.TotalPrice) AS TOTAL_SALES
FROM Order_Item OI
GROUP BY MONTH(OI.CreatedDate))
SELECT DATENAME(MONTH,m.MONTH_NAME) + '' + DATENAME(YEAR,m.MONTH_NAME) as
MONTH_NAME, o.TOTAL_SALES FROM mcte m LEFT JOIN octe o ON o.MONTH_NAME = DATENAME(MONTH,m.MONTH_NAME)
and I am getting records
MONTH_NAME TOTAL_SALES
July2019 54023.45
August2019 NULL
December2019 NULL
September2019 NULL
October2019 NULL
November2019 NULL
Here I am only getting data for previous year only, not getting data for current year.Can anyone please guide me on this.
Thank you
You are only generating months up to 12. Try replacing the first CTE with:
WITH mcte AS (
SELECT DATEADD(year, -1, getdate()) as MONTH_NAME
UNION ALL
SELECT DATEADD(MONTH,1,MONTH_NAME)
FROM mcte
WHERE month_name < GETDATE()
),
Note the difference is the WHERE clause.
The entire query should look like this:
WITH months AS (
SELECT DATEFROMPARTS(YEAR(getdate()) - 1, MONTH(getdate()), 1) as month
UNION ALL
SELECT DATEADD(MONTH, 1, month)
FROM months
WHERE EOMONTH(month) < GETDATE()
)
SELECT m.month, SUM(OI.ItemQty * OI.TotalPrice) AS TOTAL_SALES
FROM months m LEFT JOIN
Order_Item OI oi
ON oi.CreatedDate >= m.month AND
oi.CreatedDate < DATEAADD(month, 1, m.month)
GROUP BY m.month
Try doing this:
DECLARE #CurDate DATE = GET_DATE()
DECLARE #OneYearPrior DATE = DATEADD(YEAR, -1, #CurDate)
WITH relevant_months(start_date, month_of_sale, year_of_sale) AS (
SELECT
#CurDate AS start_date,
MONTH(#CurDate) as month_of_sale,
YEAR(#CurDate) as year_of_sale
UNION ALL
SELECT DATEADD(MONTH, -1, start_date) AS start_date,
MONTH(DATEADD(MONTH, -1, start_date)) as month_of_sale,
YEAR(DATEADD(MONTH, -1, start_date)) AS year_of_sale
FROM relevant_months
WHERE DATEADD(MONTH, -1, start_date) >= #OneYearPrior
),
relevant_data AS (
SELECT OI.CreatedDate,
OI.ItemQty,
OI.TotalPrice,
MONTH(OI.CreatedDate), AS month_of_sale,
YEAR(OI.CreatedDate) AS year_of_sale
FROM Order_Item OI
WHERE OI.CreatedDate >= DATEADD(YEAR, -1, GETDATE())
)
SELECT rm.month_of_sale as month, rm.year_of_sale as year,
SUM(rd.ItemQty*rd.TotalPrice) as total_sales
FROM relevant_months rm
LEFT JOIN relevant_data rd
ON rm.month_of_sale = rd.month_of_sale
AND rm.year_of_sale = rd.year_of_sale
GROUP BY rm.month_of_sale, rm.year_of_sale
ORDER BY rm.year_of_sale asc, rm.month_of_sale asc
I am trying to implement a SQL query where I need to add up a specific amount of employees hired per week, month, year and display that into a named column. So i.e. Total Hired Per Week, Total Hired Per Month, Total Hired for Year.
Two tables are joined. One is a simple table that lists every single day since 1-1-1900 to 12-31-2099. The other is a simple employee database that lists their Hire Date.
Adding explanations will assist me in learning SQL logic and will be a plus.
I am giving the query below and the output as well in the picture:
.
If I have not explained my issue clearly, please advise and I will try to restate my question.
Query:
DECLARE #STARTDATE DATETIME,
#ENDDATE DATETIME
SET #STARTDATE = '1989-12-31' -- >=
SET #ENDDATE = '2015-10-31 23:59:59' -- <
SELECT c.calendarDate,
Count(e.empid) as Number_Hired,
datepart(week,c.calendarDate) as Week,
datepart(month,c.calendarDate) as Month,
datepart(year,c.calendarDate) as Year
FROM intranet.dbo.igbl_calendar c
LEFT JOIN intranet.dbo.iemp_employee e on CONVERT(DATE, c.calendarDate) = CONVERT(DATE, e.hireDate) and aliasID = 'P'
WHERE c.calendarDate BETWEEN #STARTDATE AND #ENDDATE
GROUP BY c.calendarDate
ORDER BY c.calendarDate
Assuming all you're looking to do is add on 3 additional columns to the current query, you could left join inline views or CTEs that do the individual counts for week, month, year.
e.g.
DECLARE #STARTDATE DATETIME = '1989-12-31'
, #ENDDATE DATETIME = '2015-10-31 23:59:59'
SELECT
c.calendarDate,
Count(e.empid) as Number_Hired,
datepart(week, c.calendarDate) as [Week],
datepart(month, c.calendarDate) as [Month],
datepart(year, c.calendarDate) as [Year],
ew.WeekCount Number_Hired_This_Week,
em.MonthCount Number_Hired_This_Month,
ey.YearCount Number_Hired_This_Year
FROM
intranet.dbo.igbl_calendar c
LEFT JOIN intranet.dbo.iemp_employee e on c.calendarDate = e.hireDate AND aliasID = 'P'
LEFT JOIN (SELECT DATEPART(ww, hireDate) [Week]
, DATEPART(yy, hireDate) [Year]
, COUNT(empid) [WeekCount]
FROM intranet.dbo.iemp_employee
GROUP BY DATEPART(ww, hireDate), DATEPART(yy, hireDate)) ew
ON ew.[Week] = DATEPART(ww, c.calendarDate)
AND ew.[Year] = DATEPART(yy, c.calendarDate)
LEFT JOIN (SELECT DATEPART(mm, hireDate) [Month]
, DATEPART(yy, hireDate) [Year]
, COUNT(empid) [MonthCount]
FROM intranet.dbo.iemp_employee
GROUP BY DATEPART(mm, hireDate), DATEPART(yy, hireDate)) em
ON em.[Month] = DATEPART(mm, c.calendarDate)
AND em.[Year] = DATEPART(yy, c.calendarDate)
LEFT JOIN (SELECT DATEPART(yy, hireDate) [Year]
, COUNT(empid) [YearCount]
FROM intranet.dbo.iemp_employee
GROUP BY DATEPART(yy, hireDate)) ey
ON ey.[Year] = DATEPART(yy, c.calendarDate)
WHERE
c.calendarDate BETWEEN #STARTDATE AND #ENDDATE
GROUP BY
c.calendarDate, ew.WeekCount, em.MonthCount, ey.YearCount
ORDER BY
c.calendarDate
The basic logic being you want to find the total for a year by grouping them by the year (using datepart here), the total for each month by grouping them by year and month, and the total for each week by grouping on year and week. And then joining them back to the original query based on year/month/week.
Though honestly, I'd just do them in separate queries rather than having them all output in the one.
Personally I would minimize using JOIN when solving this kind of problem since the KEY that you need to JOIN together requires further processing, datepart() in this context.
I prefer to use a nested SELECT statement instead.
DECLARE #STARTDATE DATETIME,
#ENDDATE DATETIME
SET #STARTDATE = '1989-12-31' -- >=
SET #ENDDATE = '2015-10-31 23:59:59' -- <
WITH EmpData AS (
SELECT count(*) 'HireCount',
datepart(year, e.hireDate) 'HireYear',
datepart(month, e.hireDate) 'HireMonth',
datepart(week, e.hireDate) 'HireWeek'
FROM intranet.dbo.iemp_employee e
WHERE e.aliasID = 'P'
GROUP BY datepart(year, e.hireDate), datepart(month, e.hireDate), datepart(week, e.hireDate)
)
SELECT
datepart(year, c.calendarDate) AS Year,
datepart(month, c.calendarDate) AS Month,
datepart(week, c.calendarDate) AS Week,
(SELECT sum(HireCount) FROM EmpData WHERE datepart(year, c.calendarDate) = HireYear) 'Year Hire Count',
(SELECT sum(HireCount) FROM EmpData WHERE datepart(year, c.calendarDate) = HireYear AND datepart(month, c.calendarDate) = HireMonth) 'Month Hire Count',
(SELECT sum(HireCount) FROM EmpData WHERE datepart(year, c.calendarDate) = HireYear AND datepart(month, c.calendarDate) = HireMonth AND datepart(week, c.calendarDate) = HireWeek) 'Week Hire Count'
FROM
intranet.dbo.igbl_calendar c
WHERE
c.calendarDate BETWEEN #STARTDATE AND #ENDDATE
GROUP BY
datepart(year, c.calendarDate) as Year, datepart(month, c.calendarDate) as Month, datepart(week, c.calendarDate) as Week
ORDER BY
1,2,3
SELECT dt AS Date
,monthname
,dayname
,(
SELECT COUNT(1)
FROM Calendar
WHERE DATEPART(MM, dt) = DATEPART(MM, c.dt)
AND DATEPART(YEAR, dt) = DATEPART(YEAR, c.dt)
) AS daysInMonth
FROM Calendar AS c
WHERE dt BETWEEN '2000-01-01 00:00:00'
AND '2020-02-01 00:00:00'
the above query is for getting number of days of particular month for a particular date. here iam giving date range and for all the dates between the range iam just showing the days of that month.
The image shows the results for the query and its taking 25secs for ~7500 rows. can someone help me to reduce the time.
Try this one. Here you calculate the total only once instead of 7500 times.
Also create the index for dt field
with monthCount as (
SELECT DATEPART(YEAR, dt) as m_year,
DATEPART(MM, dt) as m_month
COUNT(1) as total
FROM Calendar
GROUP BY
DATEPART(YEAR, dt),
DATEPART(MM, dt)
)
SELECT dt AS Date
,monthname
,dayname
,total
FROM Calendar C
JOIN monthCount M
on DATEPART(YEAR, C.dt) = M.m_year
and DATEPART(MM, C.dt) = M.m_month
WHERE C.dt BETWEEN '2000-01-01 00:00:00'
AND '2020-02-01 00:00:00'
I am trying to figure out how to compare the current day's data to the same data from a week ago, 2 weeks, etc. Let's say I have a table called "Order" with 2 columns:
Order table
-----------
OrderID int identity
OrderDate datetime
If today, is Monday, I would like to be able to compare the number of orders from today to the previous Mondays for an entire year. Is this possible with a single SQL Server query? I'm using SQL 2008 if it makes a difference.
select CAST (OrderDate as date) as [Date], COUNT(*)
from Orders
where OrderDate > DATEADD(YEAR,-1, getdate())
and DATEPART(DW,OrderDate ) = DATEPART(DW,GETDATE())
group by CAST (OrderDate as date)
Try
SELECT [ColumnsYouWant]
FROM [OrderTable]
WHERE datepart(weekday, OrderDate) = datepart(weekday, getdate())
AND OrderDate >= dateadd(yyyy, -1, getdate())
This gives you Monday order counts by week number:
select year(OrderDate) as Year,
DATEPART(WEEK, OrderDate) as Week,
COUNT(*) as MondayOrderCount
from Order
where DATEPART(WEEKDAY, OrderDate) = 2
group by year(OrderDate), DATEPART(WEEK, OrderDate)
order by Year, Week