SQL for last six "full" months - sql

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)

Related

How can force SQL DateDiff function to stop rounding up?

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

Most optimal way to get all records IN previous month

In the past I have always used:
WHERE DATEDIFF(m, [DATE_COL], GETDATE()) = 1
which gets me ALL the record that occurred in the PREVIOUS month. For example if I ran this query, it will get me all records which occurred in January.
However I am currently working with a significantly bigger table and if I use the above query, it takes almost 30 minutes for it to load. However, if I use something like
WHERE [SettlementDate] >= DateAdd(DAY, -31, GETDATE())
it will usually run in under 10 seconds.
My question is:
How can I get the same result as WHERE DATEDIFF(m, [DATE_COL], GETDATE()) = 1 without the crazy increase in processing time?
Thank you!
Your query is slow because when you do DATEDIFF(m, [DATE_COL], GETDATE()) it can not use any indexes on the [Date_Col].
Anyway you can use the following where clause, this will use indexes on the [SettlementDate] and hopefully it should perform a lot better than the DATEDIFF() function.
WHERE [SettlementDate] >= DATEADD(MONTH, DATEDIFF(MONTH, 0, GETDATE())-1, 0)
AND [SettlementDate] < DATEADD(DAY,1,DATEADD(MONTH, DATEDIFF(MONTH, -1, GETDATE())-1, -1))
The problem is that you have a function call and the query optimizer cannot see inside functions. That means, it cannot decide if use an index or not. In that case it reads the whole table that can take very long time.
I suggest you to use variables and I believe your query will get better result:
declare #From datetime -- choose the same type as your SettlementDate column
set #From = DateAdd(DAY, -31, GETDATE()) -- compute the starting date
select * from yourTable where SettlementDate >= #From
In that case the sql server will know that you want to compare your SettlementDate value with a date and there is nothing other that has to compute. If you have index in that column, it will use that.
Additional information about SARGable queries: https://www.codeproject.com/Articles/827764/Sargable-query-in-SQL-server

A better way? Have date in query always use a date in the current year without maintenance

