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

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

Related

Function get the last day of month in sql

I need to get the last day of month with input of month and year. For example, with input 06/2016 it will return 30. I use SQL Server 2005. Thanks for any help.
Suppose your input is VARCHAR in the form of MM/YYYY.
Use RIGHT and LEFT to get the year and month respectively. Then use DATEFROMPARTS to generate the starting date. Next, use EOMONTH to get the last day of the month. Finally use DAY to extract the day part.
DECLARE #input VARCHAR(7) = '06/2016'
SELECT
DAY(
EOMONTH(
DATEFROMPARTS(CAST(RIGHT(#input,4) AS INT),CAST(LEFT(#input, 2) AS INT),1)
)
)
The above only works for SQL Server 2012+.
For SQL Server 2005, you can use DATEADD to generate the dates:
SELECT
DAY( -- Day part
DATEADD(DAY, -1, -- Last day of the month
DATEADD(MONTH, CAST(LEFT(#input, 2) AS INT), -- Start of next month
DATEADD(YEAR, CAST(RIGHT(#input, 4) AS INT) - 1900, 0) -- Start of the year
)
)
)
Reference:
Some Common Date Routines
You would do something like this:
select eomonth(getdate(), 0);
If you want it formatted as MM/YYYY then you'd do this:
select format(eomonth(getdate(), 0), 'MM/yyyy');
Pardon me for tossing-in a response that is not specific to "SQL Server," nor thence to "2005," but the generalized way to compute the answer that you seek is as follows:
Break down the input that you have, e.g. 06/2016, into two parts. Call 'em #MONTH and #YEAR. Define a third value, #DAY, equal to 1.
Typecast this into a date-value ... "June 1, 2016."
Now, using the date-handling functions that you're sure to have, "first add one month, then subtract one day."
One thing that you must be very careful of, when designing code like this, is to be certain(!) that your code for decoding 06/2016 works for every(!) value that actually occurs in that database, or that it can be relied upon to fail.
try this,
declare #input varchar(20)='06/2016'
set #input=#input+'/01'
declare #dtinput datetime=#input
select dateadd(day,-1,dateadd(month,datediff(month,0,#dtinput)+1,0))
--OR in sql server 2012
select eomonth(#dtinput)

SQL if statement returning incorrectly

So I have a stored procedure that is working except for how it calculates a certain field. On the tables worked with is a field called DeliveryYearMonth that is simply YYYYMM in its format. I try to compare current month info to previous month info, thus I created the following two variables.
DECLARE #CURDate VARCHAR(50) -- Current time (YYYYMM)
DECLARE #MAGDate VARCHAR(50) -- YearAgo Time (YYYYMM)
SET #CURDate = cast((select max(DeliveryYearMonth) from dbo.datatable) as varchar)
SET #MAGDate = cast((substring(#CURDate,1,4)) as varchar) --For current year
+ cast((substring(#CURDate,5,2) - 1) as varchar) -- To get previous month
This is all fine and dandy unless the the month is January, and thus the month would become 00, and the year would stay the same.
So I tried my hand at an if statement to clear this up.
if(substring(#MAGDate,5,2)) = '00'-- If current Month is January
begin
Set #MAGDate = cast((substring(#CURDate,1,4) - 1) as varchar) --For previous year
+ '12' -- December
end
This always breaks the Month Ago Date (#MAGDate) even if the IF statement isn't true. I cannot figure out why.
Thanks
To get the previous version of your string, you can do the following:
select replace(convert(varchar(7), dateadd(month, -1, CAST(#CURDate+'01' as DATE)), 121), '-', '')
As a comment suggested, though, you should really just store this as a date, assuming you are using SQL Server 2008 or more recent.

Best practice: Searching table against day, month or year

I've got a table with a "date" column, where a user input will be queried against (using stored procedure)..and results will be shown on a datagrid..
now a user can either enter a year, Year/month , Year/month/day.. (from drop down lists)
i know there r many possible ways to handle the different queries.. however i am trying to figure out which would be best practice:
Solution 1: having 3 different stored procedures , one for every case.
Solution 2: having 1 stored procedure, with 1 extra parameter as searchlvl , then using IF ELSE statements to decide what lvl of search should be applied.
Solution 3: having 1 stored procedure, and sending the datetime as 3 different parameters , then checking IF parameter is null , and using that to decide search lvl
Solution 4: your suggestions :)
NOTE: i know how to do partial search(using datepart), my question is about best practice among the 3 solutions i offered or any other solution offered in the answers..
Like which would be faster, lighter on database and such..
and which would be slower, heavier..
There are no levels.
When user selects year 2009, you search rows where date >= '2009.01.01 00:00' and < '2010.01.01 00:00'.
When he selects month 01 of year 2009 you search where date >= '2009.01.01 00:00' and < '2009.02.01 00:00'.
Of course you don't pass dates as strings, you should use CONVERT() or pass dates as DATETIME type. This is universal solution and will be fast, because it will use indexes. You can create stored procedure that takes two dates, it will allow to search by every date range, not only year/month/day.
I'd do none of the above.
You should design you stored procedure to take three different ints, one for day, one for month and one for year. Leave the parameters nullable, but establish a convention so only meaningful parameter combinations are used. Then you construct a MINDATE and MAXDATE from the parameters.
Searching Datetime columns based on day/year/month requires a query like:
SELECT * FROM table WHERE date > MINDATE AND date < MAXDATE
which is pretty inefficient but not a definite problem.
Another approach (if the table is huge) would be to create an indexed view with year/month/day integer columns and search for exact matches there. To create such a view use DATEPART().
You can use datepart to get the parts of you date you want to filter against as
declare #table table(
DateVal DATETIME
)
INSERT INTO #table SELECT GETDATE()
DECLARE #Year INT,
#Month INT,
#Day INT
SELECT #Year = 2009
SELECT DATEPART(YY, DateVal) DateYear,
DATEPART(MM, DateVal) DateMonth,
DATEPART(DD, DateVal) DateDay,
*
FROM #table
WHERE (DATEPART(YY, DateVal) = #Year OR #Year IS NULL)
AND (DATEPART(MM, DateVal) = #Month OR #Month IS NULL)
AND (DATEPART(DD, DateVal) = #Day OR #Day IS NULL)
I'd pass in year/month/date as separate parameters into one stored proc, say default of NULL.
Then, I'd use DATEADD to build up from/to datetimes and use that
...
SELECT
#ToYear = ISNULL(#ToYear, DATEPART(year, GETDATE()), --or some base value, such as "1900"
#ToMonth = ...
...
SELECT
#DateTo = DATEADD(year, #ToYear, DATEADD(month, #ToMonth, DATEADD(day, #ToDay, 0), 0), 0)
....
SELECT * FROM myTable WHERE DateColumn >= #DateFrom AND DateColumn <= #DateTo
I would not use any functions on columns or conditional logic to switch between selects

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

What's a good way to check if two datetimes are on the same calendar day in TSQL?

Here is the issue I am having: I have a large query that needs to compare datetimes in the where clause to see if two dates are on the same day. My current solution, which sucks, is to send the datetimes into a UDF to convert them to midnight of the same day, and then check those dates for equality. When it comes to the query plan, this is a disaster, as are almost all UDFs in joins or where clauses. This is one of the only places in my application that I haven't been able to root out the functions and give the query optimizer something it can actually use to locate the best index.
In this case, merging the function code back into the query seems impractical.
I think I am missing something simple here.
Here's the function for reference.
if not exists (select * from dbo.sysobjects
where id = object_id(N'dbo.f_MakeDate') and
type in (N'FN', N'IF', N'TF', N'FS', N'FT'))
exec('create function dbo.f_MakeDate() returns int as
begin declare #retval int return #retval end')
go
alter function dbo.f_MakeDate
(
#Day datetime,
#Hour int,
#Minute int
)
returns datetime
as
/*
Creates a datetime using the year-month-day portion of #Day, and the
#Hour and #Minute provided
*/
begin
declare #retval datetime
set #retval = cast(
cast(datepart(m, #Day) as varchar(2)) +
'/' +
cast(datepart(d, #Day) as varchar(2)) +
'/' +
cast(datepart(yyyy, #Day) as varchar(4)) +
' ' +
cast(#Hour as varchar(2)) +
':' +
cast(#Minute as varchar(2)) as datetime)
return #retval
end
go
To complicate matters, I am joining on time zone tables to check the date against the local time, which could be different for every row:
where
dbo.f_MakeDate(dateadd(hh, tz.Offset +
case when ds.LocalTimeZone is not null
then 1 else 0 end, t.TheDateINeedToCheck), 0, 0) = #activityDateMidnight
[Edit]
I'm incorporating #Todd's suggestion:
where datediff(day, dateadd(hh, tz.Offset +
case when ds.LocalTimeZone is not null
then 1 else 0 end, t.TheDateINeedToCheck), #ActivityDate) = 0
My misconception about how datediff works (the same day of year in consecutive years yields 366, not 0 as I expected) caused me to waste a lot of effort.
But the query plan didn't change. I think I need to go back to the drawing board with the whole thing.
This is much more concise:
where
datediff(day, date1, date2) = 0
You pretty much have to keep the left side of your where clause clean. So, normally, you'd do something like:
WHERE MyDateTime >= #activityDateMidnight
AND MyDateTime < (#activityDateMidnight + 1)
(Some folks prefer DATEADD(d, 1, #activityDateMidnight) instead - but it's the same thing).
The TimeZone table complicates matter a bit though. It's a little unclear from your snippet, but it looks like t.TheDateInTable is in GMT with a Time Zone identifier, and that you're then adding the offset to compare against #activityDateMidnight - which is in local time. I'm not sure what ds.LocalTimeZone is, though.
If that's the case, then you need to get #activityDateMidnight into GMT instead.
where
year(date1) = year(date2)
and month(date1) = month(date2)
and day(date1) = day(date2)
Make sure to read Only In A Database Can You Get 1000% + Improvement By Changing A Few Lines Of Code so that you are sure that the optimizer can utilize the index effectively when messing with dates
this will remove time component from a date for you:
select dateadd(d, datediff(d, 0, current_timestamp), 0)
Eric Z Beard:
I do store all dates in GMT. Here's the use case: something happened at 11:00 PM EST on the 1st, which is the 2nd GMT. I want to see activity for the 1st, and I am in EST so I will want to see the 11PM activity. If I just compared raw GMT datetimes, I would miss things. Each row in the report can represent an activity from a different time zone.
Right, but when you say you're interested in activity for Jan 1st 2008 EST:
SELECT #activityDateMidnight = '1/1/2008', #activityDateTZ = 'EST'
you just need to convert that to GMT (I'm ignoring the complication of querying for the day before EST goes to EDT, or vice versa):
Table: TimeZone
Fields: TimeZone, Offset
Values: EST, -4
--Multiply by -1, since we're converting EST to GMT.
--Offsets are to go from GMT to EST.
SELECT #activityGmtBegin = DATEADD(hh, Offset * -1, #activityDateMidnight)
FROM TimeZone
WHERE TimeZone = #activityDateTZ
which should give you '1/1/2008 4:00 AM'. Then, you can just search in GMT:
SELECT * FROM EventTable
WHERE
EventTime >= #activityGmtBegin --1/1/2008 4:00 AM
AND EventTime < (#activityGmtBegin + 1) --1/2/2008 4:00 AM
The event in question is stored with a GMT EventTime of 1/2/2008 3:00 AM. You don't even need the TimeZone in the EventTable (for this purpose, at least).
Since EventTime is not in a function, this is a straight index scan - which should be pretty efficient. Make EventTime your clustered index, and it'll fly. ;)
Personally, I'd have the app convert the search time into GMT before running the query.
You're spoilt for choice in terms of options here. If you are using Sybase or SQL Server 2008 you can create variables of type date and assign them your datetime values. The database engine gets rid of the time for you. Here's a quick and dirty test to illustrate (Code is in Sybase dialect):
declare #date1 date
declare #date2 date
set #date1='2008-1-1 10:00'
set #date2='2008-1-1 22:00'
if #date1=#date2
print 'Equal'
else
print 'Not equal'
For SQL 2005 and earlier what you can do is convert the date to a varchar in a format that does not have the time component. For instance the following returns 2008.08.22
select convert(varchar,'2008-08-22 18:11:14.133',102)
The 102 part specifies the formatting (Books online can list for you all the available formats)
So, what you can do is write a function that takes a datetime and extracts the date element and discards the time. Like so:
create function MakeDate (#InputDate datetime) returns datetime as
begin
return cast(convert(varchar,#InputDate,102) as datetime);
end
You can then use the function for companions
Select * from Orders where dbo.MakeDate(OrderDate) = dbo.MakeDate(DeliveryDate)
Eric Z Beard:
the activity date is meant to indicate the local time zone, but not a specific one
Okay - back to the drawing board. Try this:
where t.TheDateINeedToCheck BETWEEN (
dateadd(hh, (tz.Offset + ISNULL(ds.LocalTimeZone, 0)) * -1, #ActivityDate)
AND
dateadd(hh, (tz.Offset + ISNULL(ds.LocalTimeZone, 0)) * -1, (#ActivityDate + 1))
)
which will translate the #ActivityDate to local time, and compare against that. That's your best chance for using an index, though I'm not sure it'll work - you should try it and check the query plan.
The next option would be an indexed view, with an indexed, computed TimeINeedToCheck in local time. Then you just go back to:
where v.TheLocalDateINeedToCheck BETWEEN #ActivityDate AND (#ActivityDate + 1)
which would definitely use the index - though you have a slight overhead on INSERT and UPDATE then.
I would use the dayofyear function of datepart:
Select *
from mytable
where datepart(dy,date1) = datepart(dy,date2)
and
year(date1) = year(date2) --assuming you want the same year too
See the datepart reference here.
Regarding timezones, yet one more reason to store all dates in a single timezone (preferably UTC). Anyway, I think the answers using datediff, datepart and the different built-in date functions are your best bet.