T-SQL: A list of Date without using temp table - sql

I have a table
|Start Date|End Date |Value|Avgerage Value Per Day|
|2011-01-01 |2012-01-01| 730 | 2|
I want to turn this table into a View
|Date| Average Value |
2011-01-01 | 2
2011-01-02 | 2
2011-01-03 | 2
.....
2011-12-31 | 2
Is is possible without using temp table to generate a list of date?
Any ideas?
Edit
Thanks both of the answers
With recursive view is similar as temp table
I do worry about the performance in a view, caz the view later will be involved in other processes.
I'll try recursive view then, if it doesn't fit, I may just use an hard code date list table.

declare #start datetime
SET #start = '20110501'
declare #end datetime
SET #end ='20120501'
;with months (date)
AS
(
SELECT #start
UNION ALL
SELECT DATEADD(day,1,date)
from months
where DATEADD(day,1,date)<=#end
)
select * from months OPTION (MAXRECURSION 0);
etc..etc..etc..

Yes you can. This generates the days from the input set and then gives you the ranges you need
Though this technically internally is like temp tables you can create a recursive view :
Create View TestView as
with Data As -- Pretends to be your table of data replace with select from your tables
(
select Cast('2012-05-01' as DATETIME) [Start Date], Cast('2012-05-02' as DATETIME) [End Date], 2 [Avgerage Value Per Day]
union all
select Cast('2012-04-01' as DATETIME) [Start Date], Cast('2012-04-05' as DATETIME) [End Date], 3 [Avgerage Value Per Day]
)
,AllDates as -- generates all days
(
select cast('1900-01-01' as datetime) TheDate
union all
select TheDate + 1
from AllDates
where TheDate + 1 < '2050-12-31'
)
select TheDate [Date], o.[Avgerage Value Per Day]
from AllDates
join Data o on TheDate Between o.[Start Date] AND o.[End Date];
you can the query it but you need to ensure that you specify a recursion limit
select * from TestView
OPTION (MAXRECURSION 0)
this gives this result
Date Avgerage Value Per Day
2012-04-01 00:00:00.000 3
2012-04-02 00:00:00.000 3
2012-04-03 00:00:00.000 3
2012-04-04 00:00:00.000 3
2012-04-05 00:00:00.000 3
2012-05-01 00:00:00.000 2
2012-05-02 00:00:00.000 2
You can see that from the test data I wanted May 1-2 and April 1-5

Related

How to select Maximum and minimum date values and pass those as a query

I have a table with the following entries
CustomeID
TransDate
WorkID
1
2012-12-01
12
1
2012-12-03
45
1
2013-01-21
3
2
2012-12-23
11
3
2013-01-04
13
3
2013-12-24
16
4
2014-01-02
2
I am trying get the data between two dates and the required date values are minimum and maximum values of the column. I am able to get the desired output when I hard code the values.
SELECT *
FROM dbo.MyTable
WHERE TransDate >= '2012-12-01' AND TransDate <= '2014-01-02'
I am aware the if I remove the where clause it will solve all the issues, But my actual query is much complex and has other conditions. The only way is to get maximum date values and minimum date value from the table and pass that reference to it.
I tried the below step but that does not work and throws the below error.
SELECT *
FROM dbo.MyTable
WHERE TransDate >= '2012-12-01' AND TransDate <= MAX(TransDate)
Error
An aggregate may not appear in the WHERE clause unless it is in a subquery contained in a HAVING clause or a select list, and the column being aggregated is an outer reference.
Expected Output:
CustomeID
TransDate
WorkID
1
2012-12-01
12
1
2012-12-03
45
1
2013-01-21
3
2
2012-12-23
11
3
2013-01-04
13
3
2013-12-24
16
4
2014-01-02
2
Use a scalar subquery to find the maximum date across the whole table:
SELECT *
FROM dbo.MyTable
WHERE TransDate >= '2012-12-01' AND
TransDate < (SELECT DATEADD(DAY, 1, MAX(TransDate)) FROM dbo.MyTable);
Note that I am using a strict inequality (less than) in the WHERE clause against one day later than the max date. This will include all days which fall on or earlier than the maximum date.
You can also declare variables and then use them as given below:
DECLARE #minDate DATE = (SELECT MIN(TransDate) FROM Customer);
DECLARE #maxDate DATE = (SELECT MAX(TransDate) FROM Customer);
SELECT * FROM dbo.MyTable WHERE TransDate >= #minDate AND
TransDate <= #maxDate

