Reputation: 4733
The table has Month and year as separate columns, I am tying to create a function that will return the date in DD-MM-YYYY
format.
I need to create a starting date and ending date for each combination, unfortunately the schema cannot be changed and so I have work with it.
I have pasted the code:
CREATE FUNCTION fn.GetDateFromMonthYear
(@isStartDate BIT,
@month INT,
@year INT)
RETURNS DATE
AS
BEGIN
DECLARE @finalDate AS DATE
IF (isStartDate)
BEGIN
SELECT @finalDate = CAST('01' + '-' + @month + @year AS DATE)
END
ELSE
BEGIN
SELECT @finalDate = CAST(CAST('01' + '-' + CAST(CAST(@month AS INT) + 1 AS VARCHAR(20)) + @year AS DATETIME) - 1 AS DATE
END
RETURN @startDate
END
There are two things how is the performance of this function can there be any improvement.
How do we handle the leap year condition.
Sample input and output
Output: 01-11-2011
Output: 30-11-2011
Output: 31-12-2011
Upvotes: 0
Views: 3194
Reputation: 74730
A little maths trick with dates can solve this one:
select dateadd(month, (YEARCOLUMN-1900)*12 + MONTHCOLUMN - ISSTARTDATE), ISSTARTDATE - 1)
dates are represented numerically in SQLS, with 0 being 1900-01-01 00:00:00 and every integer increment being one day after
Subbing 1900 off your year, then multiplying by 12 gives the number of months we have to DATEADD to a start date of 0 (the first of Jan 1900) to get to your year.
Then we add one less than your month (your month is 11, we add 10 months to January to get to the start of month 11) if it's a start date, or we add the months num if it's an end date (i.e. We add another month on, we will come back a day using another trick later).
Helpfully isstartdate is a 1 when it's a start date and 0 when it's an end date, so we essentially add (monthnum - isstartdate) to vary whether it's a start or an end. Note that at the moment this algorithm will produce 2017-12-01 for components of 2017, 11 and 0 (i.e. Is an end date) so we need to come back one day in this case, so...
The only other bit of math we have to do is start counting from day 0 if it's a start date or -1 if it's an end date. This is easily achieved by subtracting 1 from isstartdate (meaning when it's a start date we begin counting from 0, when it's an end date we begin counting from -1. In other words, when it's an end date, our months are added to 1899-12-31)
http://www.sqlfiddle.com/#!6/9eecb7db59d16c80417c72d1e1f4fbf1/14236
Note: I've done this sql style with "YEARCOLUMN" etc placeholders; you'd just substitute in your relevant variable names for everything in the query above, that is IN CAPS
Upvotes: 1
Reputation: 33581
I would use an inline table valued function instead of a scalar function. They are more flexible and better for performance. Also, date do NOT have a format. The format is used in the front end. The ANSI approved format for a date representation is YYYYMMDD. That will work no matter what your local settings are.
Here is a fully functional example of doing this with an inline table valued function.
create function GetDateFromMonthYear
(
@month int
, @year int
, @isStartDate bit
) returns table as return
select MyDate = case @isStartDate
when 1
then dateadd(month, datediff(month, 0, CONVERT(char(4), @year) + right('0' + CONVERT(varchar(2), @month), 2) + '01'), 0)
else dateadd(day, -1, dateadd(month, datediff(month, 0, CONVERT(char(4), @year) + right('0' + CONVERT(varchar(2), @month), 2) + '01') + 1, 0))
end
GO
declare @SomeDates table
(
MyYear int
, MyMonth int
, IsStartDate bit
)
insert @SomeDates
(
MyYear
, MyMonth
, IsStartDate
) values
(2017, 11, 1)
,(2017, 11, 0)
,(2017, 12, 1)
,(2017, 12, 0)
select *
from @SomeDates s
cross apply dbo.GetDateFromMonthYear(s.MyMonth, s.MyYear, s.isStartDate) x
Upvotes: 3
Reputation: 2027
Here's an example, using dateadd. If the bit is true then take the first day of the month. If the bit is false then add a month and subtract a day.
DECLARE @sample TABLE ([year] int, [month] int, isStartDate bit)
INSERT INTO @sample VALUES (2017, 11, 1), (2017, 11, 0), (2017, 12, 0)
SELECT CASE WHEN isStartDate = 1
THEN CONVERT(varchar, CAST(CAST(year as varchar) + CAST(month as varchar) + '01' as date), 105)
ELSE CONVERT(varchar, DATEADD(DAY, -1, DATEADD(MONTH, 1, CAST(CAST(year as varchar) + CAST(month as varchar) + '01' as date))), 105)
END
FROM @sample
Produces:
01-11-2017
30-11-2017
31-12-2017
Upvotes: 0
Reputation: 919
Here is how I would write it but idea is basically the same:
create function dbo.GetDateFromMonthYear(
@isStartDate bit,
@month int,
@year int)
returns date
as
begin
return (
case when @isStartDate = 1
then convert(date, cast(@year as varchar(max)) + '-' + cast(@month as varchar(max)) + '-1', 121)
else dateadd(day, -1, dateadd(month, 1, convert(date, cast(@year as varchar(max)) + '-' + cast(@month as varchar(max)) + '-1', 121)))
end)
end
go
select
dbo.GetDateFromMonthYear(isStartDate, Month, Year)
from (values
(0, 2, 2016),
(0, 3, 2017),
(1, 4, 2017),
(1, 12, 2017)
)t(isStartDate, Month, Year)
Upvotes: 0
Reputation: 32737
I know that you tagged your question with 'sql-server-2008', but for anyone stumbling on this later, the EOMONTH()
and DATEFROMPARTS()
function was added in SQL Server 2012. Both of which make your problem a lot easier to solve.
CREATE FUNCTION GetDateFromMonthYear
(
@month INT
, @year INT
, @isStartDate BIT
) RETURNS TABLE AS RETURN
SELECT MyDate = CASE @isStartDate
WHEN 1
THEN DATEFROMPARTS(@year, @month, 1)
ELSE EOMONTH(DATEFROMPARTS(@year, @month, 1))
END
Upvotes: 0