Can I use recursion in a Sql Server 2005 View? - sql

I tried to use OPTION (MAXRECURSION 0) in a view to generate a list of dates.
This seems to be unsupported. Is there a workaround for this issue?
EDIT to Explain what I actually want to do:
I have 2 tables.
table1: int weekday, bool available
table2: datetime date, bool available
I want the result:
view1: date (here all days in this year), available(from table2 or from table1 when not in table2).
That means I have to apply a join on a date with a weekday.
I hope this explanation is understandable, because I actually use more tables with more fields in the query.
I found this code to generate the recursion:
WITH Dates AS
(
SELECT cast('2008-01-01' as datetime) Date
UNION ALL
SELECT Date + 1
FROM Dates
WHERE Date + 1 < DATEADD(yy, 1, GETDATE())
)

No - if you can find a way to do it within 100 levels of recusion (have a table of numbers), which will get you to within 100 recursion levels, you'll be able to do it. But if you have a numbers or pivot table, you won't need the recursion anyway...
See this question (but I would create a table and not a table-valued function), this question and this link and this link

You can use a CTE for hierarchical queries.

Here you go:
;WITH CTE_Stack(IsPartOfRecursion, Depth, MyDate) AS
(
SELECT
0 AS IsPartOfRecursion
,0 AS Dept
,DATEADD(DAY, -1, CAST('01.01.2012' as datetime)) AS MyDate
UNION ALL
SELECT
1 AS IsPartOfRecursion
,Parent.Depth + 1 AS Depth
--,DATEADD(DAY, 1, Parent.MyDate) AS MyDate
,DATEADD(DAY, 1, Parent.MyDate) AS MyDate
FROM
(
SELECT 0 AS Nothing
) AS TranquillizeSyntaxCheckBecauseWeDontHaveAtable
INNER JOIN CTE_Stack AS Parent
--ON Parent.Depth < 2005
ON DATEADD(DAY, 1, Parent.MyDate) < DATEADD(YEAR, 1, CAST('01.01.2012' as datetime))
)
SELECT * FROM CTE_Stack
WHERE IsPartOfRecursion = 1
OPTION (MAXRECURSION 367) -- Accounting for leap-years
;

Related

Trying to convert a 7 digit julian date into MMDDYY format

I'm trying to convert a 7 digit julian/mainframe date into a calendar date of format mmddyy in SQL Server.
An example of this being julian date 2005020, where the first 4 digits are the year, 2005, and the last three are the day number in a calendar, so 020 = january 20. So I'd need to get this date as 012005(MMDDYY) in SQL Server.
I've been using the following query but keep getting an error after it loads a few records:
SELECT DATEADD(day,CAST(RIGHT([julianDateColumn],3) as int)-,LEFT([julianDateColumn],4))
The error I've been getting:
Conversion failed when converting date and/or time from character string.
Originally I was doing this in an Access DB using the "DATESERIAL" function but from what I've seen the closest thing to that in SQL Server was "DATEFROMPARTS", I tried using the following formula but it also didn't work:
DATEFROMPARTS([julianDateColumn]/1000,1,[julianDateColumn] % 1000)
Thanks in advance!
The simplest would seem to be to take the left as the year, and the add the days (-1) to make a date. Also, rather than using a format of MMDDYY I'm going to go straight a date datatype. If you want it in a specific format, that's for your presentation layer.
SELECT JulianDate,
CONVERT(date,DATEADD(DAY,RIGHT(JulianDate,3)-1,CONVERT(datetime,LEFT(JulianDate,4)))) AS ActualDate --4 int strings are iterpreted as the year, so I'm going to take advantage of that
FROM (VALUES('2005020'))V(JulianDate);
Based on the comments on the answer, it appears that the OP has some dates that don't conform to the format that stated (yyyyddd). Therefore what we could use here is a calendar table, here, and then LEFT JOIN to it and see what bad rows you get (and INNER JOIN to get the dates).
You can create the table with something like this:
CREATE TABLE dbo.CalendarTable (CalendarDate date NOT NULL PRIMARY KEY,
CalenderYear AS DATEPART(YEAR, CalendarDate) PERSISTED,
CalenderMonth AS DATEPART(MONTH, CalendarDate) PERSISTED,
CalenderDay AS DATEPART(DAY, CalendarDate) PERSISTED,
CalenderMonthName AS DATENAME(MONTH, CalendarDate),
JulianDate AS DATEPART(YEAR,CalendarDate) * 1000 + DATEDIFF(DAY,DATEFROMPARTS(DATEPART(YEAR, CalendarDate),1,1),CalendarDate) + 1 PERSISTED); --Some example columns
WITH N AS(
SELECT N
FROM (VALUES(NULL),(NULL),(NULL),(NULL),(NULL),(NULL),(NULL),(NULL),(NULL),(NULL))N(N)),
Tally AS(
SELECT ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) -1 AS I
FROM N N1, N N2, N N3, N N4, N N5, N N6),
Dates AS(
SELECT CONVERT(date,DATEADD(DAY, T.I, '19000101')) AS CalendarDate
FROM Tally T)
INSERT INTO dbo.CalendarTable(CalendarDate)
SELECT CalendarDate
FROM Dates
WHERE CalendarDate < '21000101';
GO
Then we can do something like this to get the bad rows:
SELECT YT.JulianDate
FROM dbo.YourTable YT
LEFT JOIN dbo.CalendarTable CT ON YT.JulianDate = CT.JulianDate
WHERE CT.JulianDate IS NULL;
I think I would use datefromparts() and dateadd():
select dateadd(day,
right(juliandate, 3) - 1),
datefromparts(left(juliandate, 4), 1, 1)
)
select Format(cast(concat(substring('2005020', 1, 4), '-', Month(cast(substring('2005020', 5, len('2005020')) as int)) ,'-', day(dateadd(day,-1,cast(substring('2005020', 5, len('2005020')) as int ) ) )) as date), 'MMddyy')