Fill rows for missing data by last day of month

I have a table that looks like
UserID LastDayofMonth Count
1234 2015-09-30 00:00:00 12
1237 2015-09-30 00:00:00 5
3233 2015-09-30 00:00:00 3
8336 2015-09-30 00:00:00 22
1234 2015-10-31 00:00:00 8
1237 2015-10-31 00:00:00 5
3233 2015-10-31 00:00:00 7
8336 2015-11-30 00:00:00 52
1234 2015-11-30 00:00:00 8
1237 2015-11-30 00:00:00 5
3233 2015-11-30 00:00:00 7
(with around ~10,000 rows). As you can see in the example, UserID 8336 has no record for October 31st (dates are monthly but always the last day of the month, which I want to keep). How do I return a table with a records that fills in records for a period of four months so that users like 8336 get records like
8336 2015-10-31 00:00:00 0
I do have a calendar table with all days that I can use.
If I understand correctly, you want a record for each user and for each end of month. And, if the record does not currently exist, then you want the value of 0.
This is two step process. Generate all the rows first, using cross join. Then use left join to get the values.
So:
select u.userId, l.LastDayofMonth, coalesce(t.cnt, 0) as cnt
from (select distinct userId from t) u cross join
(select distinct LastDayofMonth from t) l left join
t
on t.userId = u.userId and t.LastDayofMonth = l.LastDayofMonth;
This solution uses a couple of CTEs, not knowing your calendar table layout. The only advantage this solution has over Gordon Linoff's is it doesn't assume at least one user per possible month. I've provided test data per your example with an extra record for the month of July, skipping August entirely.
/************** TEST DATA ******************/
IF OBJECT_ID('MonthlyUserCount','U') IS NULL
BEGIN
CREATE TABLE MonthlyUserCount
(
UserID INT
, LastDayofMonth DATETIME
, [Count] INT
)
INSERT MonthlyUserCount
VALUES (1234,'2015-07-31 00:00:00',12),--extra record
(1234,'2015-09-30 00:00:00',12),
(1237,'2015-09-30 00:00:00',5),
(3233,'2015-09-30 00:00:00',3),
(8336,'2015-09-30 00:00:00',22),
(1234,'2015-10-31 00:00:00',8),
(1237,'2015-10-31 00:00:00',5),
(3233,'2015-10-31 00:00:00',7),
(8336,'2015-11-30 00:00:00',52),
(1234,'2015-11-30 00:00:00',8),
(1237,'2015-11-30 00:00:00',5),
(3233,'2015-11-30 00:00:00',7)
END
/************ END TEST DATA ***************/
DECLARE #Start DATETIME;
DECLARE #End DATETIME;
--establish a date range
SELECT #Start = MIN(LastDayofMonth) FROM MonthlyUserCount;
SELECT #End = MAX(LastDayofMonth) FROM MonthlyUserCount;
--create a custom calendar of days using the date range above and identify the last day of the month
--if your calendar table does this already, modify the next cte to mimic this functionality
WITH cteAllDays AS
(
SELECT #Start AS [Date], CASE WHEN DATEPART(mm, #Start) <> DATEPART(mm, #Start+1) THEN 1 ELSE 0 END [Last]
UNION ALL
SELECT [Date]+1, CASE WHEN DATEPART(mm,[Date]+1) <> DatePart(mm, [Date]+2) THEN 1 ELSE 0 END
FROM cteAllDays
WHERE [Date]< #End
),
--cte using calendar of days to associate every user with every end of month
cteUserAllDays AS
(
SELECT DISTINCT m.UserID, c.[Date] LastDayofMonth
FROM MonthlyUserCount m, cteAllDays c
WHERE [Last]=1
)
--left join the cte to evaluate the NULL and present a 0 count for that month
SELECT c.UserID, c.LastDayofMonth, ISNULL(m.[Count],0) [Count]
FROM cteUserAllDays c
LEFT JOIN MonthlyUserCount m ON m.UserID = c.UserID
AND m.LastDayofMonth =c.LastDayofMonth
ORDER BY c.LastDayofMonth, c.UserID
OPTION ( MAXRECURSION 0 )

