Can anybody tell me why my Database selection queries asking for an orderid
WHERE [order].dateplaced >= DATEADD(millisecond,-3,ISNULL(#datefrom, #searchscope))
AND [order].dateplaced < DATEADD(millisecond,-3,DATEADD(day,1,ISNULL(#dateto, GETDATE())))
Is missing out an order over three and a half minutes before midnight on the #dateto when #datefrom and #dateto are both set to the same date?
I've tried altering the query so the initial DATEADD adds a second to both datetimes instead of taking away 3 milliseconds from both and it continues to not pull the order.
For reference the exact datetime of dateplaced is: 2009-01-20 23:56:17.933
Am I being dumb?
EDIT: I remember now why the three milliseconds thing came into play. It was because our accounts work on whole months and if you wanted a month's worth of reports you could set it from, for example, 01-Jan to 01-Feb and that would include everything up to midnight on 01 Feb. (Incidentally is that exclusive or inclusive?) However people were too dumb to actually set the date range to this they would set it from 01-Jan to 31-Jan and miss a day (don't ask me).
As I knew that SQL Server worked in resolutions of 3 milliseconds I first of all made a request for 31-Jan go until 11:59:59.997 on 31-Jan whereas 01-Feb would still go from midnight. However to compensate then on the date "from" I had to drop three milliseconds so nothing could slip through the cracks. I just presumed that SQL Server would be able to handle that. It's probably missing bits out of those reports now so I shall have to go and look those up.
Although the top-voted solution below works for all practical purposes (our bank's credit card settling software still randomly puts transactions from either side of midnight on the "wrong" side as far as our system is concerned) it still doesn't answer the question of why a transaction with a good three and a half minutes grace fails to get captured. I appreciate that just losing the time will work most of the time but the nature of our business means that on certain dates we have actual time periods of around twenty minutes where greater resolution and precision handling would be handy.
For the curious we sell concert tickets in the UK and on days where we have, for example, the Reading or V Festival going on sale we shift a couple of thousand tickets in the twenty minutes after on sale and the rest of the day have a normal amount of sales for other stuff. Those twenty minute periods become the target of much reporting and dissection as the load balancer isn't always perfect and weird record glitches do crop up. So being able to dice records down to stretches of seconds would be handy. My confidence in the software is a bit shaken by this so an actual answer would be handy.
However for the time being, the particular thing I'm doing is fine with the top-voted solution below...
GETDATE() has a time portion which you are not trimming off (you might want to trim the time part off of the other variables too). I don't know why you're messing around with milliseconds either. I know that 3 milliseconds is the smallest resolution, but I typically have never had to use it to work around range endpoints.
DECLARE #today AS DATETIME
SET #today = DATEADD(dd, 0, DATEDIFF(dd, 0, GETDATE()))
SET #datefrom = DATEADD(dd, 0, DATEDIFF(dd, 0, #datefrom))
SET #searchscope = DATEADD(dd, 0, DATEDIFF(dd, 0, #searchscope))
SET #dateto = DATEADD(dd, 0, DATEDIFF(dd, 0, #dateto))
SELECT *
FROM [order]
WHERE [order].dateplaced >= ISNULL(#datefrom, #searchscope)
AND [order].dateplaced < DATEADD(day, 1, ISNULL(#dateto, #today))
This works for me in SQL 2000 and 2008:
declare #datefrom datetime
declare #dateto datetime
set #datefrom='2009-01-20'
set #dateto='2009-01-20'
select case when '2009-01-20 23:56:17.933' >= DATEADD(millisecond,-3,#datefrom) then '>=' else 'NOT >=' end
select case when '2009-01-20 23:56:17.933' < DATEADD(millisecond,-3,DATEADD(day,1,#dateto)) then '<' else 'NOT <' end
There might be more to the problem than you're telling us.
Related
I need a WHERE statement where the date of the record is the previous day. I have the below code which will do this
WHERE DOC_DATE = dateadd(day,datediff(day,1,GETDATE()),0)
However I need this statement to get Friday's record when the current day is Monday. I have the below code but it will not work for me. No errors come back on SQL although no records results come back either. I have the below code for this
WHERE DOC_DATE = DATEADD(day, CASE WHEN datepart(dw,(GETDATE())) IN (2) then -3 ELSE -1 END ,0)
Important to add that this needs to be in a WHERE clause. This is for a Docuware administrative view I am creating. I have no control on how to write the SELECT statement, I only have access to edit the WHERE clause:
Here's a slightly "magical" way to compute the value that doesn't depend on any particular server settings such as datefirst. It's probably not immediately obvious how it works:
WHERE DOC_DATE = dateadd(day,datediff(day,'20150316',getdate()),
CASE WHEN DATEPART(weekday,getdate()) = DATEPART(weekday,'20150330')
THEN '20150313'
ELSE '20150315' END)
In the first line, we're computing the number of days which have elapsed since some arbitrary date. I picked a day in March 2015 to use as my base date.
The second line asks what today's day of the week is and if it's the same as some arbitrary "Known good" Monday. Just taking one value and comparing it to 2 depends on what your DATEFIRST setting is so I prefer not to write that.
In the third line, we decide what to do if it's a monday - I give a date that is 3 days before my arbitrary date above. If it wasn't a monday, we pick the day before.
Adding it all together, when we add the days difference from the arbitrary date back to one of these two dates from lines 3 and 4, it has the effect of shifting the date backwards 1 or 3 days.
It's can be an odd structure to see if you're not familiar with it - but combining dateadd/datediff and exploiting relationships between an arbitrary date and other dates computed from it can be useful for performing all kinds of calculations. A similar structure can be used for computing e.g. the last day of the month 15 months ago using just dateadd/datediff, an arbitrary date and another date with the right offset from the first:
SELECT DATEADD(month,DATEDIFF(month,'20010101',GETDATE()),'19991031')
As I said in a comment though, usually doing this sort of thing is only a short step away from needing to properly model your organisation's business days, at which point you'd typically want to introduce a calendar table. At one row per day, 20 years worth of pre-calculated calendar (adjusted as necessary as the business changes) is still less than 10000 rows.
You can try this.
WHERE DOC_DATE = DATEADD(DAY, CASE WHEN datepart(dw, GETDATE()) = 2 THEN -3 ELSE -1 END, CAST(GETDATE() AS DATE))
I need to query SQL for data that falls into a last month date. I thought I was using the correct query logic but I get no results and I know there are results. The snippet of code is this:
MONTH(n.JOIN_DATE) = DATEADD(month, - 1, GETDATE())
This is giving me no results and I need to get anyone who has a join date of last month. What am I missing?
Use this:
MONTH(n.JOIN_DATE) = MONTH(DATEADD(month, - 1, GETDATE()))
You need to compare apples with apples, so compare the numerical month on both sides of the equation.
Massive credit to #PaulL for figuring this out before I did.
Update:
As #DasBlinkenLight and Matt pointed out, just comparing by month leaves the door open for multiple years to be returned. One possible fix would be to also compare the years, e.g.
WHERE MONTH(n.JOIN_DATE) = MONTH(DATEADD(month, - 1, GETDATE())) AND
YEAR(n.JOIN_DATE) = YEAR(DATEADD(month, - 1, GETDATE()))
MONTH(...) produces a month number. You should not compare it to the result returned by DATEADD, which is actually a date.
If you are looking for everyone who has joined less than a month ago, you can do it like this:
WHERE DATEADD(month, 1, n.JOIN_DATE) > GETDATE()
This takes into account the year and the day as well, not only the month.
If you are looking for everyone who joined last month, no matter on what day, you can use a more complex condition:
WHERE MONTH(DATEADD(month, -1, GETDATE()) = MONTH(n.JOIN_DATE)
AND YEAR (DATEADD(month, -1, GETDATE()) = YEAR (n.JOIN_DATE)
The second condition is necessary to avoid confusion between members joining last month and members joining on the same month one or more years ago.
MONTH(n.JOIN_DATE) returns a numeric which indicate the month in date m.JOIN_DATE
DATEADD(month, - 1, GETDATE()) returns a date which indicate date in last month.
So, you can use this instead :
MONTH(n.JOIN_DATE)= MONTH(DATEADD(month, - 1, GETDATE()))
OR
n.JOIN_DATE = DATEADD(month, - 1, GETDATE())
MONTH(n.JOIN_DATE) will only return the numerical value of the month (e.g.: 11 or 5).
DATEADD(MONTH, -1, GETDATE()) will simply subtract one month from the current date. It is still in a DATETIME format.
You may be looking for:
MONTH(n.JOIN_DATE) = MONTH(DATEADD(MONTH, -1, GETDATE()))
SELECT
DATEFROMPARTS(YEAR(DATEADD(month,-1,GETDATE())),MONTH(DATEADD(month,-1,GETDATE())),1) AS StartOfLastMonth
,DATEADD(day,-1,(DATEFROMPARTS(YEAR(GETDATE()),MONTH(GETDATE()),1))) AS EndOfLastMonthAsDate
,DATEADD(day,-3,CAST(DATEFROMPARTS(YEAR(GETDATE()),MONTH(GETDATE()),1) AS DATETIME)) AS EndOfLastMonthMidngith
,CAST(DATEADD(month,-1,GETDATE()) AS DATE) AS OneMonthAgoStrartOfDay
,CAST(GETDATE() AS DATE) AS StartOfToday
,DATEADD(MS,-3,CAST(CAST(GETDATE() AS DATE) AS DATETIME)) AS MidnightLastNight
Okay As people have definitely illustrated there are a lot of different answers to your question and all are built upon similar premise. using DATEADD() with a negative number to go back a month. Or to compare month and year to see if they are the same. The former being geared at 1 month ago to today and the later being last month.
All of the answers so far, expect #DasBlinkenLight and #TimBiegeleisen, fail to take into account TIME component of your column and the GETDATE() function if you have one. If your column is DATETIME you will need to take that into account. The above SELECT query that will arm you with some ways of getting to different dates that i suspect will meet your needs.
As far as using BETWEEN with dates be careful! because the values you put in are inclusive so if you put GETDATE() on the right side of the between statement you will get today's results too but you might really want UP TO to today in which case you should change your right side argument. Also I am not sure about Oracle, mysql, etc. but Micrsofot SQL-Server is accurate to .003 milliseconds. So if you really want to look at midnight of a date you should look at 23:59:59.997 because .998 and .999 will round up to the next day.
Also to further simplify if you don't want time components you can also cast your column to DATE and it essentially drops off the time and the BETWEEN because a little clearer too, e.g. CAST(n.JOIN_DATE AS DATE)
There are definitely other questions on this subject on stackoverflow I encourage you to research.
I knew this was a simple one and I was missing something to it. My code was wrong based upon the many responses I received on this and I was comparing apples to oranges and not apples to apples. Once I added the Month() around the dateadd function, it worked.
The reply to this answer are correct. But in the light of best practice writing your query this way will make it less SARGABLE, hence making it ignore indexes if you have one. It might be better to write it as
WHERE n.JOIN_DATE between DATEADD(MONTH, DATEDIFF(MONTH, 0, GETDATE())-1, 0) AND DATEADD(MONTH, DATEDIFF(MONTH, -1, GETDATE())-1, -1)
based on the comment below I have modified the query. I guess I did not read the question in depth.
I'm trying to get the first and last day of the previous month. What I really need is a range for a BETWEEN clause, so I need the first second of the first day, and the last second of the last day.
This is the code I've been using:
set #startDate = DATEADD(mm, DATEDIFF(mm,0,DATEADD(mm, -1, getdate())), 0)
set #endDate = dateadd(ms,-3,DATEADD(mm, DATEDIFF(mm,0,getdate() ), 0))
However, for today, this is actually returning 4/1/2011 and 5/1/2011, which is not completely accurate. I want to get the last second of 4/30/2011 for the endDate.
I've been googling and I see many different ways to get the first/last day of a month. Even on SO itself, I see many different variations of this question with many different answers. I'd like to compile a list of all the methods to achieve this, and then determine which is the "best" (based on simplicity and efficiency)
(I should add that I'm using sql server 2000)
EDIT:
Re the enddate bug - this code is actually correct, the problem was just that #endDate was a smalldatetime. I changed it to a datetime and this is now working correctly
For dates I strongly recommend not using BETWEEN. This is highlighted by your need to remove 3ms from a date to get "the last moment of the previous day".
With continuous values (rather than discrete values), that can have varying degrees of accuracy, it is generally better to use >= AND <. For example...
WHERE myDateField >= '2012-04-01' AND myDateField < '2012-05-01'
By doing this you never need to even think about the accuracy of the myDateField data or data-type. It just works. Always.
With that in mind, your code is very close to what I would use...
SET #start = DATEADD(month, DATEDIFF(month, 0, getDate()) - 1, 0)
SET #end = DATEADD(month, DATEDIFF(month, 0, getDate()) , 0)
EDIT: as per the explanation from #Dems (please see the comments)
I think now my answer will be same as #Dems to we both are have same answers. :) #Dems Credit goes to you.
try this query it will get you the proper dates as per your need.
select DATEADD(mm, DATEDIFF(mm,0,DATEADD(mm, -1, GETDATE())), 0)
select DATEADD(mm, DATEDIFF(mm,0,GETDATE() ), 0)
and using this date you can directly use the >= and < as per the suggestion by #Dems below.
Hopefully this will be an easy one to answer.
I am working on a table that requires MTD data. One of our SQL guys told me to use
MONTH (#monthtodate)= 11
Where #monthtodate is set to GetDate() in the parameter list in SQL Server Management Studio. So in "theory", he says, it should select the month (11) and then get today and return all the requested data in between those two dates. But I'm thinking this isn't correct.
In looking at my data I'm starting to think that It's just returning data for the whole month of November instead of just MTD. I guess, technically, anything that has 0 won't be calculated. However that just means it's poorly written code correct?
In your opinions, would this be the better way to return MTD data:
production_date <= #today and Production_Date >= DATEADD(mm, DATEDIFF(mm, 0, #today), 0)
Thanks in advance everyone!
Here's how I do it. This should work on pretty much any version of SQL Server.
One important thing to note: at the outset, one should always establish a single value that represents 'now', the current moment in time. If you do not have a consistent value for now in your query, you will eventually get bit when your query is executed such that it crosses a date boundary whilst in-flight. Nothing like billing somebody for something they already paid for last month. Worst, edge-case bugs like that are difficult to catch, either by developers or by QA, since neither is likely to be working, say, at 11:59 on December 31.
The code:
declare
#dtNow datetime ,
#Today datetime ,
#dtFrom datetime ,
#dtThru datetime
---------------------------------------------------------------------------------------
-- set our effective notion of 'now'-ness.
--
-- We need have a consistent notion of now, lest we get bit in the a$$
-- by an edge case where we cross a day/month/year boundary in mid-execution.
--
-- NOTE: Mostly, we're interested in the *DATE* rather than the actual moment-in-time.
-- So, we carry around two flavors here.
---------------------------------------------------------------------------------------
set #dtNow = current_timestamp
set #Today = convert(datetime,convert(varchar,#dtNow,112),112)
---------------------------------------------------------------------------------------
-- compute the current date.
--
-- 1. get the current date sans timestamp (effectively start-of-day)
-- 2. add 1 day, then back off 3 millseconds to set it to the last tick of the current day
--
-- NOTE: Depending on the requirements of your particular application (and the nature
-- of your data), you might want to use the actual current date/time value as
-- your upper bound.
--
-- FURTHER NOTE: How far to back off is dependent on your date/time type:
--
-- * For DateTime, the resolution is milliseconds and the last tick of the day
-- is 997 milliseconds, so you need to back off 3ms from the start of the
-- next day.
--
-- * SmallDateTime has a 1 second resolution. The last tick of the day, natch,
-- is 59 seconds, so you need to back off 1 second from the start of the next day.
--
-- * For DateTime2, the user declares the precision in decimal fractions of a second,
-- though its resolution is 100ns ticks. You'll need (especially if you're working
-- with DateTime2 columns/variables of differing precision) experiment to figure out
-- what traps Microsoft has set for you inside DateTime2 and what you need to do to
-- make things work properly.
--
---------------------------------------------------------------------------------------
set #dtThru = dateadd(ms,-3,dateadd(day,1,#Today))
--set #dtThru = #dtNow -- if you need the actual current date/time value
---------------------------------------------------------------------------------------
-- compute start of month
--
-- We do this by subtracting the day number of 'today' from the date/time value #today.
-- That gives us the last day of the prior month. Then we add one day to get the first
-- day of the current month.
---------------------------------------------------------------------------------------
set #dtFrom = dateadd(day,1-day(#Today),#Today)
---------------------------------------------------------------------------------------
-- finally, make your query for 'current month to date'
---------------------------------------------------------------------------------------
select *
from dbo.foobar t
where t.recorded_date between #dtFrom and #dtThru
If you are asking which of these 2 queries is better from a performance standpoint:
DECLARE #now datetime = GETDATE()
SELECT *
FROM yourTable
WHERE Production_Date >= DATEADD(mm, DATEDIFF(mm, 0, #now), 0)
AND Production_Date < #now
SELECT *
FROM yourTable
WHERE YEAR(Production_Date) = YEAR(#now)
AND MONTH(Production_Date) = MONTH(#now)
AND Production_Date < #now
Then the first one would be, since it will use the index on Production_Date if there is one. However, they should both return the same results.
I realise different solutions will have different variations of what "Working Days" means but in my case I mean Monday to Friday inclusive.
Basically I have Created a function to do the calculation for me and my current solution works. My concern (and reason for asking this question) is that I am worried that this is a bad way of achieving this because the function is being called with a very high frequency. In the last 3 months it has been called 12 million times on a production system, with the average worker time 44ms.
This lead me to wonder if this is the correct way of achieving solution.
Firstly here is the function I created:
CREATE FUNCTION [dbo].[fn_WorkDays]
(
#StartDate DATETIME,
#EndDate DATETIME = NULL --#EndDate replaced by #StartDate when DEFAULTed
)
RETURNS INT
AS
BEGIN
--===== Declare local variables
--Temporarily holds #EndDate during date reversal
DECLARE #Swap DATETIME
--===== If the Start Date is null, return a NULL and exit
IF #StartDate IS NULL
RETURN NULL
--===== If the End Date is null, populate with Start Date value
-- so will have two dates (required by DATEDIFF below)
IF #EndDate IS NULL
SELECT #EndDate = #StartDate
--===== Strip the time element from both dates (just to be safe) by converting
-- to whole days and back to a date. Usually faster than CONVERT.
-- 0 is a date (01/01/1900 00:00:00.000)
SELECT #StartDate = DATEADD(dd,DATEDIFF(dd,0,#StartDate),0),
#EndDate = DATEADD(dd,DATEDIFF(dd,0,#EndDate) ,0)
--===== If the inputs are in the wrong order, reverse them
IF #StartDate > #EndDate
SELECT #Swap = #EndDate,
#EndDate = #StartDate,
#StartDate = #Swap
--===== Calculate and return the number of workdays using the
-- input parameters. This is the meat of the function.
-- This is really just one formula with a couple of parts
-- that are listed on separate lines for documentation
-- purposes.
RETURN (
SELECT
--Start with total number of days including weekends
(DATEDIFF(dd,#StartDate,#EndDate)+1)
--Subtact 2 days for each full weekend
-(DATEDIFF(wk,#StartDate,#EndDate)*2)
--If StartDate is a Sunday, Subtract 1
-(CASE WHEN DATENAME(dw,#StartDate) = 'Sunday'
THEN 1
ELSE 0
END)
--If EndDate is a Saturday, Subtract 1
-(CASE WHEN DATENAME(dw,#EndDate) = 'Saturday'
THEN 1
ELSE 0
END)
)
END
As a simple example of its use I would run this type of query:
SELECT MYTABLE.EntryDate
,dbo.fn_WorkDays(MYTABLE.EntryDate, getutcdate()) as WorkingDays
FROM MYTABLE
MyTable could contain 5000 rows all with different Dates in the EntryDate Column (5000 calls to Function)
My question is I am missing something here in the way that I am doing this, would it be beneficial to create a lookup table for this (but that is a lot of combinations of dates)
Any thoughts, improvements or recommendations would be appreciated...
I don't think there's a lot you can do with the UDF tbh - having it calculated at run-time like this in SQL is always going to incur a hit to some degree.
So, ideally (and this may not be possible as I don't know the full picture), I think what I'd do is store the WorkingDays number in your table and calculate it ONCE when the record is created. If that's not possible (i.e. when the record is created, you don't have an "end date" so it has to be worked out using "now") then I'd be considering a nightly scheduled job to go and recalculate all those particular records so that they are updated each day - then when an "end date" does get entered, that record does not get included in this batch update.
The benefits of this, are you offload the calculations to a quieter period, and only do the calculations once per day. The query becomes a lot simpler and more performant as it can just read the WorkingDays number from the table.
If that's not an option, then I'd suggest doing the calculations in the front end, remove the hit from the DB.
There's two problems here:
Calculating the number of days between two dates,
Identifying whether or not a give Date is a "business day".
The second includes easy ones like "weekday" versus "weekend", holidays (secular, religious, and legal), etc.
You'll need to solve both.
The first is easier, because relational databases will have functions to help you. It's the second that's harder and more variable, because it changes by locale and business.