1
WHERE
   StartDate BETWEEN 
       CAST(FLOOR(CAST(DATEADD("month", - 12, ISNULL(RunDate.testdate, GETDATE()) - DAY(ISNULL(RunDate.testdate, GETDATE())) + 1) AS FLOAT)) AS DATETIME)
       AND 
       CAST(FLOOR(CAST(ISNULL(RunDate.testdate, GETDATE()) - DAY(ISNULL(RunDate.testdate, GETDATE())) + 1 AS FLOAT)) AS DATETIME)

/************************************************* 
SQL Server 2005 (9.0 SP4)
StartDate is of TYPE DATETIME
StartDate date looks like 2012-07-05 12:45:10.227
RunDate is a TABLE with no records
RunDate.testdate is always NULL
**************************************************/

This WHERE clause works and produces the expected results for what the business expects but seems excessively untidy.

All that this is attempting to achieve is to bring across all records for the last 12 months, when run in July of any year. So records from 1 July 2013 to 30 June 2014.

marc_s
  • 732,580
  • 175
  • 1,330
  • 1,459

4 Answers4

3

Here is another expression:

where startdate > cast(dateadd(year, -1, getdate() - day(getdate()) as date) and
      startdate <= cast(getdate() - day(getdate()) as date)

When you subtract a number from a datetime it is treated as a number of day, which can be easier to read.

EDIT:

Oh, that's 2005, not 2008. The date logic goes like this . . . :

where startdate > cast(dateadd(year, -1, dateadd(day, - day(getdate()), getdate()) as date) and
      startdate <= cast(dateadd(day, - day(getdate()), getdate())

But you will have times on the date. There actually isn't an easy way to convert this to a date. You can do this more readily with a subquery:

from (select dateadd(day, 0, datediff(day, 0, getdate())) as today) const
where startdate > dateadd(year, -1, dateadd(day, - day(today), today) and
      startdate <= cast(dateadd(day, - day(today), today)
Gordon Linoff
  • 1,242,037
  • 58
  • 646
  • 786
  • In order to test what values are being generated I attempted to use this in a **SELECT** statement `CAST(dateadd(year, -1, getdate() - day(getdate()) as date` but get Type Date is not a defined system type. _this is SQL 2005_ – LateBloomer Jul 09 '14 at 02:11
1

My first question would be: what do you expect to see if someone executes this at any time other than July?

Never assume something outside your control. You have no control over when this will be executed so write it so it will always execute during the correct time period no matter what.

Because there are a number of ways to do this based on DBMS and even version of DBMS (SS 2005 vs 2008, for example) here it is in human-speak:

 -- Get the difference, in years, between sysdate and the base date
 -- Add that to the base date. You now have Jan 1 of whatever year it is.
 -- Add 6 months. You now have Jul 1 of that year.
 -- Add 1 year and subtract 1 day. You now have Jun 30 of the following year.

Your query will now operate on the correct range no matter when it is executed. Of course, if it is executed in, say, February, the results will not be complete because some of the range will be in the future. But the results will always be correct for the time it is executed.

One adjustment you could make is that, if executed before Jul 1 of any year, it generates a range for the most recent complete year. So Jul 1 2012 to Jun 30 2013 if executed anytime before Jul 1 2014.

[Edit: I suppose since I brought it up, I should specify how that could be done. In the first step above, where it says sysdate, change it to (sysdate minus 6 months).]

It really doesn't matter which way you design it, as long as your users know what to expect. But whatever you do, don't let your query depend on users' ability to execute it at the right time.

TommCatt
  • 5,498
  • 1
  • 13
  • 20
1

If you abandon the x BETWEEN a AND b pattern in favour of one that could roughly be expressed as

x BEGINS WITH a
  ENDS BEFORE b

(which, of course, is not valid SQL syntax but x >= a AND x < b would be a decent replacement), you will end up with both a and b having same granularity (specifically, months, because each of the two points would be the beginning of a month).

In this case it is very opportune because it will allow you to factorise calculation of both a and b nicely. But before I get there, let me tell (or remind) you about this date/time truncation technique in SQL Server:

DATEADD(unit, DATEDIFF(unit, SomeFixedDate, YourDateTime), SomeFixedDate)

In your case, the unit would be MONTH. As for SomeFixedDate, there is an established practice of using 0 there for brevity, 0 representing the date of 1900-01-011.

So, using this technique, the beginning of the current month would be calculated as

DATEADD(MONTH, DATEDIFF(MONTH, 0, GETDATE()), 0)

That would be the end of your interval. The beginning of the interval would be same except you would subtract 12 from the DATEDIFF's result, and so the complete condition to match the range would look like this:

WHERE StartDate >= DATEADD(MONTH, DATEDIFF(MONTH, 0, GETDATE()) - 12, 0)
  AND StartDate <  DATEADD(MONTH, DATEDIFF(MONTH, 0, GETDATE()), 0)

If that still looks a mouthful to you, you could try getting rid of the repetition of DATEDIFF(MONTH, 0, GETDATE()). You could view it as some abstract month number and reference it as such in both DATEADD calls. The CROSS APPLY syntax would help you with that:

…
CROSS APPLY (
  SELECT DATEDIFF(MONTH, 0, GETDATE())
) AS x (MonthNo)
WHERE StartDate >= DATEADD(MONTH, x.MonthNo-12, 0)
  AND StartDate <  DATEADD(MONTH, x.MonthNo   , 0)

Yes, that seems factorised all right. However, it might not lend itself well to readability. Someone (even yourself, at some later point) might look at it and say: "Huh, MonthNo? What in the world is that? Ah, that number…" If I were to factorise your interval calculation, I might consider this instead:

…
CROSS APPLY (
  SELECT DATEADD(MONTH, DATEDIFF(MONTH, 0, GETDATE()), 0)
) AS x (ThisMonth)
WHERE StartDate >= DATEADD(YEAR, -1, x.ThisMonth)
  AND StartDate <  x.ThisMonth

I admit, though, that readability is not a perfectly objective criterion and sometimes one chooses to write one way or the other based on one's personal tastes/preferences.


1Although hard-coding a date as an integer value is not a normal practice, I would argue that this truncation technique is a special case where using 0 in place of a datetime value should be okay: that usage appears to be so widespread that it may by now have become a "fixed expression" among those using it and is, therefore, unlikely to cause much confusion.

Community
  • 1
  • 1
Andriy M
  • 76,112
  • 17
  • 94
  • 154
0

In SQL Server 2005, you can use DATEADD and DATEDIFF to remove the time component and compute exact month boundaries:

WHERE 
    startdate >= DATEADD(year, -1, DATEADD(month, DATEDIFF(month, '', GETDATE()), ''))
    AND startdate > DATEADD(month, DATEDIFF(month, '', GETDATE()), '')
marc_s
  • 732,580
  • 175
  • 1,330
  • 1,459
Dan Guzman
  • 43,250
  • 3
  • 46
  • 71