Open Ticket Count Per Day

I have a table that looks like this
id | Submit_Date | Close_Date
------------------------------
1 | 2015-02-01 | 2015-02-05
2 | 2015-02-02 | 2015-02-04
3 | 2015-02-03 | 2015-02-05
4 | 2015-02-04 | 2015-02-06
5 | 2015-02-05 | 2015-02-07
6 | 2015-02-06 | 2015-02-07
7 | 2015-02-07 | 2015-02-08
I can get a count of how many ticket were open on a particular day with this:
Select count(*) from tickets where '2015-02-05' BETWEEN Submit_Date and Close_Date
This gives me 4, but I need this count for each day of a month. I don't want to have to write 30 queries to handle this. Is there a way to capture broken down by multiple days?
I created a solution a way back using a mix of #Heinzi s solution with the trick from Generate a resultset of incrementing dates in TSQL
declare #dt datetime, #dtEnd datetime
set #dt = getdate()
set #dtEnd = dateadd(day, 100, #dt)
SELECT dates.myDate,
(SELECT COUNT(*)
FROM tickets
WHERE myDate BETWEEN Submit_Date and Close_Date
)
FROM
(select Dates_To_Checkselect dateadd(day, number, #dt) mydate
from
(select distinct number from master.dbo.spt_values
where name is null
) n
where dateadd(day, number, #dt) < #dtEnd) dates
Code is combined from memory, I don't have it in front of me so there can be some typo's
First, you'll need a table that contains each date you want to check. You can use a temporary table for that. Let's assume that this table is called Dates_To_Check and has a field myDate:
SELECT myDate,
(SELECT COUNT(*)
FROM tickets
WHERE myDate BETWEEN Submit_Date and Close_Date)
FROM Dates_To_Check
Alternatively, you can create a huge table containing every possible date and use a WHERE clause to restrict the dates to those you are interested in.
If you're in SQL Server 2012 or newer you can do this using window functions with a small trick where you add 1 to the open days -1 to the closing days and then do a running total of this amount:
select distinct date, sum(opencnt) over (order by date) from (
select
Submit_Date as date,
1 as opencnt
from
ticket
union all
select
dateadd(day, 1, Close_Date),
-1
from
ticket
) TMP
There's a dateadd + 1 day to include the close date amount to that day
You could generate the list of dates and then retrieve the count for each date in your dateset.
The cte part generates the date list since the beginning of the year (an ssumption) and the next part calculates the count from your data set.
with cte as
(select cast('2015-01-01' as date) dt // you should change this part to the correct start date
union all
select dateadd(DD,1,dt) dt from cte
where dt<getdate()
)
select count(*)
from tickets
inner join cte
on cte.dt between Submit_Date and Close_Date
group by cte.dt

SQL Server: Finding date given EndDate and # Days, excluding days from specific date ranges

I have a TableA in a database similar to the following:
Id | Status | Start | End
1 | Illness | 2013-04-02 | 2013-04-23
2 | Illness | 2013-05-05 | 2014-01-01
3 | Vacation | 2014-02-01 | 2014-03-01
4 | Illness | 2014-03-08 | 2014-03-09
5 | Vacation | 2014-05-05 | NULL
Imagine it's keeping track of a specific user's "Away" days. Given the following Inputs:
SomeEndDate (Date),
NumDays (Integer)
I want to find the SomeStartDate (Date) that is Numdays non-illness days from EndDate. In other words, say I am given a SomeEndDate value '2014-03-10' and a NumDays value of 60; the matching SomeStartDate would be:
2014-03-10 to 2014-03-09 = 1
2014-03-08 to 2014-01-01 = 57
2013-05-05 to 2013-05-03 = 2
So, at 60 non-illness days, we get a SomeStartDate of '2013-05-03'. IS there any easy way to accomplish this in SQL? I imagine I could loop each day, check whether or not it falls into one of the illness ranges, and increment a counter if not (exiting the loop after counter = #numdays)... but that seems wildly inefficient. Appreciate any help.
Make a Calendar table that has a list of all the dates you will ever care about.
SELECT MIN([date])
FROM (
SELECT TOP(#NumDays) [date]
FROM Calendar c
WHERE c.Date < #SomeEndDate
AND NOT EXISTS (
SELECT 1
FROM TableA a
WHERE c.Date BETWEEN a.Start AND a.END
AND Status = 'Illness'
)
ORDER BY c.Date
) t
The Calendar table method lets you also easily exclude holidays, weekends, etc.
SQL Server 2012:
Try this solution:
DECLARE #NumDays INT = 70, #SomeEndDate DATE = '2014-03-10';
SELECT
[RangeStop],
CASE
WHEN RunningTotal_NumOfDays <= #NumDays THEN [RangeStart]
WHEN RunningTotal_NumOfDays - Current_NumOfDays <= #NumDays THEN DATEADD(DAY, -(#NumDays - (RunningTotal_NumOfDays - Current_NumOfDays))+1, [RangeStop])
END AS [RangeStart]
FROM (
SELECT
y.*,
DATEDIFF(DAY, y.RangeStart, y.RangeStop) AS Current_NumOfDays,
SUM( DATEDIFF(DAY, y.RangeStart, y.RangeStop) ) OVER(ORDER BY y.RangeStart DESC) AS RunningTotal_NumOfDays
FROM (
SELECT LEAD(x.[End]) OVER(ORDER BY x.[End] DESC) AS RangeStart, -- It's previous date because of "ORDER BY x.[End] DESC"
x.[Start] AS RangeStop
FROM (
SELECT #SomeEndDate AS [Start], '9999-12-31' AS [End]
UNION ALL
SELECT x.[Start], x.[End]
FROM #MyTable AS x
WHERE x.[Status] = 'Illness'
AND x.[End] <= #SomeEndDate
) x
) y
) z
WHERE RunningTotal_NumOfDays - Current_NumOfDays <= #NumDays;
/*
Output:
RangeStop RangeStart
---------- ----------
2014-03-10 2014-03-09
2014-03-08 2014-01-01
2013-05-05 2013-05-03
*/
Note #1: LEAD(End) will return the previous End date (previous because of ORDER BY End DESC)
Note #2: DATEDIFF(DAY, RangeStart, RangeStop) computes the num. of days between current start (alias x.RangeStop) and "previous" end (alias x.RangeStar) => Current_NumOfDays
Note #3: SUM( Current_NumOfDays ) computes a running total thus: 1 + 66 + (3)
Note #4: I've used #NumOfDays = 70 (not 60)

Using CTE , i need to filter out the data

I have a table named source_name whose populated data is given as below.
Date Name
------ -------
2010-01-14 a
2010-01-15 b
2010-01-16 c
2010-01-17 bc
2010-01-18 bcc
2010-01-19 bd
2010-01-20 bddd
2010-01-13 be
2010-01-12 beeeee
2010-01-11 beee
2010-01-10 beee
2010-01-09 beee
I need a CTE query to return me the following result.
Here is my condition. If i select the #no_of_days = 2 and #date = '2010-01-14'
it must return me
2010-01-15 b
2010-01-16 c
2010-01-13 be
2010-01-12 beee
I mean if the #no_of_days is selected as any number, then i want the filter data to be exactly the same number of data from the given date to that incremented date till the given no_of_days and correspondingly the date below it.
Another example if #no_of_days = 3 and #date = '2010-01-14'
2010-01-15 b
2010-01-16 c
2010-01-17 bc
2010-01-13 be
2010-01-12 beee
2010-01-11 beee
I need an help.
;
WITH myTable([Date], [Name])
AS (
SELECT *
FROM source_name
WHERE [Date] BETWEEN DATEADD(day, (#no_of_days*-1), #date) AND DATEADD(day, #no_of_days, #date)
AND [Date] != #Date
)
.... do something here.
I don't think you need a CTE for that, nor do I see how it could help.
But the DATEDIFF function should allow you to write a where clause quite easily:
SELECT *
FROM source_name
WHERE ABS(DATEDIFF( day, [Date], #Date )) BETWEEN 1 AND #no_of_days
SQLFiddle demo
EDIT: As #MatBailie rightly comments, if you need to sift through a large amount of indexed dates, you don't want to meddle with the value of the Date column before you compare it and DATEADD is your friend instead.