How can force SQL DateDiff function to stop rounding up? - sql

I am working on a query that tries to count from an exact date to an exact date. Today is 2/16/2022. Between 6/30/2020 and 2/16/2022 19 months have gone by. My DateDiff query returns 20? How can I force DateDiff function to return 19?
select datediff(month, '6/30/2020', getdate())

As per documentationSQL Docs, datediff returns
The int difference between the startdate and enddate, expressed in the boundary set by datepart.
which in your example is the number of months from June '20 to February '22 which is indeed 20. User user716255's code is pointing into the right direction in that it uses the first of each month in question. If your intention however is to really know how many months elapsed between two dates, the code should be amended like so:
(sorry, need to correct my original answer, as I misread the code from the other answer)
declare #start date
declare #end date
set #start = '20200630'
set #end = '20220216'
select datediff(month, dateadd(day, -day(#start)+1, #start), dateadd(day, -
day(#end)+1,#end))+CASE WHEN DAY(#end)>DAY(#start) THEN 1 ELSE 0 END
(with dates written in a format more useful for our international readers...)
The original coude would fail if start date would i.e. be 31st of July and the end date in February (as -31+1 would lead to a date in January)

Here is the solution that worked for me.
declare #start date
declare #end date
set #start = '6/30/2020'
set #end = '2/16/2022'
select datediff(month, dateadd(day, -day(#start)+1, #start), dateadd(day, -day(#start)+1,#end))
link to fiddle

Related

New to SQL Server looking for assistance for a datetime conversion

this is my first submission on Stack Overflow, and I am open to any suggestions on structuring my questions.
I am new to SQL Server, and I have a line of code I that I don't understand.
Can someone please explain this?
declare #prior_year datetime = convert(date, '12/31/' + convert(varchar, datepart(yy, #start_date) - 1))
This is a problematic way to determine the last day of the year before the date(time) stored in some variable.
convert(date, '12/31/' + convert(varchar, datepart(yy, #start_date) - 1))
In English, working outward:
datepart(yy, #start_date)
Tell me the year of the variable #start_date. This is poor form because, if we mean year, we should spell out YEAR (see "Date Parts" in this post). I said this already once today, but this is just laziness, it's like "I'm going to type just enough characters to avoid an unexpected result, but not enough characters to make my intent clear." Worse is YYYY, which I see often - you have a choice between YYYY and YEAR, why not type the one that's actually a word?
datepart(yy, #start_date) - 1
--------------------------- ^^^
Subtract one from that year.I don't have any issues here, but it may be clearer to use an explicit DATEADD() against the variable first, and then extracting the year from that, since things like <some date thing> - 1 can be misread as an attempt to subtract a day (also covered in the shorthand post referenced above).
convert(varchar, datepart(yy, #start_date) - 1))
--^^^^^^^^^^^^^^^^
Convert that explicitly to a string.
Also poor form here because we should always specify the length of variable-width data. In some cases this can lead to unexpected truncation. See this post.
'12/31/' + convert(varchar, datepart(yy, #start_date) - 1))
--^^^^^^^^^^
Prefix that year with 12/31 to produce a mm/dd/yyyy string.
More poor form, because this then assumes the user has US English regional settings, MDY dateformat, etc. If you're going to insist on building a string that represents a date, always use a standard, unambiguous format: YYYYMMDD. (And FWIW YYYY-MM-DD is ambiguous, try it with SET LANGUAGE FRENCH;). See this post.
convert(date, <the rest>)
Converts the whole expression to a date.
A better solution
Ideally we should not be using strings anywhere along the line for any of this. We have built-in functions that provide all kinds of native date handling capabilities without having to worry about languages, regional settings, date format preferences, or string lengths:
SELECT DATEFROMPARTS(YEAR(#start_date) - 1, 12, 31);
-- or, more explicitly:
SELECT DATEFROMPARTS(DATEPART(YEAR, #start_date) - 1, 12, 31);
The other way you can think about this is that the last day of last year is the same day as the day before the first day of this year. Thinking about it in these terms can make it much easier to conceptualize when you are doing things like prior month, where determining the last day of the previous month is more tedious. Or if you are finding endpoints for range queries, because the end of the current reporting period is never as deterministic as the start of the next reporting period. More on that here.
SELECT DATEADD(DAY, -1, DATEFROMPARTS(YEAR(#start_date), 1, 1));
Also of potential use:
Dating Responsibly
Simplify Date Period Calculations in SQL Server
SQL Server DateTime Best Practices
Four short videos in this series
Reading this from the inside out:
We're first finding the year of the date using DATEPART and then subtracting 1 so for a value for today's date: 2020-08-18 we'd be getting an integer value of 19
datepart(yy, '2020-08-18') - 1)
We're then using convert on that value to change it to a varchar:
convert(varchar, 19)
We're then using that new varchar to create a string:
'12/31/' + '19'
Finally we're using convert again to create a date from the string
convert(date, '12/31/19')
Tony,
For completeness please provide the definition and assignment for #start_date. Assuming the statement you provided works, it is probably defined something like
declare #start_date date
set #start_date = '07-01-2020'
Working from the inside out, we can then break the statement down like so ...
This will extract the year value
datepart(yy, #start_date)
This will subtract 1 from the #start_date year value, assuming 2020, this returns 2019
convert(varchar, datepart(yy, #start_date) - 1)
And then this will convert that to the last day of the previous year.
convert(date, '12/31/' + convert(varchar, datepart(yy, #start_date) - 1))
So the statement simply sets the new field to the last day of the prior year.
declare #prior_year datetime = convert(date, '12/31/' + convert(varchar, datepart(yy, #start_date) - 1))
This SQL code declares a #prior_year datetime variable that is hard coded to 12/31/. datepart is used to extract the previous year from another datetime variable #start_date` that is passed in and tacks the returned value onto the end of the '12/31/' hard coded string. So; its really just a formula of 12/31/(#start_date prior year)
So if #start_date is 8/20/2020
You will end up with the output of 12/31/2019
declare #start_date datetime = getdate()
declare #prior_year datetime = convert(date, '12/31/' + convert(varchar, datepart(yy, #start_date) - 1))
select #prior_year
Result:

SQL for last six "full" months

I have table containing one datetime column. I need to return rows for only last 6 months. This can be done by
WHERE CloseTime >= DATEADD(Month, DATEDIFF(Month, 0, DATEADD(m, - 6, CURRENT_TIMESTAMP)), 0)
This gets me data for the month I am starting this script + 6 last months. So e.g. if I run this script today, Ill get the data for this month + all previous months till April (04).
Now I need to modify the condition so if I run the script today, the data will be obtained only for months 03-09 only, exluding days in this month (10).
Any advice, please?
If you want to have the previous 6 months regardless of whether today is the 1st, 3rd, 9th, 29th, whatever, then just subtract 7 months. Here is one way to do that: get the first of the month into a variable, then use an open-ended range in the query.
DECLARE #ThisMonth DATETIME;
SET #ThisMonth = DATEADD(MONTH, DATEDIFF(MONTH, '19000101', GETDATE()), '19000101');
SELECT...
WHERE CloseTime >= DATEADD(MONTH, -7, #ThisMonth)
AND CloseTime < #ThisMonth;
You could also use 0 in place of '19000101' but I prefer an explicit date than implicit shorthand (it was a very tough habit to break).
If you really don't like variables, then you can make the query a lot more complex by repeating the expression to calculate the first of this month (and in the start of the range, subtract 7 from the number of months):
SELECT...
WHERE CloseTime >= DATEADD(MONTH, DATEDIFF(MONTH, '19000101', GETDATE())-7, '19000101')
AND CloseTime < DATEADD(MONTH, DATEDIFF(MONTH, '19000101', GETDATE()), '19000101');
Yuck. Variables make this much tidier.
When creating queries you do not want to use a function on the search column since it will result in a full table scan.
The solution works and should pick up any index on CloseTime.
-- Get me data in months 3 (mar) to 9 (sep) of this year
select
*
from
my_table
where
CloseTime between
DATEADD(d, -1, '03-01-2013') and DATEADD(d, +1, '09-20-2013')
If the table is small and a full table scan is not a issue, a simple solution is to use the MONTH function.
-- Get me data in months 3 (mar) to 9 (sep) of this year
select
*
from
my_table
where
MONTH(CloseTime) IN (3,4,5,6,7,8,9) and YEAR(CloseTime) = 2013
I looked at Aaron's article. Its a very good read.
I wondered if there was some new function that was not tested since that article.
If you are using 2012, why not use the format function? Logically, you want date variables with a 01 day. The query plan still gets a clustered index scan.
Let's see how this solution stacks up using Aaron's test database.
-- Use the sample
use [DateTesting]
go
-- Johns - log by 6 months
CREATE PROCEDURE dbo.Johns_LogBy6Months
#date SMALLDATETIME
AS
BEGIN
SET NOCOUNT ON;
DECLARE #cmp_date SMALLDATETIME = format(#date, 'yyyyMM01');
DECLARE #c INT;
SELECT #c = COUNT(*)
FROM dbo.SomeLogTable
WHERE DateColumn >= dateadd(m, -7, #cmp_date)
AND DateColumn < #cmp_date
END
GO
-- Aarons - log by 6 months
CREATE PROCEDURE dbo.Aarons_LogBy6Months
#date SMALLDATETIME
AS
BEGIN
SET NOCOUNT ON;
DECLARE #c INT;
DECLARE #cmp_date SMALLDATETIME = DATEADD(MONTH, DATEDIFF(MONTH, '19000101', #date), '19000101');
SELECT #c = COUNT(*)
FROM dbo.SomeLogTable
WHERE
DateColumn >= dateadd(m, -7, #cmp_date)
AND DateColumn < #cmp_date
END
GO
Lets make 1000 calls to the functions.
-- Sample calls x 1000
PRINT CONVERT(char(23), GETDATE(), 121);
GO
EXEC dbo.Johns_LogCountByDay #date = '20091005';
GO 1000
PRINT CONVERT(char(23), GETDATE(), 121);
GO
EXEC dbo.Aarons_LogBy6Months #date = '20091005';
GO 1000
PRINT CONVERT(char(23), GETDATE(), 121);
GO
Here are the executions times.
2013-10-10 11:58:49.547
Beginning execution loop
Batch execution completed 1000 times.
2013-10-10 11:58:52.837
Beginning execution loop
Batch execution completed 1000 times.
2013-10-10 11:58:55.883
In summary a call to the new format() function and a implicit cast to a small date time takes a little more time than a dateadd() and datediff() with two string (date) literals.
The format() solution seems more intuitive or self documenting to me. The time difference was 3.3 versus 3.0 sec.
I have to give the speed test a win to Aaron's solution. Stick with in-equality comparisons of date variables. They are faster.
In short, I will have to fix my bad habits.
I had a similar request. I needed last six months and next six months of appointments. But just like you I needed the full months. So a simple getdate +- 180 wouldn't do.
I took a more simple approach. I get the year and month and turn it into a number like 201912. Then do a between clause. Its dynamic and I get full months.
I'm sure there's more sophisticated approaches, but this is what I came up with.
WHERE Year([ApptDate])*100 + Month([ApptDate]) between Year(getdate()-180)*100 + Month(getdate()-180) and Year(getdate()+180)*100 + Month(getdate()+180)

How to calculate a fiscal month from middle of the month

I want a one month range from 2-15-13 to 2-28-13, but Dateadd(mm,-1,'2-28-13') would return 1-15-13 to 1-28-13, I want it to end at the proper end of month, i.e, 1-31-13. Any help is appreciated.
(edit/update)
I would like to make it more clear:
declare #Lastmonth_StartDate datetime,
#Lastmonth_EndDate datetime,
#StartDate datetime = '2/15/13',
#EndDate datetime = '2/28/13'
set #Lastmonth_EndDate=DATEADD(m,-1,#EndDate)
set #Lastmonth_StartDate =DATEADD(m,-1,#StartDate)
select #Lastmonth_StartDate, #Lastmonth_EndDate
Instead of jan 28, I would like jan 31 for Lastmonth_EndDate, and if start dates are 2/1/13 - 2/15/13 I would like Lastmonth_EndDate to be the 15th of the previous month.
-- Edit 6/18
I am trying to use IF or CASE to do the job but am not that good, any lead please?
To get the end of a year-month, add one month to the first of that year-month, then subtract 1 day. The rest is details.
For the syntax you use I believe you use SQL Server, for SQL Server you can do as follows:
Start of the period:
Convert(datetime, Convert(varchar(7),DateAdd(m, 0, '2-15-13'),121) + '-01 00:00:00.000', 121)
End of period:
DateAdd(d, -1, Convert(datetime, Convert(varchar(7),DateAdd(m, 1, '2-28-13'),121) + '-01 23:59:59.997', 121)))

SQL Start date is current day then end date is 1 month ago even if its not from the first to the last of the month

Ok I am trying to write a query that says get the current date and make it the start date. Then I want to go a month back from that current date for the EndDate. Is it possible to do this? Like if it was 9-15-2010 as the start date can I go a month back from that to 8-15-2010 or is this no possible....what would you do for like 9-20-2010 as the start date since every month has a different amount of days in it? Otherwise if this is not possible how else could I do this? The report will always be run on the 25th of the month so any ideas? I need to go from the 25th back a month....I can get some duplicate records between months if needed but less is obviously better
Right now I am using this:
DECLARE #StartDate DATETIME,
#EndDate DATETIME;
SET #StartDate = DATEADD(m,-1,GETDATE());
SET #EndDate = DATEADD(m, 1, #StartDate);
Does this work?
Also, how would I then say my AuditInsertTimestamp is between #Start adn #EndDate?
Currently I have this:
AND cvn.[AuditInsertTimestamp] BETWEEN #StartDate AND #EndDate ;
This is still giving me dates like 7-26-2010 though....
Thanks!
That should work. Did you try it?
If it doesn't work (and there are only 12 test cases to check if you don't trust the documentation) then you can re-build the date from the date parts.
Here's the problem. It should be like this:
cvn.[Subject] = 'Field Changed (Plate Type)'
AND (
cvn.[Note] LIKE 'Old Type: IRP%New Type: BASE PLATE%'
OR cvn.[Note] LIKE 'Old Type: Base Plate%New Type: IRP%'
)
AND cvn.AuditInsertTimestamp BETWEEN GETDATE() AND DATEADD(MONTH, -1, GETDATE())
AND takes precidence over OR, so you were picking up anything with Old Type:IRP or in the correct date range (with Old Type: Base Plate)
Based on your comment:
Well this is being used to select
records. So if I run it on the 25th I
need 30 days back then my field
AuditInsertTimestamp needs to be
between these 2 dates.
I think you need to do something like this:
SELECT * FROM Table
WHERE AuditInsertTimestamp BETWEEN GETDATE() AND DATEADD(MONTH, -1, GETDATE())

SQL Server: Get data for only the past year

I am writing a query in which I have to get the data for only the last year. What is the best way to do this?
SELECT ... FROM ... WHERE date > '8/27/2007 12:00:00 AM'
The following adds -1 years to the current date:
SELECT ... From ... WHERE date > DATEADD(year,-1,GETDATE())
I found this page while looking for a solution that would help me select results from a prior calendar year. Most of the results shown above seems return items from the past 365 days, which didn't work for me.
At the same time, it did give me enough direction to solve my needs in the following code - which I'm posting here for any others who have the same need as mine and who may come across this page in searching for a solution.
SELECT .... FROM .... WHERE year(*your date column*) = year(DATEADD(year,-1,getdate()))
Thanks to those above whose solutions helped me arrive at what I needed.
Well, I think something is missing here. User wants to get data from the last year and not from the last 365 days. There is a huge diference. In my opinion, data from the last year is every data from 2007 (if I am in 2008 now). So the right answer would be:
SELECT ... FROM ... WHERE YEAR(DATE) = YEAR(GETDATE()) - 1
Then if you want to restrict this query, you can add some other filter, but always searching in the last year.
SELECT ... FROM ... WHERE YEAR(DATE) = YEAR(GETDATE()) - 1 AND DATE > '05/05/2007'
The most readable, IMO:
SELECT * FROM TABLE WHERE Date >
DATEADD(yy, -1, CONVERT(datetime, CONVERT(varchar, GETDATE(), 101)))
Which:
Gets now's datetime GETDATE() = #8/27/2008 10:23am#
Converts to a string with format 101 CONVERT(varchar, #8/27/2008 10:23am#, 101) = '8/27/2007'
Converts to a datetime CONVERT(datetime, '8/27/2007') = #8/27/2008 12:00AM#
Subtracts 1 year DATEADD(yy, -1, #8/27/2008 12:00AM#) = #8/27/2007 12:00AM#
There's variants with DATEDIFF and DATEADD to get you midnight of today, but they tend to be rather obtuse (though slightly better on performance - not that you'd notice compared to the reads required to fetch the data).
Look up dateadd in BOL
dateadd(yy,-1,getdate())
GETDATE() returns current date and time.
If last year starts in midnight of current day last year (like in original example) you should use something like:
DECLARE #start datetime
SET #start = dbo.getdatewithouttime(DATEADD(year, -1, GETDATE())) -- cut time (hours, minutes, ect.) -- getdatewithouttime() function doesn't exist in MS SQL -- you have to write one
SELECT column1, column2, ..., columnN FROM table WHERE date >= #start
I, like #D.E. White, came here for similar but different reasons than the original question. The original question asks for the last 365 days. #samjudson's answer provides that. #D.E. White's answer returns results for the prior calendar year.
My query is a bit different in that it works for the prior year up to and including the current date:
SELECT .... FROM .... WHERE year(date) > year(DATEADD(year, -2, GETDATE()))
For example, on Feb 17, 2017 this query returns results from 1/1/2016 to 2/17/2017
For some reason none of the results above worked for me.
This selects the last 365 days.
SELECT ... From ... WHERE date BETWEEN CURDATE() - INTERVAL 1 YEAR AND CURDATE()
The other suggestions are good if you have "SQL only".
However I suggest, that - if possible - you calculate the date in your program and insert it as string in the SQL query.
At least for for big tables (i.e. several million rows, maybe combined with joins) that will give you a considerable speed improvement as the optimizer can work with that much better.
argument for DATEADD function :
DATEADD (*datepart* , *number* , *date* )
datepart can be: yy, qq, mm, dy, dd, wk, dw, hh, mi, ss, ms
number is an expression that can be resolved to an int that is added to a datepart of date
date is an expression that can be resolved to a time, date, smalldatetime, datetime, datetime2, or datetimeoffset value.
declare #iMonth int
declare #sYear varchar(4)
declare #sMonth varchar(2)
set #iMonth = 0
while #iMonth > -12
begin
set #sYear = year(DATEADD(month,#iMonth,GETDATE()))
set #sMonth = right('0'+cast(month(DATEADD(month,#iMonth,GETDATE())) as varchar(2)),2)
select #sYear + #sMonth
set #iMonth = #iMonth - 1
end
I had a similar problem but the previous coder only provided the date in mm-yyyy format. My solution is simple but might prove helpful to some (I also wanted to be sure beginning and ending spaces were eliminated):
SELECT ... FROM ....WHERE
CONVERT(datetime,REPLACE(LEFT(LTRIM([MoYr]),2),'-
','')+'/01/'+RIGHT(RTRIM([MoYr]),4)) >= DATEADD(year,-1,GETDATE())
Here's my version.
YEAR(NOW())- 1
Example:
YEAR(c.contractDate) = YEAR(NOW())- 1
For me this worked well
SELECT DATE_ADD(Now(),INTERVAL -2 YEAR);
If you are trying to calculate "rolling" days, you can simplify it by using:
Select ... FROM ... WHERE [DATE] > (GETDATE()-[# of Days])