T SQL Datepart and Count - Show also weeks with zero count - sql

I have following SQL statement:
declare #dateFrom datetime = '2015-01-01';
declare #dateTo datetime = '2015-12-31';
select
DATEPART(WEEK, OrderDate) Week, Count(*) Number
from
table
where
OrderDate between #dateFrom and #dateTo
group by
DATEPART(WEEK, OrderDate)
order by
Week
It returns the number of orders per week, but if there were no orders at all this respective week is omitted.
How can I change the statement so it will also include weeks with 0 orders?

Gofr1 was on the right track but there are issues with the query.
1 - You do not want to use the datediff() of the begin and end as the stopping condition. It works for a whole year but will not work for partial ranges.
2 - I would add year to the key since that will allow you to handle cross year cases.
3 - You need to roll up the sales before using the Year Week Common Table Expression. Otherwise you just toss out the nulls again (order dates) with the WHERE clause.
Remember, logically the join is applied then the where clause.
The code below uses the Adventure Works 2012 DW database and obtains the correct answer.
Uses a tally table for some numbers.
Generates weekly dates and calculates year/week key for given range.
Rolls up sales from the fact table for given range.
Left joins the keys to the sales and turns null totals to zero.
Code:
-- Declare start and end date
DECLARE #dte_From datetime = '2005-07-01';
DECLARE #dte_To datetime = '2007-12-31';
-- About 200K numbers
WITH cte_Tally (n) as
(
SELECT ROW_NUMBER() OVER (ORDER BY (SELECT NULL))
FROM sys.all_views a
CROSS JOIN sys.all_views b
),
-- Create year/week key
cte_YearWeekKey (MyKey) as
(
SELECT
year(dateadd(week, t.n, #dte_from)) * 1000 +
datepart(week, dateadd(week, t.n, #dte_from)) as MyKey
FROM
cte_Tally as t
WHERE
dateadd(week, t.n, #dte_from) < #dte_To
),
-- Must roll up here
cte_Sales (MyKey, MyTotal) as
(
SELECT
YEAR(F.OrderDate) * 1000 +
DATEPART(WEEK, F.OrderDate) as MyKey,
COUNT(*) as MyTotal
FROM
[AdventureWorksDW2012].[dbo].[FactResellerSales] F
WHERE
F.OrderDate between #dte_From and #dte_To
GROUP BY
YEAR(F.OrderDate) * 1000 +
DATEPART(WEEK, F.OrderDate)
)
-- Join the results
SELECT
K.MyKey, ISNULL(S.MyTotal, 0) as Total
FROM
cte_YearWeekKey as K
LEFT JOIN
cte_Sales as S
ON
k.MyKey = S.MyKey

Related

How to generate temp table with integers 1-52 for weeks of year to join on?

I need to create a temporary table (I think) that contains a single WeekID field with values 1 through 52, indicating each week of the calendar year. I want to be able to left join against this table on the week number based on some data I have to indicate totals for each week of the year.
Preferably would like to do this in a single query.
What I have been using outputs the last 5 weeks in which records exist, as opposed to the actual last 5 weeks, in which totals may be 0.
Here is my errant query that gives me last 5 weeks totals where tickets actually got opened:
SET DATEFIRST 1
SELECT TOP 5 * FROM
(SELECT TOP 5
DATEPART(year, t.TicketQueuedDateTime) AS 'TicketYear',
DATEPART(week, t.TicketQueuedDateTime) AS 'TicketWeek',
COUNT(t.TicketStatus) AS 'WeekTotal'
FROM TicketTable t
GROUP BY DATEPART(year, t.TicketQueuedDateTime), DATEPART(week, t.TicketQueuedDateTime)
ORDER BY TicketYear DESC, TicketWeek DESC) val
ORDER BY val.TicketYear, val.TicketWeek
Current output:
TicketYear TicketWeek WeekTotal
2018 25 13
2018 26 10
2018 27 4
2018 29 2
2018 32 1
This works great; however, I want to show the actual totals for the actual last 5 weeks, even if there hasn't been any tickets (a "0" output should be filled in where there are "gap" weeks with no tickets as well).
Expected output (assuming for sake of this post that we're in week 33 and there have been no tickets this week:
TicketYear TicketWeek WeekTotal
2018 29 2
2018 30 0
2018 31 0
2018 32 1
2018 33 0
(note: weeks with no tickets gaps are filled with "0" value, and reflects the actual last 5 weeks including current week)
MSSQL 2016 Enterprise Edition
Without creating temporary table, you can simplify this query using CTE, like below.
- Use recursive CTE to generate week numbers
- Get distinct years from TicketTable
- Cross join distinct years and weeks to get all combinations
- Then left join it with TicketTable to get count for each year-week
;With WEEK_CTE as (
Select 1 as WeekNo
UNION ALL
SELECT 1 + WeekNo from WEEK_CTE
WHERE WeekNo < 52
)
Select yr.Year AS 'TicketYear'
, wk.WeekNo AS 'TicketWeek'
, COUNT(t.TicketStatus) AS 'WeekTotal'
from Week_CTE wk
cross join (select distinct year(TicketQueuedDateTime) as [Year] from TicketTable) yr
left join TicketTable t on wk.WeekNo = DATEPART(WEEK, t.TicketQueuedDateTime) and yr.Year = YEAR(t.TicketQueuedDateTime)
group by yr.Year, wk.WeekNo
You could generate such a table in a number of ways. If you don't already have a tally table in your database (i.e. a table with sequential integers in it), I'd suggest creating one, as their usefulness is endless. Regardless, you can create one on the fly using row_number(). Then just subtract the integer value you generated from the current date in weeks, selecting the top 52 of em. Strip out the year and week, and you my friend, have got yourself the query to populate your join table.
-- Creating a numbers table
if object_id('tempdb.dbo.#Numbers') is not null drop table #Numbers
create table #Numbers
(
num int primary key clustered
)
-- Populating it with some numbers
insert into #Numbers (num)
select row_number() over (order by (select null)) - 1
from sys.all_objects
select top 52
WeeksAgo = num,
TicketYear = year(dateadd(week, -num, getdate())),
TicketWeek = datepart(week, dateadd(week, -num, getdate()))
from #Numbers
I reused #Xedni's query and came up with the query below:
if object_id('tempdb.dbo.#Numbers') is not null drop table #Numbers
create table #Numbers
(
num int primary key clustered
)
-- Populating it with some numbers
insert into #Numbers (num)
select row_number() over (order by (select null)) - 1
from sys.all_objects
select TicketYear = year(dateadd(week, -num, getdate())),
TicketWeek = datepart(week, dateadd(week, -num, getdate()))
from #Numbers
SELECT TOP 5 * FROM
(SELECT TOP 5
DATEPART(year, t.TicketQueuedDateTime) AS 'TicketYear',
DATEPART(week, t.TicketQueuedDateTime) AS 'TicketWeek',
COUNT(t.TicketStatus) AS 'WeekTotal'
FROM #Numbers as n
LEFT OUTER JOIN TicketTable as t ON year(dateadd(week, -n.num, getdate())) = t.DATEPART(year, t.TicketQueuedDateTime) AND datepart(week, dateadd(week, -n.num, getdate())) = DATEPART(week, t.TicketQueuedDateTime)
GROUP BY DATEPART(year, t.TicketQueuedDateTime), DATEPART(week, t.TicketQueuedDateTime)
ORDER BY TicketYear DESC, TicketWeek DESC) val
ORDER BY val.TicketYear, val.TicketWeek
PS: I was not able to test this and if you're looking for performance, this is probably not the best query to use. But try this out, if it works for you, we can work on improving the performance.
Cheers!

adding a row for missing data

Between a date range 2017-02-01 - 2017-02-10, i'm calculating a running balance.
I have days where we have missing data, how would I include these missing dates with the previous days balance ?
Example data:
we are missing data for 2017-02-04,2017-02-05 and 2017-02-06, how would i add a row in the query with the previous balance?
The date range is a parameter, so could change....
Can i use something like the lag function?
I would be inclined to use a recursive CTE and then fill in the values. Here is one approach using outer apply:
with dates as (
select mind as dte, mind, maxd
from (select min(date) as mind, max(date) as maxd from t) t
union all
select dateadd(day, 1, dte), mind, maxd
from dates
where dte < maxd
)
select d.dte, t.balance
from dates d outer apply
(select top 1 t.*
from t
where t.date <= d.dte
order by t.date desc
) t;
You can generate dates using tally table as below:
Declare #d1 date ='2017-02-01'
Declare #d2 date ='2017-02-10'
;with cte_dates as (
Select top (datediff(D, #d1, #d2)+1) Dates = Dateadd(day, Row_Number() over (order by (Select NULL))-1, #d1) from
master..spt_values s1, master..spt_values s2
)
Select * from cte_dates left join ....
And do left join to your table and get running total
Adding to the date range & CTE solutions, I have created Date Dimension tables in numerous databases where I just left join to them.
There are free scripts online to create date dimension tables for SQL Server. I highly recommend them. Plus, it makes aggregation by other time periods much more efficient (e.g. Quarter, Months, Year, etc....)

Query to check number of records created in a month.

My table creates a new record with timestamp daily when an integration is successful. I am trying to create a query that would check (preferably automated) the number of days in a month vs number of records in the table within a time frame.
For example, January has 31 days, so i would like to know how many days in january my process was not successful. If the number of records is less than 31, than i know the job failed 31 - x times.
I tried the following but was not getting very far:
SELECT COUNT (DISTINCT CompleteDate)
FROM table
WHERE CompleteDate BETWEEN '01/01/2015' AND '01/31/2015'
Every 7 days the system executes the job twice, so i get two records on the same day, but i am trying to determine the number of days that nothing happened (failures), so i assume some truncation of the date field is needed?!
One way to do this is to use a calendar/date table as the main source of dates in the range and left join with that and count the number of null values.
In absence of a proper date table you can generate a range of dates using a number sequence like the one found in the master..spt_values table:
select count(*) failed
from (
select dateadd(day, number, '2015-01-01') date
from master..spt_values where type='P' and number < 365
) a
left join your_table b on a.date = b.CompleteDate
where b.CompleteDate is null
and a.date BETWEEN '01/01/2015' AND '01/31/2015'
Sample SQL Fiddle (with count grouped by month)
Assuming you have an Integers table*. This query will pull all dates where no record is found in the target table:
declare #StartDate datetime = '01/01/2013',
#EndDate datetime = '12/31/2013'
;with d as (
select *, date = dateadd(d, i - 1 , #StartDate)
from dbo.Integers
where i <= datediff(d, #StartDate, #EndDate) + 1
)
select d.date
from d
where not exists (
select 1 from <target> t
where DATEADD(dd, DATEDIFF(dd, 0, t.<timestamp>), 0) = DATEADD(dd, DATEDIFF(dd, 0, d.date), 0)
)
Between is not safe here
SELECT 31 - count(distinct(convert(date, CompleteDate)))
FROM table
WHERE CompleteDate >= '01/01/2015' AND CompleteDate < '02/01/2015'
You can use the following query:
SELECT DATEDIFF(day, t.d, dateadd(month, 1, t.d)) - COUNT(DISTINCT CompleteDate)
FROM mytable
CROSS APPLY (SELECT CAST(YEAR(CompleteDate) AS VARCHAR(4)) +
RIGHT('0' + CAST(MONTH(CompleteDate) AS VARCHAR(2)), 2) +
'01') t(d)
GROUP BY t.d
SQL Fiddle Demo
Explanation:
The value CROSS APPLY-ied, i.e. t.d, is the ANSI string of the first day of the month of CompleteDate, e.g. '20150101' for 12/01/2015, or 18/01/2015.
DATEDIFF uses the above mentioned value, i.e. t.d, in order to calculate the number of days of the month that CompleteDate belongs to.
GROUP BY essentially groups by (Year, Month), hence COUNT(DISTINCT CompleteDate) returns the number of distinct records per month.
The values returned by the query are the differences of [2] - 1, i.e. the number of failures per month, for each (Year, Month) of your initial data.
If you want to query a specific Year, Month then just simply add a WHERE clause to the above:
WHERE YEAR(CompleteDate) = 2015 AND MONTH(CompleteDate) = 1

Pad out an SQL table with data for Graphing Purposes

SQL Server 2005
I have an SQL Function (ftn_GetExampleTable) which returns a table with multiple result rows
EXAMPLE
ID MemberID MemberGroupID Result1 Result2 Result3 Year Week
1 1 1 High Risk 2 xx 2011 22
2 11 4 Low Risk 1 yy 2011 21
3 12 5 Med Risk 3 zz 2011 25
etc.
Now I do a count and group by on a table above this for Result 2 for instance so I get
SELECT MemberGroupID, Result2, Count(*) AS ExampleCount, Year, Week
FROM ftn_GetExampleTable
GROUP BY MemberGroupID, Result2, Year, Week
MemberGroupID Result2 ExampleCount Year Week
1 2 4 2011 22
4 1 2 2011 21
5 3 1 2011 25
Now imagine when I go to graph this new table between Weeks 20 and 23 of Year 2011, you'll see that it won't graph 20 or 23 or certain groups or even certain results in this example as they are not in the included data, so I need "false data" inserted into this table which has all the possibilities so they at least show on a graph even if the count is 0, does this make sense?
I am wondering on the easiest and kind of most dynamic way as it could be Result1 or Result3 I want to Graph on (different column types).
Thanks in advance
It looks like your dimensions are: MemberGroupID,Result2, and week (Year,Week).
One approach to solving this is to generate a list of all values you want for all the dimensions, and produce a cartesian product of them. As an example,
SELECT m.MemberGroupID, n.Result2, w.Year, w.Week
FROM (SELECT MemberGroupID FROM ftn_GetExampleTable GROUP BY MemberGroupID) m
CROSS
JOIN (SELECT Result2 FROM ftn_GetExampleTable GROUP BY Result2 ) n
CROSS
JOIN (SELECT Year, Week FROM myCalendar WHERE ... ) w
You don't necessarily need a table named myCalendar. (That approach does seem to be the popular one.) You just need a row source from which you can derive a list of (Year, Week) tuples. (There are answers to the question elsewhere in Stackoverflow, how to generate a list of dates.)
And the list of MemberGroupID and Result2 values doesn't have to come from the ftn_GetExampleTable rowsource, you could substitute another query.
With a cartesian product of those dimensions, you've got a complete "grid". Now you can LEFT JOIN your original result set to that.
Any place you don't have a matching row from the "gappy" result query, you'll get a NULL returned. You can leave the NULL, or replace it with a 0, which is probably what you want if it's a "count" you are returning.
SELECT d.MemberGroupID
, d.Result2
, d.Year
, d.Week
, IFNULL(r.ExampleCount,0) as ExampleCount
FROM ( <dimension query from above> ) d
LEFT
JOIN ( <original ExampleCount query> ) r
ON r.MemberGroupID = d.MemberGroupID
AND r.Result2 = d.Result2
AND r.Year = d.Year
AND r.Week = d.Week
That query can be refactored to make use of Common Table Expressions, which makes the query a little easier to read, especially if you are including multiple measures.
; WITH d AS ( /* <dimension query with no gaps (example above)> */
)
, r AS ( /* <original query with gaps> */
SELECT MemberGroupID, Result2, Count(*) AS ExampleCount, Year, Week
FROM ftn_GetExampleTable
GROUP BY MemberGroupID, Result2, Year, Week
)
SELECT d.*
, IFNULL(r.ExampleCount,0)
FROM d
LEFT
JOIN r
ON r.Year=d.Year AND r.Week=d.Week AND r.MemberGroupID = d.MemberGroupID
AND r.Result2 = d.Result2
This isn't a complete working solution to your problem, but it outlines an approach you can use.
Whenever I need to generate a sequence within SQL-Server I use the sys.all_objects table along with the ROW_NUMBER function, then maninpulate it as required:
SELECT ROW_NUMBER() OVER(ORDER BY Object_ID) AS Sequence
FROM Sys.All_Objects
So for the list of year and week numbers I would use:
DECLARE #StartDate DATETIME,
#EndDate DATETIME
SET #StartDate = '20110101'
SET #EndDate = '20120601'
SELECT DATEPART(YEAR, Date) AS YEAR,
DATEPART(WEEK, Date) AS WeekNum
FROM ( SELECT DATEADD(WEEK, ROW_NUMBER() OVER(ORDER BY Object_ID) - 1, #StartDate) AS Date
FROM Sys.All_Objects
) Dates
WHERE Date < #endDate
Where the dates subquery provides a list of dates at one week intervals between your start and end dates.
So in your example the end result would be something like:
DECLARE #StartDate DATETIME,
#EndDate DATETIME
SET #StartDate = '20110101'
SET #EndDate = '20120601'
;WITH Data AS
( SELECT MemberGroupID,
Result2,
Count(*) AS ExampleCount,
Year,
Week
FROM ftn_GetExampleTable
GROUP BY MemberGroupID, Result2, Year, Week
), Dates AS
( SELECT DATEPART(YEAR, Date) AS YEAR,
DATEPART(WEEK, Date) AS WeekNum
FROM ( SELECT DATEADD(WEEK, ROW_NUMBER() OVER(ORDER BY Object_ID) - 1, #StartDate) AS Date
FROM Sys.All_Objects
) Dates
WHERE Date < #endDate
)
SELECT YearNum,
WeeNum,
MemberID,
Result2,
COALESCE(ExampleCount, 0) AS ExampleCount
FROM Dates
LEFT JOIN Data
ON YearNum = Data.Year
AND WeekNum = Data.Week

Sql to select row from each day of a month

I have a table which store records of all dates of a month. I want to retrieve some data from it. The table is so large that I should only selecting a fews of them. If the records have a column "ric_date" which is a date, how can I select records from each of the dates in a month, while selecting only a fews from each date?
The table is so large that the records for 1 date can have 100000 records.
WITH T AS (
SELECT ric_date
FROM yourTable
WHERE rice_date BETWEEN #start_date AND #end_date -- thanks Aaron Bertrand
GROUP BY ric_date
)
SELECT CA.*
FROM T
CROSS APPLY (
SELECT TOP 500 * -- 'a fews'
FROM yourTable AS YT
WHERE YT.ric_date = T.ric_date
ORDER BY someAttribute -- not required, but useful
) AS CA
Rough idea. This will get the first three rows per day for the current month (or as many that exist for any given day - there may be days with no rows represented).
DECLARE
#manys INT = 3,
#month DATE = DATEADD(DAY, 1-DAY(GETDATE()), DATEDIFF(DAY, 0, GETDATE()));
;WITH x AS
(
SELECT some_column, ric_date, rn = ROW_NUMBER() OVER
(PARTITION BY ric_date ORDER BY ric_date)
FROM dbo.data
WHERE ric_date >= #month
AND ric_date < DATEADD(MONTH, 1, #month)
)
SELECT some_column, ric_date FROM x
WHERE rn <= #manys;
If you don't have supporting indexes (most importantly on ric_date), this won't necessarily scale well at the high end.