SELECT Date_Received, DateAdd(Year, DateDiff(year, Cast('3/01/2010 12:00:00AM' as DateTime) ,
GetDate())-1, Cast('3/01/2010 12:00:00AM' as DateTime)) as minimum_date
FROM [Volunteers].[dbo].[Applications]
WHERE Date_received >= DateAdd(Year, DateDiff(year, Cast('3/01/2010 12:00:00AM' as DateTime),
GetDate())-1, Cast('3/01/2010 12:00:00AM' as DateTime))
In several subqueries where I need to check that a date is within an acceptable range. I need to avoid using a simple constant as I really don't want to update it or a config file each new school year.
My current solution is to enter the date into the query and use some complicated DATEADD tricks to get the current year(or previous year) into the date I am using in the comparison. The exact code is above. Is there a cleaner way for me to do this?
Thanks
Edit
The business requirement is to find applications submitted between 3/01 and 7/31.
We are running background checks and it costs us money for each check we do. Identifying applications submitted during this time period helps us determine if we should do a full, partial or no background check. I will also need to check if dates concerning the previous year.
We will be doing this every year and we need to know if they were in the current year. Maintaining the queries each year to update the dates is not something I want to do.
So I am looking for a good technique to keep the year parts of the dates relevant without having to update the query or a config file.
Old TSQL trick: cast the date to a string in a format that starts with the four-digit year, using substring to take the first four characters of that, cast it back to a date.
Actually, the reason that it's an old TSQL trick is that, if I recall correctly, there wasn't a year() function back then. Given that there's one now, using year( getdate() ) , as others' have answered, is probably the better answer.
SELECT YEAR(GETDATE())
will give you the current year.
If you need to query by month and year a lot, you should also consider making those properties into persisted, computed fields:
ALTER TABLE dbo.Applications
ADD DateReceivedMonth AS MONTH(Date_Received) PERSISTED
ALTER TABLE dbo.Applications
ADD DateReceivedYear AS YEAR(Date_Received) PERSISTED
SQL Server will now extract the MONTH and YEAR part of your Date_Received and place them into two new columns. Those are persisted, e.g. stored along side with your table data. SQL Server will make sure to keep them up to date automatically, e.g. if you change Date_Received, those two new columns will be recomputed (but not on every SELECT).
Now, your queries might be a lot easier:
SELECT (list of fields)
FROM dbo.Applications
WHERE DateReceivedYear = 2010 AND DateReceivedMonth BETWEEN 3 AND 7
Since these are persisted fields, you can even put an index on them to speed up queries against them!
Is there any reason you cannot simply use the Year function?
Select Date_Received
, Year(GetDate())
- Year('3/01/2010 12:00:00AM') - 1
+ Year('3/01/2010 12:00:00AM')
From [Volunteers].[dbo].[Applications]
Where Date_received >= ( Year(GetDate())
- Year('3/01/2010 12:00:00AM') - 1
+ Year('3/01/2080 12:00:00AM') )
Another way would be to use a common-table expression
With Years As
(
Select Year(GetDate()) As CurrentYear
, Year('3/01/2010 12:00:00AM') As ParamYear
, Year('3/01/2080 12:00:00AM') As BoundaryYear
)
Select Date_Received
, CurrentYear - Years.ParamYear - 1 + Years.ParamYear
From [Volunteers].[dbo].[Applications]
Cross Join Years
Where Date_received >= ( Years.CurrentYear
- Years.ParamYear - 1 + Years.BoundaryYear )
TSQL Function returns four digit year dependent on year. This behaves much like the standard SQL YEAR functions [Thomas - nod] which 'CAN' be tweaked using sp_configure on the advanced options, however, the code below is provided as a framework for CUSTOM requirements and can be modified as required. e.g. return as int, use with standard DATETIME functions in SQL to achieve what is needed. e.g. When working with "dirty" data I had to migrate, I used it with the PATINDEX() function to strip non-numeric values etc.
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
-- =============================================
-- Author: Andrew McLintock
-- Create date: 13 July 2016
-- Description: Return 4-digit YEAR
-- =============================================
/*
SELECT Staging.fn_4year('06')
SELECT Staging.fn_4year('56')
SELECT Staging.fn_4year('99')
SELECT Staging.fn_4year('1906')
SELECT Staging.fn_4year('2025')
*/
CREATE FUNCTION Staging.fn_4year
(
#year_in varchar (4)
)
RETURNS varchar(4)
AS
BEGIN
DECLARE #yeartmp int, #Retval varchar(4)
SET #yeartmp = CAST(REPLACE(#year_in,' ','') AS INT)
IF LEN(CAST(#yeartmp AS Varchar)) = 4
BEGIN
Return cast(#yeartmp as varchar(4))
END
IF LEN(#year_in) = 2
BEGIN
SET #Retval = CAST(iif(#yeartmp > 49, #yeartmp + 1900, #yeartmp + 2000) AS varchar(4))
END
RETURN #Retval
END
GO
Consider keeping a set of datetime variables help readability and maintainability. I'm not sure I've captured all your requirements, especially with reference to 'previous year'. If it's as simple as finding applications submitted between 3/01 and 7/31, then this should work. If you need to determine those that were submitted Aug 1 (last year) through Feb 28 (current year), this solution could be modified to suit.
DECLARE #Start smalldatetime, #End smalldatetime, #CurrYear char(4)
SELECT #CurrYear = YEAR(getdate())
SELECT #Start = CAST( 'mar 1 ' + #CurrYear as smalldatetime),
#End = CAST( 'jul 31 ' + #CurrYear as smalldatetime)
SELECT *
FROM Applications
WHERE Date_Received
BETWEEN #Start AND #End

How can I get the date of the first second of the year with SQL?

I'm working on a purging procedure on SQL Server 2005 which has to delete all rows in a table older than 1 year ago + time passed in the current year.
Ex: If I execute the procedure today 6-10-2009 it has to delete rows older than 2008-01-01 00:00 (that is 2007 included and backwards).
How can I get the date of the first second of the year?
I've tried this:
select cast((DATEPART(year, getdate()) -1 )AS DATETIME);
but I get 1905-07-02 00:00:00.000 and not 2008-01-01 00:00 (as I wrongly expected).
Can someone help me, please?
EDIT: This was returning current year, when the question was for previous year. Code has been corrected to reflect this.
use this
select DATEADD(yy, DATEADD(yy, DATEDIFF(yy,0,getdate()), 0), -1)
OR to use your variable:
select DATEADD(yy, DATEADD(yy, DATEDIFF(yy,0,#YourDateTimeValue), 0), -1)
This will work:
select cast('01 jan' + CAST((DATEPART(year, getdate())-1) as varchar) AS DATETIME);
(I know it's not the "best" solution and probably involves more casts than necessary, but it works, and for how this will be used it seems to be a pragmatic solution!)
SELECT DATEADD(year, DATEDIFF(year, 365, GETDATE()), 0)

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])