I have a table of names and associated birthdates. I can retrieve the names of everyone whose birthday is today by matching the MONTH and DAY dateparts between the birthdate in the database and the current date. However, I need to pull "looking forward" lists of, say, all birthdays in the next two weeks.
The obvious solution would be a computed column for each person, showing his/her "birthday this year". It's easy to pull the month and day from the birthday, add the current year, and cast the whole string as a date. That way I could just retrieve those whose "birthday this year" is within X days of the current date. However, I have one person with a birthday on February 29, and there isn't a Feb. 29 every year, so the calculated column "chokes" when I query or open the table with the following error:
The conversion of a char data type to a datetime data type resulted in an out-of-range datetime value.
Suggestions on a way to either make a computed column work properly in this situation, or an alternative way to query using the date of birth in the table?
Change your query from using convert to using try_convert which will not error on nonexistent date but will instead return NULL. This will exclude your Feb 29 birthday client if running year is not leap.
Your approach won't work for the end of December, because the year changes. Here is a different approach:
Add the number of years to the date and let the database handle leap years.
Then do the comparison.
So, the logic for the first is:
select dateadd(year, year(getdate()) - year(dob), dob)
Then to get dates of birth in the next two weeks:
where dateadd(year, year(getdate()) - year(dob), dob) >= convert(date, getdate()) and
dateadd(year, year(getdate()) - year(dob), dob) < dateadd(14, day, convert(date, getdate())
However, this still doesn't handle dates of birth that could be next year. So, to handle the end of year, consider that as well:
where (dateadd(year, year(getdate()) - year(dob), dob) >= convert(date, getdate()) and
dateadd(year, year(getdate()) - year(dob), dob) < dateadd(14, day, convert(date, getdate())
) or
(dateadd(year, 1 + year(getdate()) - year(dob), dob) >= convert(date, getdate()) and
dateadd(year, 1 + year(getdate()) - year(dob), dob) < dateadd(14, day, convert(date, getdate())
)
To do this - you need to calculate the current year's DOB and the persons next date of birth. To calculate the current year date of birth we can use a simple calculation:
dateadd(year, datediff(year, DateOfBirth, CurrentDate), DateOfBirth)
Then - we need to calculate the next date of birth, which is simply adding a year if the current year DOB is less than CurrentDate:
dateadd(year, iif(CurrentDOB < CurrentDate, 1, 0), CurrentDOB)
Now - it is just a matter of checking if the next DOB is in the range. Here is some sample data to show how to put this together.
--==== Some sample dates of birth - including leap year DOB's
Declare #testData Table (DateOfBirth date);
Insert Into #testData (DateOfBirth)
Values ('1992-01-09'), ('2020-02-29'), ('1965-09-30'), ('1984-02-29');
--==== Test using different dates
Declare #current_date date = '2021-02-28';
--==== Use CROSS APPLY to calculate current year DOB and Next DOB
Select *
From #testData As td
Cross Apply (Values (dateadd(year, datediff(year, td.DateOfBirth, #current_date), td.DateOfBirth))) As y(CurrentDOB)
Cross Apply (Values (dateadd(year, iif(y.CurrentDOB < #current_date, 1, 0), y.CurrentDOB))) As n(NextDOB)
Where n.NextDOB <= dateadd(day, 14, #current_date);
If today is 2021-02-28 then the birthdays that fall on the 29th will be included. If today is 2021-03-01 then those will not be included because they would be calculated as 2022-02-28 which is not within 14 days.
Related
I'm looking to calculate how many weeks an employee will have worked if they started mid year until the end of the current fiscal year (1st April - 31st March).
For example, an employee started working at the company on 01/10/2017 (UK date) I need to calculate the number of weeks they will have worked until 31/03/2018 (inclusive).
The field for the employee start date is 'START_DATE' from table 'Employee'. I also have a calendar table with every date format you could imagine and also includes fiscal year.
I found this question but it doesn't quite solve my problem: Calculate totals of field based on current fiscal year only - SQL
Any help much appreciated.
It does depend on how you classify what a week is. Does it have to be a full week? Does starting on a day that is not a Monday mean that it's not counted as a full week if they finish on a day that is not Friday? This is where you have to identify your business logic.
Here are some fundamental DATEDIFF operations that you can use to work out differences between two dates, which you can use as a basis for your calculations:
DECLARE #startDate DATE = '2017-10-01'
DECLARE #endDate DATE = '2018-03-31'
SELECT #startDate StartDate, #endDate EndDate,
DATEDIFF(DAY, #startDate, #endDate) DaysPassed,
DATEDIFF(WEEK, #startDate, #endDate) WeeksWorked,
DATEDIFF(DAY, #startDate, #endDate) / 7.0 CalculatedWeeksWorked
Produces:
StartDate EndDate DaysPassed WeeksWorked CalculatedWeeksWorked
---------- ---------- ----------- ----------- ---------------------
2017-10-01 2018-03-31 181 25 25.857142
Also, you may want to consider the number of days worked excluding weekends to work out how many full weeks are worked, if so, have a look at this post:
get DATEDIFF excluding weekends using sql server
Fiscal Year
To work out the fiscal year, you should be able to simply look at the month value of the date like so:
DECLARE #startDate DATE = '2017-10-01';
-- if the month is greater than 3, add a year, else take the current year
SELECT CASE
WHEN DATEPART(MONTH, #startDate) > 3 THEN
CAST(DATEPART(YEAR, #startDate) + 1 AS VARCHAR(10)) + '-03-31'
ELSE
CAST(DATEPART(YEAR, #startDate) AS VARCHAR(10)) + '-03-31'
END AS fiscalYearEnd;
Edit the #startDate and test the above, it should hopefully work for most cases. I've given it a quick test and it seems to return the expected result.
DATEDIFF function:
ROUND(DATEDIFF(CURRENT_TIMESTAMP, START_DATE)/7, 0) AS weeks
where 0 is the number of decimal.
The problem with WEEKS is that it won't return correct results for dates that cross over January 1st.
i need help on my little problem.
SELECT FORMAT(ServiceDate, 'dd-MM-yyy") AS ServiceDate
FROM Services
WHERE Day(ServiceDate) BETWEEN '1' AND Day(getdate() -2)
AND Month(ServiceDate) =
CASE
WHEN Day(getdate()) <=2
THEN Month(getdate() -1
ELSE Month(getdate())
END
AND Year(ServiceDate) = Year(getdate())
Now the problem is the first and the second of the Month.
The query don't use the last month. It shows the actual month.
I hope its clear what i need.
if we have the 01-06-2016 and i need minus 2, so the query must give me back to the day 30-05-2016
big THX
the output for today with this query
output query
Assuming you are using sql-server, you need to use DATEADD(Day, -2, GETDATE()) for subtracting 2 days from current date.
I think I understand the logic now:
If the current day is the 1st of the month, get all the records from the start of previous month, until 2 days before it ends.
If the current day is the 2nd of the month, get all the records from the start of the previous month until one day before it ends.
If the current day is the 3rd of the month or higher, get all the records from the beginning of the current month until 2 days ago.
Since you are using the FORMAT() function that was introduced in 2012 version, you can also use the EOMONTH() function that was introduced in the same version.
This function returns the date of the end of the month of the date it receives as an argument, and also have a useful optional second argument that specifies the numbers of months to add to the date passed to the function.
Using this function will allow you to write your query without using any functions on the ServiceDate column, thus enabling the use of any indexes defined on this column.
DECLARE #Now datetime = GETDATE()
SELECT FORMAT(ServiceDate, 'dd-MM-yyy') AS ServiceDate
FROM Services
WHERE (
DAY(#Now) <= 2
AND ServiceDate >= DATEADD(DAY, 1, EOMONTH(#Now, -2))
AND ServiceDate < DATEADD(DAY, -(DAY(#Now)-1), EOMONTH(#Now, -1))
)
OR
(
DAY(GETDATE()) > 2
AND ServiceDate >= DATEADD(DAY, 1, EOMONTH(#Now, -1))
AND ServiceDate < DATEADD(DAY, -2, #Now)
)
Compute enddate as 2 days before getdate() and select data in interval from enddate's first of month and enddate.
SELECT FORMAT(ServiceDate, 'dd-MM-yyy") AS ServiceDate
FROM Services
CROSS APPLY (SELECT enddate = DATEADD(D,-2,getdate()) x
WHERE ServiceDate BETWEEN DATEADD(MONTH,DATEDIFF(MONTH,0,x.enddate),0) AND x.enddate
The following SQL statement sets the date for a particular column:
DATEDUE=convert(varchar,GETDATE(),103 )
However, its sets the current date such as 05/08/2015. What I wish to do is to have it in the same dd/mm/yyyy format but to set the day to the 15th and the month to the previous month, so 05/08/2015 should be 15/07/2015 instead.
select CONVERT(VARCHAR(10),
DATEADD(MONTH, DATEDIFF(MONTH, 0, GETDATE())-1, 0) + 14
,103)
Result: 15/07/2015
Important Note
Since DATEDUE column is storing dates, you should really use the sql server DATE data type for storing date values in that column.
if you decide you just need the most recent previous 15th of the month you can use this.
SELECT CONVERT(VARCHAR(10),
DATEADD(DAY,14,DATEADD(MONTH,DATEDIFF(MONTH,0,GETDATE()) -
(CASE WHEN DATEPART(DAY,GETDATE()) > 15 THEN 0 ELSE 1 END), 0)),
103)
05/08/2015 becomes 15/07/2015 and 24/08/2015 becomes 15/08/2015 instead of 15/07/2015
I'm hoping to find a solution for this to automate a report I have. Basically what I'm trying to accomplish here is grabbing a date (first day of previous month, two years ago through last day of previous month current year).
So the date span if running this month would look like this: between 4/1/2013 and 3/31/2015
I have found code to get the date two years ago but I'm not able to also incorporate the month functions... Any help is very much appreciated!
For year I'm using this:
SELECT CONVERT(VARCHAR(25),DATEADD(year,-2,GETDATE()),101)
First day of previous month 2 years ago:
SELECT CONVERT(DATE,dateadd(day, -1, dateadd(day, 1 - day(GETDATE()), GETDATE())))
Last day of last month:
SELECT CONVERT(DATE,DATEADD(month, DATEDIFF(month, 0, DATEADD(year,-2,GETDATE())), 0))
Then just do whatever logic you need with them
Your where clause can look something like this:
where date >= cast(dateadd(year, -2,
dateadd(month, -1, getdate() - day(getdate()) + 1)
) as date) and
date < cast(getdate() - day(getdate()) + 1 as date)
This makes use of the handy convenience that subtracting/adding a number to a datetime is the same as adding a date. The start date says: get the first day of the month, then subtract one month, then subtract two years. This could have been done as dateadd(month, -25, . . .), but I think separating the logic is clearer.
This gives you two dates you are looking for:
SELECT
CAST((DATEADD(yy, -2, DATEADD(d, -1 * DATEPART(dd, getdate()) + 1 , GETDATE() ))) as date) as yourTwoYearsAgoDate,
CAST((DATEADD(d, -1 * DATEPART(dd, GETDATE()), GETDATE())) as date) as yourEndOfLastMonthDate
Given a reference date (e.g. "today"),
declare #today date = '23 April 2015'
The 1st of the month is computed by subtracting 1 less than the day number of the current month:
select first_of_current_month = dateadd(day,1-day(#today),#today)
The last day of the previous month is day 0 of the current month, so to get the last day of the previous month, just subtract the current day number:
select last_of_previous_month = dateadd(day,-day(#today),#today)
Moving two years back is easy:
select two_years_back = dateadd(year,-2, #today )
Putting it all together, this should do you:
declare #today date = '23 April 2015'
select *
first_day_of_current_month = dateadd(day,1-day(#today),#today),
last_day_of_previous_month = dateadd(day, -day(#today),#today) ,
date_from = dateadd(year,-2, dateadd(day,1-day(#today),#today) ) ,
date_thru = dateadd(day, -day(#today),#today)
yielding the expected results:
first_day_of_current_month: 2015-04-01
last_day_of_previous_month: 2015-03-31
date_from : 2013-04-01
date_thru : 2015-03-31
So you should be able to say something like this:
select *
from foo t
where t.transaction_date between dateadd(year,-2, dateadd(day,1-day(#today),#today) )
and dateadd(day, -day(#today),#today)
If you have to deal with datetime values rather than date, its easier to not use between and say something like this:
declare #today date = current_timestamp -- get the current date without a time component
select *
from foo t
where t.transaction_date >= dateadd(year,-2, dateadd(day,1-day(#today),#today) )
and t.transaction_date < dateadd(year, 0, dateadd(day, -day(#today),#today)
[superfluous addition of 0 years added for clarity]
How the calculation happen for MONTH datepart in DATEADD()
Add Month
SELECT '2012-01-29' AS [Date], CAST(DATEADD(MONTH, 1, '2012-01-31') AS DATE) AS NextDate
UNION
SELECT '2012-01-31' AS [Date], CAST(DATEADD(MONTH, 1, '2012-01-31') AS DATE) AS NextDate
UNION
SELECT '2013-01-31' AS [Date], CAST(DATEADD(MONTH, 1, '2013-01-31') AS DATE) AS NextDate
Result
Subtract Month
SELECT '2012-02-29' AS [Date], CAST(DATEADD(MONTH, -1, '2012-02-29') AS DATE) AS PrevDate
UNION
SELECT '2012-03-01' AS [Date], CAST(DATEADD(MONTH, -1, '2012-03-01') AS DATE) AS PrevDate
Result
When I add a Month for the dates 29,30,31 of Jan'2012, I get the same result as February 29. For subtract, for the date 29 Feb'2012, it shows 29 Jan'2012. There is no way to get the dates 30 & 31 of Jan'2012.
I want to know some brief explanation.
The behaviour is explicitly documented in the documentation for DATEADD:
DATEADD (datepart , number , date )
...
If datepart is month and the date month has more days than the return month and the date day does not exist in the return month, the last day of the return month is returned. For example, September has 30 days; therefore, the two following statements return 2006-09-30 00:00:00.000:
SELECT DATEADD(month, 1, '2006-08-30');
SELECT DATEADD(month, 1, '2006-08-31');
As to why it has this behaviour, it all comes down to the fact that variable length months mean that you have to apply some form of tradeoff when performing date maths, and no one "correct" answer exists. Do you think of 31st January as being "the last day of January" or "30 days after the 1st day of January". Both of those are correct ways of thinking about the 31st. But if you change January to February, you now obtain two different dates - 28th or 29th of February for "the last day of February" or 2nd or 3rd of March for "30 days after the 1st day of February".
But functions have to return just one value.
I'm not saying that SQL Server applies either of the above interpretations. What it does do, though, is ensures that if you add, say, 1 month onto a particular date, you can be sure that the resulting date falls in the following month.