generate sql temp table of sequential dates to left outer join to - sql

i have a table of data that i want to select out via stored proc such that users can connect a MS excel front end to it and use the raw data as a source to graph.
The problem with the raw data of the table is there exist gaps in the dates because if there is no data for a given day (there is no records with that date) then when users try to graph it it creates problems.
I want too update my stored proc to left outer join to a temp table of dates so that the right side will come in as nulls that i can cast to zero's for them to have a simple plotting experience.
how do i best generate a one field table of dates between a start and end date?

In SQL Server 2005 and up, you can use something like this (a Common Table Expression CTE) to do this:
DECLARE #DateFrom DATETIME
SET #DateFrom = '2011-01-01'
DECLARE #DateTo DATETIME
SET #DateTo = '2011-01-10'
;WITH DateRanges AS
(
SELECT #DateFrom AS 'DateValue'
UNION ALL
SELECT DATEADD(DAY, 1, DateValue)
FROM DateRanges
WHERE DateValue < #DateTo
)
SELECT * FROM DateRanges
You could LEFT OUTER JOIN this CTE against your table and return the result.

Another way to do it is with a memory table. It won't choke due to recursion limitations like some of the above solutions.
DECLARE #dates AS TABLE ([Date] date);
DECLARE #date date = {d '2010-10-01'};
DECLARE #endDate date = {d '2010-11-01'};
while (#date < #endDate)
BEGIN
INSERT INTO #dates VALUES (#date);
SET #date = dateadd(DAY, 1, #date)
END
SELECT * FROM #dates;
SQL Fiddle

One way would be with a CTE:
with cte_dates as (
select cast('20110119' as datetime) as [date]
union all
select dateadd(dd, 1, [date])
from cte_dates
where dateadd(dd, 1, [date]) <= '20111231'
)
select [date], YourColumn
from cte_dates
left join YourTable
on ...
option (maxrecursion 0);

Related

Add a record for every Week-Day of the year to a blank table

I have a blank table that has two columns [ID] and [MyDate].
I would like to populate that table with all of the days of the current year MINUS weekends.
Is there a way to do this with a SQL query?
In this case I am using MSSQL T-SQL
I do not have any example code, as I am at a loss on where to get started for this scenario.
Using a numbers (Tally) table helps you to avoid using loops.
If you don't already have a numbers table, you can use this script to create it:
SELECT TOP 10000 IDENTITY(int,0,1) AS Number
INTO Tally
FROM sys.objects s1
CROSS JOIN sys.objects s2
ALTER TABLE Tally ADD CONSTRAINT PK_NumbersTest PRIMARY KEY CLUSTERED (Number)
For more information about the creation of a numbers table, read this SO post.
Now that you have a numbers table, you can use a cte to generate the dates you want. I've used DATEFROMPARTS and GETDATE() to get Jauary 1st of the current year, if you are using a version of sql server below 2012 you need to use other methods for that:
DECLARE #StartDate Date,
#EndDate Date
SELECT #StartDate = DATEFROMPARTS(YEAR(GetDate()), 1, 1)
SELECT #EndDate = DATEADD(Year, 1, #StartDate)
Now, create a CTE to get the dates required using the numbers table, and insert the records from the cte to the table:
;WITH CTE AS
(
SELECT DATEADD(Day, Number, #StartDate) As TheDate
FROM Tally
WHERE DATEADD(Day, Number, #StartDate) < #EndDate
)
INSERT INTO WeekDays
SELECT TheDate
FROM CTE
WHERE DATEPART(WeekDay, TheDate) BETWEEN 2 AND 6
See a live demo on rextester.
This will do it. Here the 1 and the 7 represents Sunday and Saturday
CREATE TABLE T (
ID INT NOT NULL IDENTITY(1,1),
MyDate DATE NOT NULL)
DECLARE #Start DATE
DECLARE #End DATE
SET #Start = '20170101'
SET #End = '20171231'
WHILE #Start <= #End
BEGIN
IF (DATEPART(DW, #Start) NOT IN (1,7))
BEGIN
INSERT INTO T (MyDate) VALUES (#Start)
END
SET #Start = DATEADD(DAY, 1, #Start)
END
SELECT * FROM T
Here's my quick attempt at your problem. Just use your table instead
select
CAST('2017-03-15' as datetime) as datestuff
into #test
Delete from #test
DECLARE #Y datetime = CAST('2017-12-31' AS DATE)
while #y != '2017-01-01'
begin
if DATENAME(DW, #y) not IN ('SUNDAY', 'SATURDAY')
BEGIN
INSERT INTO #test
SELECT #y
END
SET #Y = DATEADD(DD, -1, #Y)
end
select * from #test

Use a WITH in an SQL function?

I'm trying to make a function but I do not know how to get the WITH in it. Here is my code.
CREATE FUNCTION CubicVolume (#StartDate date, #EndDate date) RETURNS #TableDays TABLE
(Days int)
AS
BEGIN
INSERT #TableDays
WITH Dates AS (
SELECT #StartDate AS DayInQuestion
UNION ALL
SELECT DATEADD(Day, 1, DayInQuestion) AS DayInQuestion
FROM Dates AS Dates
WHERE (DayInQuestion < #EndDate)
)
SELECT DISTINCT count(Dates.DayInQuestion)
FROM Dates AS Dates LEFT OUTER JOIN
HEATHrs ON Dates.DayInQuestion = HEATHrs.StartDate
WHERE (CAST(DATEPART(weekday, Dates.DayInQuestion) AS int) BETWEEN 2 AND 6)
RETURN
END
You have to put the Common Table Expression (CTE) before the INSERT:
WITH Dates AS
(
SELECT #StartDate AS DayInQuestion
UNION ALL
SELECT DATEADD(Day, 1, DayInQuestion) AS DayInQuestion
FROM Dates AS Dates
WHERE (DayInQuestion < #EndDate)
)
INSERT #TableDays
SELECT DISTINCT count(Dates.DayInQuestion)
FROM Dates AS Dates LEFT OUTER JOIN
HEATHrs ON Dates.DayInQuestion = HEATHrs.StartDate
WHERE (CAST(DATEPART(weekday, Dates.DayInQuestion) AS int) BETWEEN 2 AND 6)

How to sort varchar string properly with numeric values on both ends?

I'm building a Common Table Expression (CTE) in SQL Server 2008 to use in a PIVOT query.
I'm having difficulty sorting the output properly because there are numeric values that sandwich the string data in the middle. Is it possible to do this?
This is a quick and dirty example, the real query will span several years worth of values.
Example:
Declare #startdate as varchar(max);
Declare #enddate as varchar(max);
Set #startdate = cast((DATEPART(yyyy, GetDate())-1) as varchar(4))+'-12-01';
Set #enddate = cast((DATEPART(yyyy, GetDate())) as varchar(4))+'-03-15';
WITH DateRange(dt) AS
(
SELECT CONVERT(datetime, #startdate) dt
UNION ALL
SELECT DATEADD(dd,1,dt) dt FROM DateRange WHERE dt < CONVERT(datetime, #enddate)
)
SELECT DISTINCT ',' + QUOTENAME((cast(DATEPART(yyyy, dt) as varchar(4)))+'-Week'+(cast(DATEPART(ww, dt) as varchar(2)))) FROM DateRange
Current Output:
,[2012-Week48]
,[2012-Week49]
,[2012-Week50]
,[2012-Week51]
,[2012-Week52]
,[2012-Week53]
,[2013-Week1]
,[2013-Week10]
,[2013-Week11]
,[2013-Week2]
,[2013-Week3]
,[2013-Week4]
,[2013-Week5]
,[2013-Week6]
,[2013-Week7]
,[2013-Week8]
,[2013-Week9]
Desired Output:
,[2012-Week48]
,[2012-Week49]
,[2012-Week50]
,[2012-Week51]
,[2012-Week52]
,[2012-Week53]
,[2013-Week1]
,[2013-Week2]
,[2013-Week3]
,[2013-Week4]
,[2013-Week5]
,[2013-Week6]
,[2013-Week7]
,[2013-Week8]
,[2013-Week9]
,[2013-Week10]
,[2013-Week11]
EDIT
Of course after I post the question my brain started working. I changed the DATEADD to add 1 week instead of 1 day and then took out the DISTINCT in the select and it worked.
DECLARE #startdate AS VARCHAR(MAX);
DECLARE #enddate AS VARCHAR(MAX);
SET #startdate = CAST((DATEPART(yyyy, GetDate())-1) AS VARCHAR(4))+'-12-01';
SET #enddate = CAST((DATEPART(yyyy, GetDate())) AS VARCHAR(4))+'-03-15';
WITH DateRange(dt) AS
(
SELECT CONVERT(datetime, #startdate) dt
UNION ALL
SELECT DATEADD(ww,1,dt) dt FROM DateRange WHERE dt < CONVERT(datetime, #enddate)
)
SELECT ',' + QUOTENAME((CAST(DATEPART(yyyy, dt) AS VARCHAR(4)))+'-Week'+(CAST(DATEPART(ww, dt) AS VARCHAR(2)))) FROM DateRange
I can't see the sample SQL code (that site is blacklisted where I am).
Here is a trick for sorting that data in the proper order is to use the length first and then the values:
select col
from t
order by left(col, 6), len(col), col;
Have you considered to sort on two temporary columns (year in smallint and week in tinyint to save space … or directly using the datepart integer if space is not a problem to you and you prefer fast run) along with the use of "order by year, week" ?
If you store dates using a more suitable type (what I suggest), it would then become :
WITH [Define the CTE expression name and column list]
AS
(
SELECT CAST(DATEPART(yyyy, dt) as smallint(4)) year, cast(DATEPART(ww, dt) as tinyint(2)) week, [your columns here]
FROM DateRange WHERE dt < #enddate
)
[Define the outer query referencing the CTE name]
ORDER BY year, week;
GO
Also, please note that string operations will slow your queries so avoid them when possible !
I like Gordon's answer, but if you were hell-bent on text manipulation in your order by:
ORDER BY CAST(REPLACE(LEFT('[2012-Week48]',5),'[','')AS INT)
,CAST(REPLACE(RIGHT('[2012-Week48]',CHARINDEX('Week','[2012-Week48]')-4),']','') AS INT)
Here is another option converting the beginning and ending parts of the column to integer.
SELECT *
FROM YourTable
ORDER BY CAST(SUBSTRING(yourcolumn,1,4) as int),
CAST(SUBSTRING(yourcolumn,CHARINDEX('Week',yourcolumn)+4,len(yourcolumn)) as int)
SQL Fiddle Demo
This will work assuming the format of the data is always the same.
Since you are using dt to generate the string, you should sort by using the date's parts:
WITH DateRange(dt) ...
SELECT DISTINCT ',' + QUOTENAM...
ORDER BY DATEPART(yyyy, dt), DATEPART(ww, dt)
I needed to change the DATEADD portion of the query and remove the DISTINCT. Once changed the order sorted properly on it's own
DECLARE #startdate AS VARCHAR(MAX);
DECLARE #enddate AS VARCHAR(MAX);
SET #startdate = CAST((DATEPART(yyyy, GetDate())-1) AS VARCHAR(4))+'-12-01';
SET #enddate = CAST((DATEPART(yyyy, GetDate())) AS VARCHAR(4))+'-03-15';
WITH DateRange(dt) AS
(
SELECT CONVERT(datetime, #startdate) dt
UNION ALL
SELECT DATEADD(ww,1,dt) dt FROM DateRange WHERE dt < CONVERT(datetime, #enddate)
)
SELECT ',' + QUOTENAME((CAST(DATEPART(yyyy, dt) AS VARCHAR(4)))+'-Week'+(CAST(DATEPART(ww, dt) AS VARCHAR(2)))) FROM DateRange

SQL: How to create a temp table and fill it with date within a select-from statement

I wish to create a temp table with 1 datetime column and then fill it with date(30 days before today). I wish to do all these in a select-from statement.
I could do it with a "WITH" loop as below prior to the select-from statement. However, I wish to do it within a select-from statement.
declare #endDate datetime
set #endDate = dateadd(day,-30,getdate())
with CTE_Table (
Select dataDate = dateadd(day,-1,getdate()) from CTE_Table
where datediff(day,dataDate,#endDate) < 0
)
select * from CTE_Table
Please help... :....(
You can use SELECT ... INTO.
BTW Your recursive CTE is invalid. A fixed version is below
DECLARE #endDate DATETIME
SET #endDate = dateadd(day, -30, getdate());
WITH CTE_Table(dataDate)
AS (SELECT dateadd(day, -1, getdate())
UNION ALL
SELECT dateadd(day, -1, dataDate)
FROM CTE_Table
WHERE datediff(day, dataDate, #endDate) < 0)
SELECT dataDate
INTO #T
FROM CTE_Table
You could do:
CREATE TABLE #temptable
(
DateColumn DATETIME
)
INSERT INTO #temptable
SELECT dataDate FROM CTE_Table

Selecting all dates from a table within a date range and including 1 row per empty date

I am trying to refactor some code in an ASP.Net website and having a problem with a stored procedure I am writing.
What I want to do is get a date range, then select all data within that range from a table BUT if a date is not present I need to still select a row.
My idea for this as you can see in the code below is to create a temporary table, and populate it with all the dates within my date range, then join this onto the table I am selecting from however this does not work. Am I doing something wrong here? The tempDate column is always null in this join however I have checked the table and it deffinately has data in it.
-- Parameters
DECLARE #DutyDate datetime='2012-01-01 00:00:00'
DECLARE #InstructorID nvarchar(2) = N'29'
DECLARE #datesTBL TABLE (tempDate DATETIME)
-- Variables
DECLARE #StartDate DATETIME
DECLARE #EndDate DATETIME
SELECT
#StartDate =StartDate,
#EndDate = EndDate
FROM
DutyPeriodTbl
WHERE
(StartDate <= #DutyDate)
AND
(EndDate >= #DutyDate)
DECLARE #d DATETIME = #StartDate
WHILE #d<=#EndDate
BEGIN
INSERT INTO #datesTBL VALUES (CONVERT(DATETIME, #d, 102))
SET #d=DATEADD(day,1,#d)
END
SELECT
dt.tempDate ,
InstructorID, EventStart,
EventEnd, cancelled,
cancelledInstructor,
EventType, DevName,
Room, SimLocation,
ClassLocation, Event,
Duration, TrainingDesc,
Crew, Notes,
LastAmended, InstLastAmended,
ChangeAcknowledged, Type,
OtherType, OtherTypeDesc,
CourseType
FROM
OpsInstructorEventsView iv
LEFT OUTER JOIN
#datesTBL dt
ON
CONVERT(DATETIME, iv.EventStart, 102) = CONVERT(DATETIME, dt.tempDate, 102)
WHERE
InstructorID = #InstructorID
AND
EventStart BETWEEN CONVERT(DATETIME, #StartDate, 102) AND CONVERT(DATETIME, #EndDate, 102)
ORDER BY
EventStart
There are several ways of dealing with missing rows, but all are about having another set of data to combine with your current results.
That could be derived from your results, created by a CTE or other process (such as your example), or (my preference) by using a permanent template to join against.
The template in your case could just be a table of dates, like your #datesTBL. The difference being that it's created in advance with, for example, 100 years worth of dates.
Your query may then be similar to your example, but I would try the following...
SELECT
dt.tempDate ,
InstructorID, EventStart,
EventEnd, cancelled,
cancelledInstructor,
EventType, DevName,
Room, SimLocation,
ClassLocation, Event,
Duration, TrainingDesc,
Crew, Notes,
LastAmended, InstLastAmended,
ChangeAcknowledged, Type,
OtherType, OtherTypeDesc,
CourseType
FROM
#datesTBL dt
LEFT OUTER JOIN
OpsInstructorEventsView iv
ON iv.EventStart >= dt.tempDate
AND iv.EventStart < dt.tempDate + 1
AND iv.InstructorID = #InstructorID
WHERE
dt.tempDate >= #StartDate
AND dt.tempDate <= #EndDate
ORDER BY
dt.tempDate,
iv.EventStart
This puts the calendar template on the LEFT, and so makes many queries easier as you know the calendar's date field is always populated, is always a date only (no time part) value, is in order, is simple to GROUP BY, etc.
Well, idea is the same, but i would write function, that returns table with all dates in period. Look at this:
Create Function [dbo].[Interval]
(
#DateFrom Date,
#DateTo Date
)
Returns #tab Table
(
MyDate DateTime
)
As
Begin
Declare #Days int
Declare #i int
Set #Days = DateDiff(Day, #DateFrom, #DateTo)
Set #i = 0;
While (#Days > #i)
Begin
Insert Into #tab(MyDate)
Values (DateAdd(Day, #i, #DateTo))
Set #i = #i + 1
End
return
End
And reuse the function whenever you need it..
Select *
From [dbo].[Interval]('2011-01-01', GETDATE())