SQL Server Parameter Date Range Issues - sql-server

I am using the following query to get the difference between two dates. The date ranges are tolling 12 months interval.
CY stand for Current year while PY stands for Previous Year. The dates in Current year are used to calculate the previous year dates
When I execute my query I have the following output, where the month is 11 and day 364. But I want my months to be twelve and the day 365 or (366 for leap year).
DECLARE #CY_StartDate date =CAST(DATEADD(MONTH, DATEDIFF(MONTH, -1, GETDATE())-13, 0) AS DATE),
#CY_EndDate date =CAST(DATEADD(MONTH, DATEDIFF(MONTH, -1, GETDATE())-1, -1) AS DATE); --- Rolling 12 months
DECLARE #PY_startDate date =DATEADD(YEAR,-1,#CY_StartDate),
#PY_EndDate date =DATEADD(YEAR,-1,#CY_EndDate)
SELECT
#CY_StartDate AS CY_Start,
#CY_EndDate AS CY_End,
#PY_StartDate AS PY_Start,
#PY_EndDate AS PY_End,
DATEDIFF(year, #CY_StartDate, #CY_EndDate) AS yr,
DATEDIFF(month, #CY_StartDate, #CY_EndDate) AS month,
DATEDIFF(day, #CY_StartDate, #CY_EndDate) AS day
Current Output
CY_Start CY_End PY_Start PY_End yr month day
2017-10-01 2018-09-30 2016-10-01 2017-09-30 1 11 364
Expected output
CY_Start CY_End PY_Start PY_End yr month day
2017-10-01 2018-09-30 2016-10-01 2017-09-30 1 12 365

The values you are getting make sense. DATEDIFF counts the ticks between 2 dates, where a tick is the value of the first parameter. So, for example: DATEDIFF(MONTH, '20180101','20180228') will return 1, as only 1 tick has occured (2 - 1 = 1). Seems, here, you simply need to add 1:
DECLARE #CY_StartDate date =CAST(DATEADD(MONTH, DATEDIFF(MONTH, -1, GETDATE())-13, 0) AS DATE),
#CY_EndDate date =CAST(DATEADD(MONTH, DATEDIFF(MONTH, -1, GETDATE())-1, -1) AS DATE); --- Rolling 12 months
DECLARE #PY_startDate date =DATEADD(YEAR,-1,#CY_StartDate),
#PY_EndDate date =DATEADD(YEAR,-1,#CY_EndDate)
select
#CY_StartDate as CY_Start,
#CY_EndDate AS CY_End,
#PY_StartDate AS PY_Start,
#PY_EndDate AS PY_End,
DATEDIFF(year,#CY_StartDate,DATEADD(DAY,1,#CY_EndDate)) as yr,
DATEDIFF(month,#CY_StartDate,DATEADD(DAY,1,#CY_EndDate)) as month,
DATEDIFF(day,#CY_StartDate,DATEADD(DAY,1,#CY_EndDate)) as day
The reason I used a further DATEADD is because this makes it consistent with every expression. The value of yr was correct, however, for dates like 20170101 and 20171231, the value of yr would be 0. Hence adding 1 day the the value of #CY_EndDate makes this far more reliable, should the dates move.

Common sense. How many numbers are there between 1 and 10 including both? You might say that there are 10 - 1 = 9 which is incorrect. The correct answer is (10 - 1) + 1 = 10.
Likewise, if you have two inclusive dates e.g. 2017-10-01 and 2018-09-30 you add one to DATEDIFF(DAY, '2017-10-01', '2018-09-30') to get 365 instead of 364.
However, as suggested in the other answer, it is much better to the end date exclusive (not counted) which makes date calculations straight forward. In your example, you should add 1 day to the last date so that you have [2017-10-01, 2018-10-01) and DATEDIFF will produce desired results.

Related

Flooring SQL dates to the first of the month

I have dates in the following format:
2019-09-01 00:00:00.000
2019-09-02 00:00:00.000
2019-10-22 00:00:00.000
I want to floor the dates to get the following:
2019-09-01 00:00:00.000
2019-09-01 00:00:00.000
2019-10-01 00:00:00.000
How can I do this in a select statement? I tried DATEPART but I can only extract the month number.
Another option: Use DateFromParts:
SELECT DATEFROMPARTS(YEAR(DateColumn), MONTH(DateColumn), 1) As FirstOfMonth
FROM TableName;
I prefer this option over the EOMonth() approach suggested in gvee's answer just because it's more readable and doesn't involve any date calculations.
EOMonth() is your friend!
SELECT '2019-10-22 00:00:00.000' AS original_value
, EOMonth('2019-10-22 00:00:00.000') AS end_of_month
, DateAdd(dd, 1, EOMonth('2019-10-22 00:00:00.000')) AS start_of_next_month
, DateAdd(mm, -1, DateAdd(dd, 1, EOMonth('2019-10-22 00:00:00.000'))) AS start_of_this_month
;
To keep the datetime format the same, you can use DATEADD(month, DATEDIFF(month, 0, your_date_column), 0)
This method counts the months from day 0 (01/01/1900) to your original date, then adds that number of months back to day 0, which returns the beginning of the month
There are no straight forward function to find the first day of a month in SQL but one can make use of LAST_DAY() function to calculate it by using these steps:
First, get the last day of the month of a date.
Second, add 1 day to get the first day of the next month using DATE_ADD() function
Third, subtract 1 month to get the first day of the month of the date.
At last, format the date calculated into yyyy-mm-dd HH:mm:ss format as required
The following query illustrates how to get the first day of the month of 2017-07-14.
SELECT DATE_FORMAT(DATE_ADD(DATE_ADD(LAST_DAY('2017-07-14'),
INTERVAL 1 DAY),
INTERVAL - 1 MONTH),
"%Y-%m-%d %T") AS first_day;
I used the following links to write the answer.
https://www.mysqltutorial.org/mysql-last_day/ ,
https://www.w3schools.com/sql/func_mysql_date_format.asp

Convert the date functions code to snowflake

This is sql server code, I have to convert this where condition to snowflake code, can I know how do we convert this into snowflake code and also I need explanation please.
where CONVERT(date, CONVERT(VARCHAR(8), 19000000+datecolumn)) >= cast(CONVERT(VARCHAR(25),DATEADD(MONTH, DATEDIFF(MONTH, 0, GETDATE()) -1 , 0),120) as date)
and CONVERT(date, CONVERT(VARCHAR(8), 19000000+datecolumn)) <= cast(CONVERT(VARCHAR(25), DATEADD(MONTH, DATEDIFF(MONTH, -1, GETDATE())-1, -1),120) as date)
so the left side is the same for both CONVERT(date, CONVERT(VARCHAR(8), 19000000+datecolumn))
so the outer most layer is a convertion to date which is snow falk can also be done via ::date and is side of that there is a conversion of a number to a 8 character string CONVERT(VARCHAR(8), 19000000+datecolumn) which is in a format that can be considered a date, and has the input as 19000000 + datecolumn so I would guess you date format is YYYYMMDD and datecolumn is YYMMDD since 1900. Which in snowflake would use the explicit form of TO_DATE
TO_DATE( ( 19000000 + datecolumn )::text, 'YYYYMMDD')
both the right hand sides are also comparing to dates, thus the outer CAST(... as date) which is the same as the ::text for I use above, see CAST
so the inner most part is DATEDIFF(MONTH, 0, GETDATE()) which is the number of months since beginning of time in your DB timeframe, and the current date in months, with 1 is subtracted from, and that many months are added since 0 in DB timeframe, thus DATEADD(MONTH, DATEDIFF(MONTH, 0, GETDATE()) -1, 0) is the begin of the prior month. As today is December 22nd (if we pretend this year is year 0 in DB time) we are in month 12 which we minus 1 from thus add 11 months to 0 we get the date 2019-11-01, now 0 seems to be '1900-01-01' accord to the post, and I am going to guess the numeric value is whole days (as it's seems to be Microsoft's style), which would make the -1 start be the year '1899-12-31' and thus while we are finding one month month of time due to the date being a month prior, the date we are starting from is the end of the month, thus the second statement appears to be "end of the prior month" but I am not sure how SQL Server handles in the prior month was Feb, and thus if the end date is truncated to end of Feb, or if you end up 31 days after the start of the 1st of Feb.
Ether way that date is then turned to a text and then converted to date
It would again make more sense to just get the CURRENT_DATE(), truncate that to the month, and then step backward. And if you want the end of the prior month truncate and step back a day, or if you want 31 days, step forward that amount.
Thus
dateadd(month, -1, date_trunc('month', current_date())) as first_day_of_prior_month
dateadd(day, -1, date_trunc('month', current_date())) as last_day_of_prior_month
which is actually what the snowflake date-time help suggests

How to find a date in sql of previous 10 year first day of year to todays date

I writing a query where I need to get a data from last 10 year i.e. (01/01/2009) to todays (07/04/2019). But the query I have written is returning the date 07/04/2009.
This is my code that I have written:
(SELECT DATEADD(YEAR,-10,GETDATE()))
How can I make it work so it can return date of 01/01/2009 to today's date.
(SELECT DATEADD(YEAR,-10,GETDATE()))
I expect to get the result 01/01/2009 to 07/04/2019
You could make use of date from parts function as well.
select dateadd(year, -10, datefromparts(year(getdate()),1,1))
Output:
2009-01-01
You could use:
SELECT GETDATE(), -- current date
DATEADD(yy, DATEDIFF(yy, 0, DATEADD(YEAR,-10,GETDATE())), 0) -- 1st day of year 10 years ago
db<>fiddle demo

Counting datediff from 2 date

I have 2 datetime fields, NEW_EMPLOYMENT_STARTDATE and NEW_EMPLOYMENT_ENDDATE
When I calculate the difference in months using datediff with these values for startdate and enddate:
NEW_EMPLOYMENT_STARTDATE = 2017-15-01
NEW_EMPLOYMENT_ENDDATE = 2018-14-01
With this query:
DATEDIFF(MONTH, NEW_EMPLOYMENT_STARTDATE, NEW_EMPLOYMENT_ENDDATE)
It returns 12 months, but when I have values like this:
NEW_EMPLOYMENT_STARTDATE = 2017-01-01
NEW_EMPLOYMENT_ENDDATE = 2017-31-12
It returns 11 months.
How can I get 12 months? I am using this query:
DATEDIFF(MONTH, NEW_EMPLOYMENT_STARTDATE, NEW_EMPLOYMENT_ENDDATE)-
CASE
WHEN DATEPART(DAY, NEW_EMPLOYMENT_ENDDATE) > DATEPART(DAY, NEW_EMPLOYMENT_STARTDATE)
THEN 0
ELSE 0 END AS MONTH_DIFF
It still returns 11 months.
EDIT:
According to my case, the value of the end date always less 1 day from start date, so i make a trick to check condition with case when like this:
CASE
WHEN DATEPART(DAY, NEW_EMPLOYMENT_ENDDATE) > DATEPART(DAY, NEW_EMPLOYMENT_STARTDATE)
THEN DATEDIFF(MONTH, NEW_EMPLOYMENT_STARTDATE, NEW_EMPLOYMENT_ENDDATE)+1
ELSE DATEDIFF(MONTH, NEW_EMPLOYMENT_STARTDATE, NEW_EMPLOYMENT_ENDDATE)
END AS DATEDIF
i add + 1 value to the end date so i ta can be round to next month, give feedback from my solution sir thanks
You're expectations are incorrect. When you do a DATEDIFF using MONTH, it does not consider the day portion of the dates. Consider that it is simply considering the difference in the month numbers only, regardless of the day specified.
This query:
SELECT DATEDIFF(MONTH, '20170101', '20171231') MonthsDiff
Is equivalent to this:
SELECT DATEPART(MONTH, '20171231') - DATEPART(MONTH, '20170101') MonthsDiff
The documentation for DATEDIFF states:
DATEDIFF ( datepart , startdate , enddate )
The first option is DATEPART:
datepart
Is the part of startdate and enddate that specifies the type of boundary crossed.
If you want something closer to what you expect, you can do a simple calculation based on performing the DATEDIFF in days, the dividing it by the approximate number of days in a month.
SELECT DATEDIFF(DAY, '20170101', '20171231') / ( 365 / 12 ) MonthsDiff
This will round the output to the closest month number, it all depends on how accurate you want to be. If you want months as a decimal for greater accuracy then run the below:
SELECT DATEDIFF(DAY, '20170101', '20171220') / ( 365.00 / 12 ) MonthsDiff
Note: This does not take into account leap years, for larger date ranges that might include leap years, which will make a minor difference to the accuracy.
datediff() does something very particular. It counts the number of "boundaries" between two date/time values. In your case, there are eleven boundaries -- one for each month in the year before December.
This behavior is not necessarily intuitive. If you add one day to each of your dates:
NEW_EMPLOYMENT_STARTDATE = '2017-01-02' (YYYY-MM-DD is standard format)
NEW_EMPLOYMENT_ENDDATE = '2018-01-01'
Then you will have 12 months.
If you want to round up, you can play with the dates. One method would be to normalize the first value to the beginning of the month and then add 15 days to the second value:
DATEDIFF(MONTH,
DATEADD(DAY, 1 - DAY(NEW_EMPLOYMENT_STARTDATE), NEW_EMPLOYMENT_STARTDATE)
DATEADD(DAY, 15 + 1 - DAY(NEW_EMPLOYMENT_STARTDATE), NEW_EMPLOYMENT_ENDDATE)
)
This would happen to work for the two examples you give.
Please use this select to achieve your desired result. You can use table columns instead of variables:
declare #new_employment_startdate datetime = convert (datetime, '2017-01-01', 121);
declare #new_employment_enddate datetime = convert (datetime, '2018-01-14', 121);
select
datediff(month, #new_employment_startdate, #new_employment_enddate)
+ case when
datediff(month, dateadd(ms, -3, dateadd(dd, datediff(dd, 0, #new_employment_startdate), 0)), #new_employment_startdate) = 1
and datediff(month,#new_employment_enddate , dateadd(dd, datediff(dd, 0, #new_employment_enddate) + 1, 0)) = 1
then 1
else 0
end;
Some explanations:
I check or start date is first month day AND end date is last month day. At this case I add +1 to standard datediff by month.
You can better understand my used datetime manipulations by using these example queries: https://gist.github.com/runnerlt/60636b029ab47845fdfd8924ed482e61
You need to add 1 more day in your End Date.
DATEDIFF(MONTH, NEW_EMPLOYMENT_STARTDATE, DATEADD(DD,1,NEW_EMPLOYMENT_ENDDATE))
You could match the output with MS Excel.

To get the previous month's last date till last second

I want to get last month's last date (whether 30 or 31) and time till last second, whenever the query is executed.
Eg. 11/30/2015 11:59:59 PM
So I have a query like
SELECT DATEADD(ss, (60*60*24)-1, DATEADD(MONTH, DATEDIFF(MONTH, 0, GETDATE()), -1))
It solves my problem. But what is the difference between the query written above and the one below, when I change the DATEDIFF part and replace 0 with 1?
SELECT DATEADD(ss, (60*60*24)-1, DATEADD(MONTH, DATEDIFF(MONTH, 1, GETDATE()), -1))
Will both of these queries gives the same result whenever they are run, or which should I consider as the permanent solution?
Do NOT do this; attempt to get the "last second" of the last day of the previous month
I make this bold statement on the assumption you are attempting to use BETWEEN and you are concerned with the accuracy of something like this:
select sum(value) from Atable
where [Adate] BETWEEN '20151201' AND '21051231 23:59:59'
But the complexity of arriving at the last point in time on the last day of any month is solved so easily by using the first day of the next month instead. All that you also need to do is drop the use of BETWEEN. Like this:
select sum(value) from Atable
where [Adate] >= '20151201' and [Adate] < '21060101'
LESS THAN "the first day of the this month"
That is how you solve your conundrum.
& by the way: The precision (accuracy) of smalldatetime, datetime and datetime2 all differ, all the more reason not to use BETWEEN.
see "Be careful about rounding errors." at http://sqlmag.com/t-sql/t-sql-best-practices-part-2
Specifically, do this:
DateLogged < SELECT DATEADD(MONTH, DATEDIFF(MONTH, 0, GETDATE()), 0)
This will be 100% accurate for date, smalldatetime, datetime and datetime2 columns.
Here is another attempt to explain why LESS THAN [the_next_day_at_00:00:00+0000000] is accurate and using 22:59:59 is NOT accurate. Please take note of the sample data accuracy
SQL Fiddle
MS SQL Server 2014 Schema Setup:
Query 1:
DECLARE #Tbl TABLE
( [ID] int identity(1,1)
, [DT_a] datetime
, [DT_b] datetime
, [DT_c] datetime2
)
INSERT INTO #Tbl
([Dt_a], [Dt_b], [Dt_c])
VALUES
(
'20151231 23:59:59'
, '20151231 23:59:59.997'
, '20151231 23:59:59.9999999'
)
select
'where [DT_b] <= 20151231 23:59:59' as FilterString
, max([Dt_a]) as [Dt_a]
, max([Dt_b]) as [Dt_b]
, max([Dt_c]) as [Dt_c]
from #Tbl
where [DT_b] <= '20151231 23:59:59'
UNION ALL
select
'where [DT_b] < 20160101'
, max([Dt_a]) as [Dt_a]
, max([Dt_b]) as [Dt_b]
, max([Dt_c]) as [Dt_c]
from #Tbl
where [DT_b] < '20160101'
Results:
| FilterString | Dt_a | Dt_b | Dt_c |
|-----------------------------------|----------------------------|----------------------------|-----------------------------|
| where [DT_b] <= 20151231 23:59:59 | (null) | (null) | (null) |
| where [DT_b] < 20160101 | December, 31 2015 23:59:59 | December, 31 2015 23:59:59 | 2015-12-31 23:59:59.9999999 |
Data accuracy
smalldatetime: one minute
datetime: rounded to increments of .000, .003, or .007 seconds
datetime2: 100 nanoseconds
To avoid possible errors from rounding by time units DO NOT USE <= 23:59:59
Instead use LESS THAN [the_next_day]
AND, as a consequence AVOID USING BETWEEN for date ranges.
See this link for how DATEDIFF is used or should be used in SQL Server. The 2nd argument, the one which does not seem to make a difference in your case, is supposed to be the start date which is subtracted from the end date (getdate()) to get the difference and then converted to months. I would try and use this function the typical way and provide a proper start date.
Also below is an alternative way of getting the same result
SELECT DATEADD(ss, -1, '01/' + CONVERT(VARCHAR, DATEPART(MONTH, getdate())) + '/' + CONVERT(VARCHAR, DATEPART(YEAR, getdate())));
It is because of DATEDIFF( MONTH, 0, GETDATE()) Function
If you use an integer as the second argument, this is interpreted as the number of days since 1900-01-01 regardless of the Interval you are using in the datediff function.
For eg:
SELECT YEAR(0), MONTH(0), DAY(0);
year month day
1900 1 1
Now if I Increment 0 to 1 in year, month, day
SELECT YEAR(1), MONTH(1), DAY(1);
year month day
1900 1 2
Now if I Increment values to 365,
SELECT YEAR(365), MONTH(365), DAY(365);
year month day
1901 1 1
You can see the Year got incremented by 1.
there are many ways to find out the previous month's last date. Here is the one I am using.
SELECT DATEADD(s,-1,DATEADD(mm, DATEDIFF(m,0,GETDATE()),0))
Well it would be expected that substracting one millisecond to the first day of the current month you would get the last millisecond of the previous month but it doesn't work that whay, with datediff millisecond,-1 you still get the first day of the month you have to do datediff millisecond,-2 to reach 997 milliseconds, no way to get 999 nor 998.(without using text).
select dateadd(MILLISECOND,-2,dateadd(month, datediff(month, 0, getdate()), 0))
And you get 2020-01-31 23:59:59.997
To get last second of current month use:
SELECT DATEADD(MILLISECOND, -10, CAST (EOMONTH(GETDATE()) AS DATETIME))
and you get:
2021-12-30 23:59:59.000
Explanation: takes begin of next month (2021-12-31) and convert to datetime (2021-12-31 00:00:00.000) then takes 1 second to get (2021-12-30 23:59:59.000)

Resources