How to create multiple rows (as date column) into CTE? (out of thin air)

I have a stored-procedure and I want to add date column to the tables. Then I wonder how do I get MS-SQL to generate multiple rows on the fly using CTE. Say I have this..
(GETDATE() - 548) --(365 days --> 12 months, 548 days --> 18 months...
How do you guys whip up a query that would create 548 rows and have the date column as row
#1 - '12/17/2012'
#2 - '12/16/2012'
#3 - '12/15/2012'
etc. all the way to row #548? All of that into a CTE?
Thanks...
Unless I am missing something, it sounds like you want the following:
;with dates(value) as
(
select DATEADD(d, -548, cast(getdate() as DATE))
union all
select DATEADD(D, 1, value)
from dates
where DATEADD(D, 1, value) <= cast(getdate() as DATE)
)
select *
from dates
order by value desc
OPTION (MAXRECURSION 1000)
See SQL Fiddle with Demo

SQL Count to include zero values

I have created the following stored procedure that is used to count the number of records per day between a specific range for a selected location:
[dbo].[getRecordsCount]
#LOCATION as INT,
#BEGIN as datetime,
#END as datetime
SELECT
ISNULL(COUNT(*), 0) AS counted_leads,
CONVERT(VARCHAR, DATEADD(dd, 0, DATEDIFF(dd, 0, Time_Stamp)), 3) as TIME_STAMP
FROM HL_Logs
WHERE Time_Stamp between #BEGIN and #END and ID_Location = #LOCATION
GROUP BY DATEADD(dd, 0, DATEDIFF(dd, 0, Time_Stamp))
but the problem is that the result does not show the days where there are zero records, I pretty sure that it has something to do with my WHERE statement not allowing the zero values to be shown but I do not know how to over come this issue.
Thanks in advance
Neil
Not so much the WHERE clause, but the GROUP BY. The query will only return data for rows that exist. That means when you're grouping by the date of the timestamp, only days for which there are rows will be returned. SQL Server can't know from context that you want to "fill in the blanks", and it wouldn't know what with.
The normal answer is a CTE that produces all the days you want to see, thus filling in the blanks. This one's a little tricky because it requires a recursive SQL statement, but it's a well-known trick:
WITH CTE_Dates AS
(
SELECT #START AS cte_date
UNION ALL
SELECT DATEADD(DAY, 1, cte_date)
FROM CTE_Dates
WHERE DATEADD(DAY, 1, cte_date) <= #END
)
SELECT
cte_date as TIME_STAMP,
ISNULL(COUNT(HL_Logs.Time_Stamp), 0) AS counted_leads,
FROM CTE_Dates
LEFT JOIN HL_Logs ON DATEADD(dd, 0, DATEDIFF(dd, 0, Time_Stamp)) = cte_date
WHERE Time_Stamp between #BEGIN and #END and ID_Location = #LOCATION
GROUP BY cte_date
Breaking it down, the CTE uses a union that references itself to recursively add one day at a time to the previous date and remember that date as part of the table. If you ran a simple statement that used the CTE and just selected * from it, you'd see a list of dates between start and end. Then, the statement joins this list of dates to the log table based on the log timestamp date, while preserving dates that have no log entries using the left join (takes all rows from the "left" side whether they have matching rows on the "right" side or not). Finally, we group by date and count instead and we should get the answer you want.
When there is no data to count, there is no row to return.
If you want to include empty days as a 0, you need to create a table (or temporary table, or subquery) to store the days, and left join to your query from that.
eg: something like
SELECT
COUNT(*) AS counted_leads,
CONVERT(VARCHAR, DATEADD(dd, 0, DATEDIFF(dd, 0, Time_Stamp)), 3) as TIME_STAMP
FROM
TableOfDays
left join
HL_Logs
on TableOfDays.Date = convert(date,HL_Logs.Time_Stamp)
and ID_Location = #LOCATION
WHERE TableOfDays.Date between #BEGIN and #END
GROUP BY DATEADD(dd, 0, DATEDIFF(dd, 0, Time_Stamp))
Use a left outer join. Such as
select count(stuff_ID), extra_NAME
from dbo.EXTRAS
left outer join dbo.STUFF on suff_EXTRA = extra_STUFF
group by extra_NAME
I just recently has a similar task and used this as a backdrop to my work. However, as explained by robwilliams I too, couldn't get it KeithS solution to work. Mine task was slightly different I was doing it by hours vs days but I think the solution to the neilrudds question would be
DECLARE #Start as DATETIME
,#End as DATETIME
,#LOCATION AS INT;
WITH CTE_Dates AS
(
SELECT #Start AS cte_date, 0 as 'counted_leads'
UNION ALL
SELECT DATEADD(DAY, 1, cte_date) as cte_date, 0 AS 'counted_leads'
FROM CTE_Dates
WHERE DATEADD(DAY, 1, cte_date) <= #End
)
SELECT cte_date AS 'TIME_STAMP'
,COUNT(HL.ID_Location) AS 'counted_leads'
FROM CTE_Dates
LEFT JOIN HL_Logs AS HL ON CAST(HL.Time_Stamp as date) = CAST(cte_date as date)
AND DATEPART(day, HL.Time_Stamp) = DATEPART(day,cte_date)
AND HL.ID_Location = #LOCATION
group by cte_date
OPTION (MAXRECURSION 0)

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.

Grouping OR clause with DATEDIFFS in SQL

I have a sql table. We'll call it 'table'. I'm trying to do a select with a few WHERE clauses. I need the last where clause to be an OR. Normally, I just do this:
SELECT *
FROM table
WHERE 1=1
AND (firstName='tom' OR lastName='jones')
However, on this particular query, I need the two sides of the OR to be DATEDIFFS. I'm trying to grab all rows where the difference between the timestamp and "NOW" is either greater than 10 hours or less than 0 hours. So, what I've got now, which errors, is this:
SELECT *
FROM table
WHERE 1=1
AND (
DATEDIFF(hh, table.timestamp, GETDATE()) > 10
OR
DATEDIFF(hh, table.timestamp, GETDATE()) < 0
)
I have tried several different versions of what I have above and nothing seems to be working. Any help would be much appreciated! I'm sure I've just been staring at it too long and it's something stupid.
A new more detailed query:
Yeah, there's two left joins in my query which is probably what's making a difference here. I was trying to keep my examples simple, but it wound up biting me. Here's a more relevant query:
SELECT
tblCheckoutActivity.rid,
tblCheckoutActivity.checkedOutUser,
tblCheckoutActivity.checkedOutTime,
tblCheckoutInventory.serial,
tblCheckoutInventory.unit,
tblCheckoutInventory.tag,
tblCheckoutUsers.firstName,
tblCheckoutUsers.lastName
FROM
tblCheckoutActivity
LEFT JOIN
tblCheckoutInventory
ON
tblCheckoutActivity.scannerID = tblCheckoutInventory.SID
LEFT JOIN
tblCheckoutUsers
ON
tblCheckoutActivity.checkedOutUser = tblCheckoutUsers.badgeID
WHERE
tblCheckoutActivity.checkedInTime Is Null
AND
tblCheckoutInventory.site='San Francisco'
AND
(
DATEDIFF(hh, tblCheckoutActivity.checkedOutTime, GETDATE()) > 10
OR
DATEDIFF(hh, tblChecckoutActivity.checkedOutTime, GETDATE()) < 0
)
ORDER BY tblCheckoutUsers.lastName
I think your problem is somewhere else. I just ran this with no errors:
with temp as
(
select 1 as id, CAST('10/31/2011' as DATETIME) as timestamp
UNION
SELECT 2, CAST('11/1/2011' as DATETIME)
UNION
SELECT 3, CAST('11/2/2011' as DATETIME)
UNION
SELECT 4, CAST('11/3/2011' as DATETIME)
UNION
SELECT 5, CAST('11/6/2011' as DATETIME)
UNION
SELECT 6, CAST('11/8/2011' as DATETIME)
)
SELECT *
FROM temp
WHERE 1=1
AND (
DATEDIFF(hh, timestamp, GETDATE()) > 10
OR
DATEDIFF(hh, timestamp, GETDATE()) < 0
)
Are you showing us an abbreviated version of your query? Is there code before this query that is missing from your example?
If you're using the timestamp datatype, then that's your problem. You can't do a DATEDIFF comparison between a DATETIME and a timestamp.
SELECT *
FROM table
WHERE
DATEDIFF(hh, table.timestamp, GETDATE()) > 10
UNION
SELECT *
FROM table
WHERE
DATEDIFF(hh, table.timestamp, GETDATE()) < 0
quick and dirty but it should work