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

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!

Related

SQL query - Get records between same day-month period but year

I have a table with records from 2010 until now with a datetime field. Need to find records in same period over the years.
Example of what I need:
Rows that are between two dates(D1 and D2) that came from an input form: 11-15(November 15) and 01-15(January 15, next year)
2021-11-15 to 2022-01-15
2020-11-15 to 2021-01-15
2019-11-15 to 2020-01-15
and so on....
Additional info:
Search dates came from a form so it is not always same. If date period happens to be in same year my query works.
What i have tried:
SELECT * FROM my_table WHERE (DATEPART(MONTH, date_field)>=11
AND DATEPART(DAY, date_field)>=15)
AND (DATEPART(MONTH, date_field)<=1
AND DATEPART(DAY, datefield)<=15)
But obviously it does not work.....
Any ideas ?
You can do it with a tally
with t0(n) as (
select n
from (
values (1),(2),(3),(4),(5),(6),(7),(8),(9),(10)
) t(n)
), tally as(
select top(13) 2009 + row_number() over(order by t1.n) year
from t0 t1, t0 t2, t0 t3
)
select m.*
from tally t
join mytable m on m.date_field >= datefromparts(t.year, 11, 15)
and date_field < datefromparts(t.year + 1, 1, 16);
You need to change your where condition to yield expected records
since you want records within a certain day/month range, The records can fall within either one criterion or other, because of AND the records fail to satisfy the where clause hence nothing is fetched
SELECT * FROM my_table
WHERE ( DATEPART(MONTH, date_field) >=11 AND DATEPART(DAY, date_field)>=15
OR ( DATEPART(MONTH, date_field) <=1 AND DATEPART(DAY, datefield)<=15)
this should do the trick

How To Select Records in a Status Between Timestamps? T-SQL

I have a T-SQL Quotes table and need to be able to count how many quotes were in an open status during past months.
The dates I have to work with are an 'Add_Date' timestamp and an 'Update_Date' timestamp. Once a quote is put into a 'Closed_Status' of '1' it can no longer be updated. Therefore, the 'Update_Date' effectively becomes the Closed_Status timestamp.
I'm stuck because I can't figure out how to select all open quotes that were open in a particular month.
Here's a few example records:
Quote_No Add_Date Update_Date Open_Status Closed_Status
001 01-01-2016 NULL 1 0
002 01-01-2016 3-1-2016 0 1
003 01-01-2016 4-1-2016 0 1
The desired result would be:
Year Month Open_Quote_Count
2016 01 3
2016 02 3
2016 03 2
2016 04 1
I've hit a mental wall on this one, I've tried to do some case when filtering but I just can't seem to figure this puzzle out. Ideally I wouldn't be hard-coding in dates because this spans years and I don't want to maintain this once written.
Thank you in advance for your help.
You are doing this by month. So, three options come to mind:
A list of all months using left join.
A recursive CTE.
A number table.
Let me show the last:
with n as (
select row_number() over (order by (select null)) - 1 as n
from master..spt_values
)
select format(dateadd(month, n.n, q.add_date), 'yyyy-MM') as yyyymm,
count(*) as Open_Quote_Count
from quotes q join
n
on (closed_status = 1 and dateadd(month, n.n, q.add_date) <= q.update_date) or
(closed_status = 0 and dateadd(month, n.n, q.add_date) <= getdate())
group by format(dateadd(month, n.n, q.add_date), 'yyyy-MM')
order by yyyymm;
This does assume that each month has at least one open record. That seems reasonable for this purpose.
You can use datepart to extract parts of a date, so something like:
select datepart(year, add_date) as 'year',
datepart(month, date_date) as 'month',
count(1)
from theTable
where open_status = 1
group by datepart(year, add_date), datepart(month, date_date)
Note: this counts for the starting month and primarily to show the use of datepart.
Updated as misunderstood the initial request.
Consider following test data:
DECLARE #test TABLE
(
Quote_No VARCHAR(3),
Add_Date DATE,
Update_Date DATE,
Open_Status INT,
Closed_Status INT
)
INSERT INTO #test (Quote_No, Add_Date, Update_Date, Open_Status, Closed_Status)
VALUES ('001', '20160101', NULL, 1, 0)
, ('002', '20160101', '20160301', 0, 1)
, ('003', '20160101', '20160401', 0, 1)
Here is a recursive solution, that doesn't rely on system tables BUT also performs poorer. As we are talking about months and year combinations, the number of recursions will not get overhand.
;WITH YearMonths AS
(
SELECT YEAR(MIN(Add_Date)) AS [Year]
, MONTH(MIN(Add_Date)) AS [Month]
, MIN(Add_Date) AS YMDate
FROM #test
UNION ALL
SELECT YEAR(DATEADD(MONTH,1,YMDate))
, MONTH(DATEADD(MONTH,1,YMDate))
, DATEADD(MONTH,1,YMDate)
FROM YearMonths
WHERE YMDate <= SYSDATETIME()
)
SELECT [Year]
, [Month]
, COUNT(*) AS Open_Quote_Count
FROM YearMonths ym
INNER JOIN #test t
ON (
[Year] * 100 + [Month] <= CAST(FORMAT(t.Update_Date, 'yyyyMM') AS INT)
AND t.Closed_Status = 1
)
OR (
[Year] * 100 + [Month] <= CAST(FORMAT(SYSDATETIME(), 'yyyyMM') AS INT)
AND t.Closed_Status = 0
)
GROUP BY [Year], [Month]
ORDER BY [Year], [Month]
Statement is longer, also more readable and lists all year/month combinations to date.
Take a look at Date and Time Data Types and Functions for SQL-Server 2008+
and Recursive Queries Using Common Table Expressions

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

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

Data appear at least once for every month in the last X month

My problem:
Table: trans_detail:
PhoneNo | Datetime
01234 | 2013-01-05 20:40:10
01245 | 2013-04-02 21:00:13
05678 | 2013-04-16 01:24:07
04567 | 2013-07-23 07:00:00
etc | etc
I want to get all phoneNo that appears at least once for every month in the last X month (X month can be any month between 1-12).
For example: get all phone no. that appears at least once for Every Month in the last 3 months.
I am using SQL Server 2005.
Here is a quick query that comes close to what you want:
select PhoneNo
from trans_detail d
where d.datetime >= dateadd(mm, -#X, getdate())
group by PhoneNo
having count(distinct year(datetime)*12+month(datetime)) = #X
The where clause filters the data to only include rows in the last #X months. the having clause checks that each month is in the data, by counting the number of distinct months.
The above version of the query assumes that you mean calendar months. So, it has boundary condition problems. If you run it on June 16th, then it looks back one month and makes sure that the phone number appears at least once since May 16th. I am unclear on whether you want to insist that the number appear twice (once in May and once in June) or if once (once during the time period). The solution to this is to move the current date back to the end of the previous month:
select PhoneNo
from trans_detail d cross join
(select cast(getdate() - day(getdate) + 1 as date) as FirstOfMonth const
where d.datetime >= dateadd(mm, -#X, FirstOfMonth) and
d.datetime < FirstOfMonth
group by PhoneNo
having count(distinct year(datetime)*12+month(datetime)) = #X
Here it is. First two CTEs are to find and prepare last X months, third CTE is to group your data by phones and months. At the end just join the two and return where number of matching rows are equal to number of months.
DECLARE #months INT
SET #Months = 3
;WITH CTE_Dates AS
(
SELECT GETDATE() AS Dt
UNION ALL
SELECT DATEADD(MM,-1,Dt) FROM CTE_Dates
WHERE DATEDIFF(MM, Dt,GETDATE()) < #months-1
)
, CTE_Months AS
(
SELECT MONTH(Dt) AS Mn, YEAR(Dt) AS Yr FROM CTE_Dates
)
, CTE_Trans AS
(
SELECT PhoneNo, MONTH([Datetime]) AS Mn, YEAR([Datetime]) AS Yr FROM dbo.trans_detail
GROUP BY PhoneNo, MONTH([Datetime]), YEAR([Datetime])
)
SELECT PhoneNo FROM CTE_Months m
LEFT JOIN CTE_Trans t ON m.Mn = t.Mn AND m.Yr = t.Yr
GROUP BY PhoneNo
HAVING COUNT(*) = #months
SQLFiddle Demo - with added some more data that will match for last 3 months

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