3

I have currently written a small UDF (Table Value Type) to return a table of dates between a start and end date. Start and End dates are already retrieved from another table.

Start_End_Table
WeekNumber | Start Date | End Date

Function:

CREATE FUNCTION [dbo].[DatesBetween](@startDate datetime, @endDate datetime)
RETURNS @dates TABLE (
   DateValue datetime NOT NULL
) 
AS
BEGIN
   WHILE (@startDate <= @endDate) BEGIN
      INSERT INTO @dates VALUES (@startDate);
      SET @startDate = DATEADD(day, 1, @startDate);
   END;

   RETURN;
END;

Until now, I have been using this function in the following manner,

SELECT * FROM [dbo].[DatesBetween](@startDate datetime, @endDate datetime);

Going forward I need to get start, end dates from Start_End_Date table and call the function. Thus I do not think returning a table is an option any longer. How can I get this function to return an array of dates instead of a table? Or is there any other way I could wrap this up?

bonCodigo
  • 14,268
  • 1
  • 48
  • 91
  • 1
    a more efficient approach would be to set up a permanent pre-populated date table (or a calendar table as they are sometimes called), index it, and filter on that table instead of using a WHILE loop. – g2server Jul 23 '14 at 03:14
  • I'm a little confused. Are you calling the function using variables currently? – shree.pat18 Jul 23 '14 at 03:17
  • @g2server setting up a calendar table isn't an option as the tables are out of our scope for change...currently we are given start, end dates and week number columns. So I have to use those two to get the set of dates. – bonCodigo Jul 23 '14 at 03:20
  • @shree.pat18 currently using variables for testing purpose - but I must use the columns available in the start_end_table within the function. Is there a way star, end dates can be incorporated into one query while using the function or what changes do I have to do to the function? – bonCodigo Jul 23 '14 at 03:36
  • 2
    +1 because this question allowed me a lot of learning – Nizam Jul 23 '14 at 05:39
  • @Nizam: agreed. it was a good exchange. – Solomon Rutzky Jul 23 '14 at 05:43

1 Answers1

2

Try CROSS APPLY as it will run the function for each row in the JOINed table:

SELECT [Date Value]
FROM dbo.Start_End_Table dates
CROSS APPLY [dbo].[DatesBetween](dates.StartDate, dates.EndDate)
WHERE [Week Number]=@WeekNumber;

A sample output would be something like for a start date = 1/1/2014, end date = 1/3/2014, @weeknumber = 1:

 | DateValue
  1/1/2014
  1/2/2014
  1/3/2014

SQL Fiddle

EDIT:
Try the following Inline Table-Valued Function as it will be much faster than your multi-line TVF due to:

  1. Inline TVFs are inherently faster than multi-line TVFs
  2. It is set-based rather than relying on a WHILE loop

--

CREATE FUNCTION dbo.GetDates(@StartDate DATETIME, @EndDate DATETIME)
RETURNS TABLE
AS RETURN
WITH cte AS
(
    SELECT ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) AS [RowNum]
    FROM sys.objects WITH (NOLOCK)
)
SELECT DATEADD(DAY, (cte.[RowNum] - 1), @StartDate) AS [DateValue]
FROM cte
WHERE cte.[RowNum] < (DATEDIFF(DAY, @StartDate, @EndDate) + 2);
GO

SELECT * FROM dbo.GetDates('1/1/2014', '1/3/2014');
Solomon Rutzky
  • 46,688
  • 9
  • 128
  • 171
  • +1 and I am about to accept this as the answer. Great stuff. But I have one question. Given that I am already using a function, how heavy `CROSS APPLY` will be on the query performance? (it's a large set of data we are looking at) – bonCodigo Jul 23 '14 at 05:00
  • performance is best determined via testing. It depends on how many Rows you have in the `Start_End_Table` and how far apart the `Start Date` and `End Date` values are. The `CROSS APPLY` will not be the bottle neck here. The UDF that does a WHILE loop will be the scalability issue if anything is. And in that case, you can replace that with a CLR version that will be _a lot_ faster. If you like, I can give you an example of that as well based on a free CLR library that I wrote and have available for download. – Solomon Rutzky Jul 23 '14 at 05:03
  • 1
    The number of dates generated are always 7. Start Date and End Date refers to a week start and end date. So we are retrieving week dates FYI. A year has 52 week and user has the ability to go back and forth between last year and current year – bonCodigo Jul 23 '14 at 05:06
  • @strutzky Would you please help us informing how to improve the UDF? – Nizam Jul 23 '14 at 05:07
  • @bonCodigo: if the dates are always 7, then why bother with EndDate? Don't you just need StartDate and then can count up 6 from that? It would be faster than comparing date values per each loop. – Solomon Rutzky Jul 23 '14 at 05:07
  • @bonCodigo: if the dates returned are always 7, that doesn't match the edit you did on my answer to show only 3 dates being returned. Which is it? – Solomon Rutzky Jul 23 '14 at 05:09
  • Well we take start and end date into account and Monday is always 1st day of the week. So it's fine with what's returned on your answer. You see... there are couple of constraints. Your answer is fit enough to be accepted - the only thing I am doing now is an sql explain for performance. – bonCodigo Jul 23 '14 at 05:12
  • @bonCodigo: I just came up with a more efficient UDF. I will update my answer. – Solomon Rutzky Jul 23 '14 at 05:18
  • 1
    In this case, there is an assumption that `sys.objects` will have a sufficient number of objects to cover the interval, right? – Nizam Jul 23 '14 at 05:27
  • 1
    @Nizam: yes, I assumed it would never go above 7 so it was safe. If more dates are needed then another table such as `sys.all_columns` could be used. That or a `CROSS JOIN` to `sys.objects` again. – Solomon Rutzky Jul 23 '14 at 05:31
  • 1
    @bonCodigo: no, my edit did not change anything. It is simply a swap-out replacement for your `DatesBetween` function. That is all. It still fits into the `CROSS APPLY` query that is the first part of my answer. I called it `GetDates` but you can rename my proposed function to `DatesBetween` if that makes things clearer. – Solomon Rutzky Jul 23 '14 at 05:32
  • 2
    @bonCodigo everything continues the same. He only changed your UDF. The you will use the new function and the CROSS APPLY he explained before. The query will be the same changing the UDF `DatesBetween` to `GetDates` which seems much faster. – Nizam Jul 23 '14 at 05:34
  • @bonCodigo: glad to help. now I desperately need sleep :). – Solomon Rutzky Jul 23 '14 at 05:41