Selecting only 'month-day time' from Datetime field in SQL (server 2012) - sql

I'm looking at changing a specific date to a different date in a case statement, however, I only want it to apply to a day, month and time.
For example, I want to get the case statement to change any date which falls on 31/12 # 23:59:00 to 01/01 # 00:00:00 but unless I write the case to include each year for the next 40 years to cover myself, I've not been able to resolve this.
I am writing this from the UK with date format being dd/mm/yyyy (above example is 31st December and 1st January).
The format of the datetime field in the database is 'datetime': 2019-07-01 13:14:47).

I can't tell if you want the return type to be a date or datetime. If a date, you can do:
(case when year(dateadd(minute, 1, datecol)) <> year(datecol)
then datefromparts(year(datecol) + 1, month(datecol), day(datecol))
else cast(datecol as date)
end)
The logic would be similar for a datetime, assuming datecol is already a datetime:
(case when year(dateadd(minute, 1, datecol)) <> year(datecol)
then datefromparts(year(datecol) + 1, month(datecol), day(datecol))
else datecol
end)

If I understand correctly you want to round the dates inside last minute of year into the next year. You can do this:
SELECT datecol, CASE
WHEN MONTH(datecol) = 12 AND DAY(datecol) = 31 AND CAST(datecol AS TIME(3)) >= '23:59:00' THEN CAST(DATEADD(MINUTE, 1, datecol) AS DATE)
ELSE datecol
END
FROM (VALUES
(CAST('2018-12-31 23:58:59.997' AS DATETIME)),
(CAST('2018-12-31 23:59:00.000' AS DATETIME)),
(CAST('2018-12-31 23:59:59.997' AS DATETIME)),
(CAST('2019-01-01 00:00:00.000' AS DATETIME))
) AS v(datecol)
Result:
2018-12-31 23:58:59.997 2018-12-31 23:58:59.997
2018-12-31 23:59:00.000 2019-01-01 00:00:00.000
2018-12-31 23:59:59.997 2019-01-01 00:00:00.000
2019-01-01 00:00:00.000 2019-01-01 00:00:00.000

Related

Get the date for custom day but current month in sql

I am working on some reporting module, where I need to implement the logic which gets a date as below cases -
My table :-
Id
Day
1
8
2
14
3
22
4
29
Now I have to write a query to get result as below -
Case 1- If current date (GETDATE()) is 2022-9-5 00:00:00.000
result
2022-9-8 00:00:00.000
2022-9-14 00:00:00.000
2022-9-22 00:00:00.000
2022-9-29 00:00:00.000
Case 2- If current date (GETDATE()) is 2022-9-16 00:00:00.000
result
2022-10-8 00:00:00.000
2022-10-14 00:00:00.000
2022-9-22 00:00:00.000
2022-9-29 00:00:00.000
Note : The query should work with any month / year.
select dateadd(day, day, eomonth(getdate(), case when day < datepart(day, getdate()) then 0 else -1 end)) as result
from t
result
2022-10-08 00:00:00.000
2022-10-14 00:00:00.000
2022-09-22 00:00:00.000
2022-09-29 00:00:00.000
Fiddle
Some DATEADD willhep, as you first need to know, the first daty of the next month and then you can add the das from your table
The moth seledcted will be determined if the day of the Selct run is smaller than the day in the table
CREATE TABLE table1
([Id] int, [Day] int)
;
INSERT INTO table1
([Id], [Day])
VALUES
(1, 8),
(2, 14),
(3, 22),
(4, 29)
;
4 rows affected
SELECT getdate()
(No column name)
2022-09-20 18:55:21.917
SELECT
DATEADD(DAY, [Day] -1,DATEADD(MONTH, DATEDIFF(MONTH, 0, GETDATE())+
(CASE WHEN DAY(GETDATE()) < [Day] THEN 0 ELSE 1 END), 0))
FROM table1
(No column name)
2022-10-08 00:00:00.000
2022-10-14 00:00:00.000
2022-09-22 00:00:00.000
2022-09-29 00:00:00.000
fiddle
An IF ELSE is probably what you need
https://learn.microsoft.com/en-us/sql/t-sql/language-elements/if-else-transact-sql?view=sql-server-ver16
So if the day is greater than the day in the other table, add one month to the date.

Query based on day-of-week/time range

I'm on SQL Server 2014. I have a table with columns like this:
id (int, PK, identity)
effectiveDate (datetime)
expirationDate (datetime)
1
2022-07-06 18:00:00.000
2022-07-06 23:00:00.000
2
2022-07-08 22:00:00.000
2022-07-09 02:00:00.000
I need to select rows where the current date/time (GETDATE()) lands within the day-of-week/time range represented by these datetimes, beginning on the effectiveDate. So think of row 1 as the range Wednesday 18:00 -> Wednesday 23:00 and row 2 as Friday 22:00 -> Saturday 02:00. (Keep in mind the day-of-week/time range can span multiple days, as in the 2nd row.)
Examples:
GETDATE() = 2022-07-06 19:30:00.000 (i.e Wednesday at 19:30)
Selects row 1
GETDATE() = 2022-07-30 01:00:00.000 (i.e. Saturday at 01:00)
Selects row 2
GETDATE() = 2022-06-30 19:00:00.000 (i.e. Wednesday at 19:00 which matches row 1 on the day-of-week/time range, but is before the effective date)
Selects nothing
I'm not quite sure how to attack this. Maybe we could date adjust each row's effectiveDate and expirationDate as many weeks forward as needed to place the effectiveDate before GETDATE() (assuming the effectiveDate <= GETDATE()). Any thoughts?
The DATEPART with weekday is the key to checking the weekday range. There are two cases. For example, Mon to Wed is different than Wed to Mon. Mon to Wed is easy with a test of Mon<=day<= Wed. For Wed to Mon, it becomes day < Mon or day > Wed. However, this is the same as NOT Mon<=day<=Wed. The query might not be exactly what you need, but should be a good start.
with TestData as (
select *
from (
values
(1, CAST('2022-07-06 18:00:00.000' as datetime), CAST('2022-07-06 23:00:00.000' as datetime)),
(2, CAST('2022-07-08 22:00:00.000' as datetime), CAST('2022-07-09 02:00:00.000' as datetime)),
(3, CAST('2022-07-09 22:00:00.000' as datetime), CAST('2022-07-11 10:00:00.000' as datetime))
) t (id, effectiveDate, expirationDate)
), TestSamples as (
select *
from (
values
(CAST('2022-07-06 19:30:00.000' as datetime)),
(CAST('2022-07-30 01:00:00.000' as datetime)),
(CAST('2022-07-31 01:00:00.000' as datetime)),
(CAST('2022-06-30 19:00:00.000' as datetime)),
(CAST('2022-08-04 19:00:00.000' as datetime))
) t (testDate)
), WeekDayRange as (
select id, effectiveDate, expirationDate,
datename(weekday, effectiveDate) as WDNAME1, datename(weekday, expirationDate) as WDNAME2,
datepart(weekday, effectiveDate) as WD1, datepart(weekday, expirationDate) as WD2
from TestData
) select t.testDate, datename(weekday, t.testDate) as [WDNAME], datepart(weekday, t.testDate) as [WD], r.*
from TestSamples t
left join WeekDayRange r
on (r.WD1 <= r.WD2 and datepart(weekday, t.testDate) BETWEEN r.WD1 and r.WD2)
or (r.WD1 > r.WD2 and not datepart(weekday, t.testDate) BETWEEN r.WD2 and r.WD1)
where t.testDate > r.effectiveDate
testDate WDNAME WD id effectiveDate expirationDate WDNAME1 WDNAME2 WD1 WD2
----------------------- --------- ----------- ----------- ----------------------- ----------------------- --------- --------- ----------- -----------
2022-07-06 19:30:00.000 Wednesday 4 1 2022-07-06 18:00:00.000 2022-07-06 23:00:00.000 Wednesday Wednesday 4 4
2022-07-30 01:00:00.000 Saturday 7 2 2022-07-08 22:00:00.000 2022-07-09 02:00:00.000 Friday Saturday 6 7
2022-07-31 01:00:00.000 Sunday 1 3 2022-07-09 22:00:00.000 2022-07-11 10:00:00.000 Saturday Monday 7 2
I think I got this working with a bit of math:
DECLARE #myGetDate DATETIME;
SET #myGetDate = '2022-07-30 01:00:00.000';
WITH AdjustedWeeklyDates AS (
SELECT
ID,
DATEADD(WEEK, DATEDIFF(day, effectiveDate, #myGetDate) / 7, effectiveDate) AS adjustedEffectiveDate,
DATEADD(WEEK, DATEDIFF(day, effectiveDate, #myGetDate) / 7, expirationDate) AS adjustedExpirationDate
FROM
dbo.myTable
WHERE
effectiveDate <= #myGetDate
)
SELECT
mt.ID, mt.effectiveDate, mt.expirationDate, awd.adjustedEffectiveDate, awd.adjustedExpirationDate
FROM dbo.myTable mt
INNER JOIN AdjustedWeeklyDates awd ON mt.ID = awd.ID
WHERE
awd.adjustedEffectiveDate <= #myGetDate
AND awd.adjustedExpirationDate > #myGetDate
To explain:
DATEDIFF(day, effectiveDate, #myGetDate) returns the number of days between the effectiveDate and the current date. So for example, say it was 20 days ago.
/ 7 gets the number of weeks as an int, since the DATEDIFF returns an int. This also results in the floor of the quotient. So, with our example 20 days / 7, the quotient is about 2.86, but this will result in an even 2
DATEADD adds the number of weeks to bring us up to or before the current date/time. We add the same number of weeks to the expiration date, resulting in the same range as the original effective/expiration dates, which may or may not extend around the current date.
Finally the check for effectiveDate <= #myGetDate guarantees the current date is after or equal to the effective date.

how to generate a table of Monday dates that from a specific Monday to current date in sql

In SQL Server I need to generate a table of Mondays up to the current date.
The starting date is always 2020-04-27, which is a Monday.
For example, if today is 2020-05-25, I need a table like below:
date
0 2020-04-27
1 2020-05-04
2 2020-05-11
3 2019-05-18
4 2019-05-25
If today's date is 2020-05-23, then it's:
date
0 2020-04-27
1 2020-05-04
2 2020-05-11
3 2019-05-18
How I can produce the table like that?
You can use a recursive CTE to generate the list of dates:
WITH mondays AS (
SELECT CAST('2020-04-27' AS date) AS monday
UNION ALL
SELECT DATEADD(WEEK, 1, monday)
FROM mondays
WHERE DATEADD(WEEK, 1, monday) <= GETDATE()
)
SELECT *
FROM mondays
Output:
monday
2020-04-27
2020-05-04
2020-05-11
2020-05-18
Demo on dbfiddle
Note that if you want to generate a list of more than 100 dates, you will need to increase the maximum recursion level (which defaults to 100). This can be done by adding OPTION (MAXRECURSION 0) to the end of the query i.e.
SELECT *
FROM mondays
OPTION (MAXRECURSION 0)
Use master..spt_values this default table to attain this
DECLARE #DATEFROM DATE ='2020-04-27',
#DATETO DATE= '2020-05-25'
SELECT ALLDATES AS MONDATES FROM
(SELECT DATEADD(D, NUMBER, #DATEFROM) AS ALLDATES FROM MASTER..SPT_VALUES
WHERE TYPE = 'P' AND NUMBER BETWEEN 0 AND DATEDIFF(DD, #DATEFROM, #DATETO)) AS D1
WHERE DATENAME(DW, D1.ALLDATES)IN('MONDAY')

Auto pickup dates from SQL Server - T-SQL

I am looking for some T-SQL code that should pick the date which is "One Year back from current date (at the same time last Sunday in the month of January)".
For example:
Current day expected result
2017-02-05 2016-01-31
2017-01-05 2015-01-25
2018-02-19 2017-01-29
2018-01-19 2016-01-31
2019-02-28 2018-01-28
Please note: The year starts from last Sunday in January
I have some T-SQL code which is being used in SQL Server 2014:
select
convert(varchar(10), DATEADD(day, DATEDIFF(day, '19000107', DATEADD(month, DATEDIFF(MONTH, 0, CONVERT(date, CONVERT(VARCHAR(4), (CASE WHEN MONTH(GetDate()) = 1 THEN CONVERT(VARCHAR(4), GetDate(), 112) - 1 ELSE CONVERT(VARCHAR(4), GetDate(), 112) END), 112) + '0101')), 30)) / 7 * 7, '19000107'), 120)
The above code picks the date for current year's (last Sunday in January month). But I want T-SQL code to pick last year's (last Sunday's date in January month) date.
In detail - I want T-SQL code to produce expected result from below table
Current day T-SQL code answer expected result
2017-02-05 2017-01-29 2016-01-31
2017-01-05 2016-01-31 2015-01-25
2018-02-19 2018-01-28 2017-01-29
2018-01-19 2017-01-29 2016-01-31
2019-02-28 2019-01-27 2018-01-28
Any help please.
The best thing for this question is a numbers and date table. This answer shows you how to create one. Such a table is very handsome in many situations...
If I understand this correctly, you want the last Sunday in January of the previous year in all cases? Try this:
DECLARE #dummy TABLE(ID INT IDENTITY,YourDate DATE);
INSERT INTO #dummy VALUES
('2017-02-05'),('2017-01-05'),('2018-02-19'),('2018-01-19'),('2019-02-28');
WITH Years AS
(
SELECT * FROM (VALUES(2010),(2011),(2012),(2013),(2014),(2015),(2016),(2017),(2018),(2019),(2020)) AS t(Yr)
)
,LastSundays AS
(
SELECT Yr AS TheYear
,DATEADD(DAY,(DATEPART(WEEKDAY,LastOfJanuary) % 7)*(-1),LastOfJanuary) AS LastSundayOfJanuary
FROM Years
CROSS APPLY(SELECT CAST(CAST(Yr AS VARCHAR(4)) + '0131' AS DATE)) AS t(LastOfJanuary)
)
SELECT *
FROM #dummy AS d
INNER JOIN LastSundays AS ls ON YEAR(DATEADD(YEAR,-1,d.YourDate))=ls.TheYear;
The result (I do not understand row 2 and 4 completely...)
ID YourDate TheYear LastSundayOfJanuary
1 2017-02-05 2016 2016-01-31
2 2017-01-05 2016 2016-01-31 <--Your sample data is different...
3 2018-02-19 2017 2017-01-29
4 2018-01-19 2017 2017-01-29 <--Your sample data is different...
5 2019-02-28 2018 2018-01-28
Hint You might need to introduce ##DATEFIRST into your calculations...
Here is a way to do it without a date table (which is still a good idea BTW). Tested on all your inputs and it delivers the correct output each time. Obviously you would refactor this a bit as it's longwinded, just to show each step.
/* The input date. */
DECLARE
#input DATE = '2019-02-28';
/* The input date less one year. */
DECLARE
#date_minus_one_year DATE = DATEADD(yy,-1,#input);
/* The year part of the input date less one year. */
DECLARE
#year_date_part INT = DATEPART(yy,#date_minus_one_year);
/* 31 Jan of the previous year. */
DECLARE
#prev_year_jan_eom DATE = CAST(CAST(#year_date_part AS VARCHAR(4))+'-01-31' AS DATE);
/* What day of the week is 31 Jan of the previous year? */
DECLARE
#previous_eom_dw_part INT = DATEPART(dw,#prev_year_jan_eom);
/* Offest 31 Jan to the previous Sunday, won't change if the 31st is itself a Sunday. */
DECLARE
#output DATE = DATEADD(dd,1 - #previous_eom_dw_part,#prev_year_jan_eom);
/* Input and output */
SELECT
#input input
,#output [output];
I didn't think of a way to do it without the conditional in a case. It also uses the trick of casting a numeric year value to a January 1st date.
select case
when
datepart(dayofyear, dt) >
31 - datepart(weekday, dateadd(day, 30, cast(year(dt) as varchar(4))))
then
dateadd(day,
31 - datepart(weekday, dateadd(day, 30, cast(year(dt) as varchar(4)))),
cast(year(dt) as varchar(4))
)
else
dateadd(day,
31 - datepart(weekday, dateadd(day, 30, cast(year(dt) - 1 as varchar(4)))),
cast(year(dt) - 1 as varchar(4))
)
end
from (values
('20100201'), ('20110301'), ('20120401'),
('20130501'), ('20140601'), ('20150701'),
('20160801'), ('20170901'), ('20181001')
) t(dt)
Just for fun (untested)
select
dateadd(week,
-52 * ceil(sign(datediff(day, dt, hs)) + 0.5),
js
)
from
(select <date> dt) as t
cross apply
(
select 31 - datepart(weekday,
datefromparts(year(dt), 1, 31) as js
) t2;
SELECT
convert(varchar(10), DATEADD(day, DATEDIFF(day, '19000107', DATEADD(month, DATEDIFF(MONTH, 0, CONVERT(date, CONVERT(VARCHAR(4), (CASE WHEN MONTH(DATEADD(year,-1,GetDate())) = 1 THEN CONVERT(VARCHAR(4), DATEADD(year,-1,GetDate()), 112) - 1 ELSE CONVERT(VARCHAR(4), DATEADD(year,-1,GetDate()), 112) END), 112) + '0101')), 30)) / 7 * 7, '19000107'), 120)

Calculate exact date difference in years using SQL

I receive reports in which the data is ETL to the DB automatically. I extract and transform some of that data to load it somewhere else. One thing I need to do is a DATEDIFF but the year needs to be exact (i.e., 4.6 years instead of rounding up to five years.
The following is my script:
select *, DATEDIFF (yy, Begin_date, GETDATE()) AS 'Age in Years'
from Report_Stage;
The 'Age_In_Years' column is being rounded. How do I get the exact date in years?
All datediff() does is compute the number of period boundaries crossed between two dates. For instance
datediff(yy,'31 Dec 2013','1 Jan 2014')
returns 1.
You'll get a more accurate result if you compute the difference between the two dates in days and divide by the mean length of a calendar year in days over a 400 year span (365.2425):
datediff(day,{start-date},{end-date},) / 365.2425
For instance,
select datediff(day,'1 Jan 2000' ,'18 April 2014') / 365.2425
return 14.29461248 — just round it to the desired precision.
Have you tried getting the difference in months instead and then calculating the years that way? For example 30 months / 12 would be 2.5 years.
Edit: This SQL query contains several approaches to calculate the date difference:
SELECT CONVERT(date, GetDate() - 912) AS calcDate
,DATEDIFF(DAY, GetDate() - 912, GetDate()) diffDays
,DATEDIFF(DAY, GetDate() - 912, GetDate()) / 365.0 diffDaysCalc
,DATEDIFF(MONTH, GetDate() - 912, GetDate()) diffMonths
,DATEDIFF(MONTH, GetDate() - 912, GetDate()) / 12.0 diffMonthsCalc
,DATEDIFF(YEAR, GetDate() - 912, GetDate()) diffYears
I think that division by 365.2425 is not a good way to do this. No division can to this completely accurately (using 365.25 also has issues).
I know the following script calculates an accurate date difference (though might not be the most speedy way):
declare #d1 datetime ,#d2 datetime
--set your dates eg:
select #d1 = '1901-03-02'
select #d2 = '2016-03-01'
select DATEDIFF(yy, #d1, #d2) -
CASE WHEN MONTH(#d2) < MONTH(#d1) THEN 1
WHEN MONTH(#d2) > MONTH(#d1) THEN 0
WHEN DAY(#d2) < DAY(#d1) THEN 1
ELSE 0 END
-- = 114 years
For comparison:
select datediff(day,#d1 ,#d2) / 365.2425
-- = 115 years => wrong!
You might be able to calculate small ranges with division, but why take a chance??
The following script can help to test yeardiff functions (just swap cast(datediff(day,#d1,#d2) / 365.2425 as int) to whatever the function is):
declare #d1 datetime set #d1 = '1900-01-01'
while(#d1 < '2016-01-01')
begin
declare #d2 datetime set #d2 = '2016-04-01'
while(#d2 >= '1900-01-01')
begin
if (#d1 <= #d2 and dateadd(YEAR, cast(datediff(day,#d1,#d2) / 365.2425 as int) , #d1) > #d2)
begin
select 'not a year!!', #d1, #d2, cast(datediff(day,#d1,#d2) / 365.2425 as int)
end
set #d2 = dateadd(day,-1,#d2)
end
set #d1 = dateadd(day,1,#d1)
end
You want the years difference, but reduced by 1 when the "day of the year" of the future date is less than that of the past date. So like this:
SELECT *
,DATEDIFF(YEAR, [Begin_date], [End_Date])
+ CASE WHEN CAST(DATENAME(DAYOFYEAR, [End_Date]) AS INT)
>= CAST(DATENAME(DAYOFYEAR, [Begin_date]) AS INT)
THEN 0 ELSE -1 END
AS 'Age in Years'
from [myTable];
For me I calculate the difference in days
Declare #startDate datetime
Declare #endDate datetime
Declare #diff int
select #diff=datediff(day,#startDate,#endDate)
if (#diff>=365) then select '1Year'
if (#diff>=730) then select '2Years'
-----etc
I have found a better solution. This makes the assumption that the first date is less than or equal to the second date.
declare #dateTable table (date1 datetime, date2 datetime)
insert into #dateTable
select '2017-12-31', '2018-01-02' union
select '2017-01-03', '2018-01-02' union
select '2017-01-02', '2018-01-02' union
select '2017-01-01', '2018-01-02' union
select '2016-12-01', '2018-01-02' union
select '2016-01-03', '2018-01-02' union
select '2016-01-02', '2018-01-02' union
select '2016-01-01', '2018-01-02'
select date1, date2,
case when ((DATEPART(year, date1) < DATEPART(year, date2)) and
((DATEPART(month, date1) <= DATEPART(month, date2)) and
(DATEPART(day, date1) <= DATEPART(day, date2)) ))
then DATEDIFF(year, date1, date2)
when (DATEPART(year, date1) < DATEPART(year, date2))
then DATEDIFF(year, date1, date2) - 1
when (DATEPART(year, date1) = DATEPART(year, date2))
then 0
end [YearsOfService]
from #dateTable
date1 date2 YearsOfService
----------------------- ----------------------- --------------
2016-01-01 00:00:00.000 2018-01-02 00:00:00.000 2
2016-01-02 00:00:00.000 2018-01-02 00:00:00.000 2
2016-01-03 00:00:00.000 2018-01-02 00:00:00.000 1
2016-12-01 00:00:00.000 2018-01-02 00:00:00.000 1
2017-01-01 00:00:00.000 2018-01-02 00:00:00.000 1
2017-01-02 00:00:00.000 2018-01-02 00:00:00.000 1
2017-01-03 00:00:00.000 2018-01-02 00:00:00.000 0
2017-12-31 00:00:00.000 2018-01-02 00:00:00.000 0