Standard syntax for previous Monday/Sunday dates using GETDATE() - sql

These seem a bit verbose and maybe over-complicated; can I simplify and/or make them more readable?
They are returning the dates (as integer type) of the previous week's Monday and Sunday.
declare #sDate int = CONVERT(CHAR(8),DATEADD(wk, DATEDIFF(wk,0,GETDATE())-1, 0),112),
#edate int = CONVERT(CHAR(8),DATEADD(wk, DATEDIFF(wk,0,GETDATE()), -1),112);

From my experience, if you convert a date to char for any reason other than presentation, you have made a mistake. Also - if you call GetDate twice, you should expect different answers.
DECLARE
#Now datetime,
#WeekStart datetime,
#LastWeekStart datetime,
#LastWeekEnd datetime
SET #Now = GetDate()
SET #WeekStart = DateAdd(wk, DateDiff(wk, 0, #Now), 0) --standard time trimmer
SET #LastWeekStart = DateAdd(wk, -1, #WeekStart)
SET #LastWeekEnd = DateAdd(dd, -1, #WeekStart)
SELECT #Now, #WeekStart, #LastWeekStart, #LastWeekEnd
Also be aware that there is a sql setting that controls where sql server thinks that the week starts, so this code may not give Monday->Sunday depending on that setting.

The simplest way is to use a calendar table. A calendar table might simplify your query to something along these lines.
-- Select the previous Sunday. The right inequality operator depends
-- on exactly what *you* mean by "previous".
select max(calendar_date)
from calendar
where calendar_date < current_date
and calendar_day_of_week = 'Sun';

Related

Is there a way to preserve locale when format a datetime in SQL? [duplicate]

I have an sql DateTime (ms sql server) and want to extract the same date without the seconds:
e.g. 2011-11-22 12:14:58.000 to become: 2011-11-22 12:14:00.000
How can I do this? I was thinking to use DATEADD in combination with DATEPART but seems very error prone (besides performance issues)
SELECT DATEADD(MINUTE, DATEDIFF(MINUTE, 0, yourcolumn), 0) FROM yourtable
This will be effective, if you don't want a slow conversion between datatypes.
For a solution that truncates using strings try this:
SELECT CAST(CONVERT(CHAR(16), GetDate(),20) AS datetime)
CHAR(16) works only if our variable is converted to ODBC canonical format, as shown above by using 20 as the format specifier.
DECLARE #date DateTime = '2011 Nov 22 12:14:55';
SELECT CONVERT(Char(16), #date ,20) AS datetime
Results:
| datetime |
|------------------|
| 2011-11-22 12:14 |
Then you simply cast back to a DateTime type to continue using the value.
NOTE: This is only viable for data types that do not carry TimeZone info.
Also type conversions to VarChar and back are usually LESS performant than using DateTime functions that use numeric operations internally.
Consider other solutions posted if performance is a concern or if you must retain timezone information.
DECLARE #TheDate DATETIME
SET #TheDate = '2011-11-22 12:14:58.000'
DATEADD(mi, DATEDIFF(mi, 0, #TheDate), 0)
In queries
/* ...all records in that minute; index-friendly expression */
WHERE TheDate BETWEEN DATEADD(mi, DATEDIFF(mi, 0, #TheDate), 0)
AND DATEADD(mi, DATEDIFF(mi, 0, #TheDate) + 1, 0)
Date and time needs carefully and not being converted as TEXT.
My personal solution:
CREATE FUNCTION [dbo].[fnDateTimeTruncated]
(
#datetime DATETIME
)
RETURNS DATETIME
AS
BEGIN
RETURN DATETIMEFROMPARTS ( year(#datetime), month(#datetime), day(#datetime), DATEPART(hh,#datetime), DATEPART(mi,#datetime), 0, 0)
END
Edited:
Regarding http://blog.waynesheffield.com/wayne/archive/2012/03/truncate-a-date-time-to-different-part/, DateAdd has a better performance.
Thanks to t-clausen.dk
With a little fiddling around, this seems to work well:
SELECT CAST(CONVERT(CHAR(17), bl.[time],113) AS varchar(17))
Result given: 2011-11-22 12:14
The exact way I'm using it in my query as part of the selection list :
,CAST(CONVERT(CHAR(17), bl.[time],113) AS varchar(17))
+ ' (UTC +0)' AS [TIME]
Gives me the result: 15 Dec 2017 06:43 (UTC +0)
From SQL Server 2014, You can use Format function for this.
for Ex.
declare #Startdate datetime = '2020-11-07 15:27:50.713'
set #Startdate = Convert(datetime,FORMAT(#Startdate, 'yyyy-MM-dd HH:mm'))
> Result is
2020-11-07 15:27:00.000
If there is no milliseconds, than
DECLARE #dt datetime2 = '2011-11-22 12:14:58.000';
DECLARE #goalDt datetime2 = DATEADD(second,-DATEPART(second,#dt), #dt);
To remove a milliseconds part, add
SET #goalDt = DATEADD(millisecond,-DATEPART(millisecond,#goalDt ), goalDt dt);
To Round Off it:
DECLARE #TheDate DATETIME;
SET #TheDate = '2019-1-2 12:14:58.400';
SELECT CAST(#TheDate AS SMALLDATETIME);
To just Truncate:
DECLARE #TruncTheDate DATETIME;
SET #TruncTheDate = '2019-1-2 12:14:58.400';
SELECT DATEADD(mi, DATEDIFF(mi, 0, #TruncTheDate), 0);
select substring(cast(cast(getdate() as time(0)) as char(8)),0,6)

A way to extract from a DateTime value data without seconds

I have an sql DateTime (ms sql server) and want to extract the same date without the seconds:
e.g. 2011-11-22 12:14:58.000 to become: 2011-11-22 12:14:00.000
How can I do this? I was thinking to use DATEADD in combination with DATEPART but seems very error prone (besides performance issues)
SELECT DATEADD(MINUTE, DATEDIFF(MINUTE, 0, yourcolumn), 0) FROM yourtable
This will be effective, if you don't want a slow conversion between datatypes.
For a solution that truncates using strings try this:
SELECT CAST(CONVERT(CHAR(16), GetDate(),20) AS datetime)
CHAR(16) works only if our variable is converted to ODBC canonical format, as shown above by using 20 as the format specifier.
DECLARE #date DateTime = '2011 Nov 22 12:14:55';
SELECT CONVERT(Char(16), #date ,20) AS datetime
Results:
| datetime |
|------------------|
| 2011-11-22 12:14 |
Then you simply cast back to a DateTime type to continue using the value.
NOTE: This is only viable for data types that do not carry TimeZone info.
Also type conversions to VarChar and back are usually LESS performant than using DateTime functions that use numeric operations internally.
Consider other solutions posted if performance is a concern or if you must retain timezone information.
DECLARE #TheDate DATETIME
SET #TheDate = '2011-11-22 12:14:58.000'
DATEADD(mi, DATEDIFF(mi, 0, #TheDate), 0)
In queries
/* ...all records in that minute; index-friendly expression */
WHERE TheDate BETWEEN DATEADD(mi, DATEDIFF(mi, 0, #TheDate), 0)
AND DATEADD(mi, DATEDIFF(mi, 0, #TheDate) + 1, 0)
Date and time needs carefully and not being converted as TEXT.
My personal solution:
CREATE FUNCTION [dbo].[fnDateTimeTruncated]
(
#datetime DATETIME
)
RETURNS DATETIME
AS
BEGIN
RETURN DATETIMEFROMPARTS ( year(#datetime), month(#datetime), day(#datetime), DATEPART(hh,#datetime), DATEPART(mi,#datetime), 0, 0)
END
Edited:
Regarding http://blog.waynesheffield.com/wayne/archive/2012/03/truncate-a-date-time-to-different-part/, DateAdd has a better performance.
Thanks to t-clausen.dk
With a little fiddling around, this seems to work well:
SELECT CAST(CONVERT(CHAR(17), bl.[time],113) AS varchar(17))
Result given: 2011-11-22 12:14
The exact way I'm using it in my query as part of the selection list :
,CAST(CONVERT(CHAR(17), bl.[time],113) AS varchar(17))
+ ' (UTC +0)' AS [TIME]
Gives me the result: 15 Dec 2017 06:43 (UTC +0)
From SQL Server 2014, You can use Format function for this.
for Ex.
declare #Startdate datetime = '2020-11-07 15:27:50.713'
set #Startdate = Convert(datetime,FORMAT(#Startdate, 'yyyy-MM-dd HH:mm'))
> Result is
2020-11-07 15:27:00.000
If there is no milliseconds, than
DECLARE #dt datetime2 = '2011-11-22 12:14:58.000';
DECLARE #goalDt datetime2 = DATEADD(second,-DATEPART(second,#dt), #dt);
To remove a milliseconds part, add
SET #goalDt = DATEADD(millisecond,-DATEPART(millisecond,#goalDt ), goalDt dt);
To Round Off it:
DECLARE #TheDate DATETIME;
SET #TheDate = '2019-1-2 12:14:58.400';
SELECT CAST(#TheDate AS SMALLDATETIME);
To just Truncate:
DECLARE #TruncTheDate DATETIME;
SET #TruncTheDate = '2019-1-2 12:14:58.400';
SELECT DATEADD(mi, DATEDIFF(mi, 0, #TruncTheDate), 0);
select substring(cast(cast(getdate() as time(0)) as char(8)),0,6)

Is there an easier way to set a DateTime in SQL Server to be 23:59:59

Good Morning All,
I'm trying to refactor an SQL stored procedure. I'm no SQL expert, but something tells me there must be a better way to do this.
IF #ipv_dtEndDate IS NOT NULL
BEGIN
SET #ipv_dtEndDate = DATEADD(hh,23,#ipv_dtEndDate)
SET #ipv_dtEndDate = DATEADD(mi,59,#ipv_dtEndDate)
SET #ipv_dtEndDate = DATEADD(ss,59,#ipv_dtEndDate)
END
This value is used later inside a WHERE clause. These filters seem difficult to understand to me. I was hoping to come up with a cleaner implementation.
AND qtrh.StatusTime <= IsNull(#ipv_dtEndDate, qtrh.StatusTime)
And this date calculation...
AND DATEDIFF(ss,qtrh.StatusTime,ISNULL(#dtNow,DATEADD(ss,-1,qtrh.StatusTime))) < DATEDIFF(ss,ISNULL(#dtDateOptionCompare,GETDATE()),GETDATE())
... seems quite convoluted and unreadable. If any SQL gurus out there have some suggestions on how I can improve this, I would love to hear some ideas. Thanks for your time. Have a terrific holiday weekend.
Cheers,
~ck in San Diego
If the only use of #ipv_dtEndDate is inside the Where clause, you could remove the entire IF #ipv_dtEndDate IS NOT NULL block, and replace the condition in the SQL query with:
AND qtrh.StatusTime < DATEADD(dd,1,IsNull(#ipv_dtEndDate, qtrh.StatusTime))
(Strictly speaking, you will now also be including StatusTime values between 23:59:59 and 00:00:00, which were previously excluded.)
How about this?
SET #ipv_dtEndDate = CONVERT(varchar, #ipv_dtEndDate, 101) + ' 23:59:59'
Usually I use < and the date for the next day rather than trying to run a <+ with the last second of midnight.
You could convert the date to varchar, add your "23:59:59" and then convert it back to datetime
If you feel uncomfortable with the varchar approach, you can do the following.
SET #ipv_dtEndDate = DATEADD(ss, DATEDIFF(ss, 0, '11:59:59'), #ipv_dtEndDate)
To convert a date without a time (rather, with the time set to "midnight the morning of") to the "end of the day",
you can just add the number of seconds:
DECLARE #ipv_dtEndDate datetime
SET #ipv_dtEndDate = 'Sep 3, 2010'
PRINT convert(varchar(50), #ipv_dtEndDate, 109) -- Before
SET #ipv_dtEndDate = dateadd(ss, 1439, #ipv_dtEndDate)
PRINT convert(varchar(50), #ipv_dtEndDate, 109) -- After
Of course, SQL datetime is accurate to the [333rd of a] millisecond, so the end of the day is actually:
DECLARE #ipv_dtEndDate datetime
SET #ipv_dtEndDate = 'Sep 3, 2010'
PRINT convert(varchar(50), #ipv_dtEndDate, 109) -- Before
SET #ipv_dtEndDate = dateadd(ms, 1439997, #ipv_dtEndDate)
PRINT convert(varchar(50), #ipv_dtEndDate, 109) -- After
Using the built in (and mathematically based) date/time functions will be more efficient than converting to character strings and back.
This will return the latest time for today:
SELECT DATEADD(ms, -2, DATEADD(dd, 1, DATEDIFF(dd, 0, GETDATE())))
Just substitute GETDATE() with whatever you want to be inclusive. So, in your example:
AND qtrh.StatusTime <= DATEADD(ms, -2, DATEADD(dd, 1, DATEDIFF(dd, 0, #ipv_dtEndDate)))

Fastest way to check date range

I store events in SQLServer 2005 where the time the event occured is important and must be stored in the datebase. What is the fastest way to write the date range check in the where clause to ensure everything on that day is selected?
Currently when #DateStart and #DateEnd are passed in I set #DateStart to midnight and set #DateEnd to the last instant before midnight as the very first thing to catch every possible event on the day.
IF (#DateStart IS NOT NULL)
BEGIN
SET #DateStart = CAST (
( CAST (DATEPART (yyyy,#DateStart) AS NVARCHAR(4)) +'/'+
CAST (DATEPART (mm,#DateStart) AS NVARCHAR(2)) +'/'+
CAST (DATEPART (dd,#DateStart) AS NVARCHAR(2)) +' '+
'00:00:00.000'
)
AS DATETIME)
END
IF (#DateEnd IS NOT NULL)
BEGIN
SET #DateEnd = CAST (
( CAST (DATEPART (yyyy,#DateEnd) AS NVARCHAR(4)) +'/'+
CAST (DATEPART (mm,#DateEnd) AS NVARCHAR(2)) +'/'+
CAST (DATEPART (dd,#DateEnd) AS NVARCHAR(2)) +' '+
'23:59:59.997'
)
AS DATETIME
)
END
So the where clause is very easy to read:
WHERE ( EventDate >= #DateStart AND EventDate <= #DateEnd )
Thanks,
You could always use the alternate syntax of WHERE EventDate BETWEEN #DateStart AND #DateEnd
Your where clause would look like;
WHERE DateCol >= DATEADD(dd, DATEDIFF(dd, 0, #DateStart), 0) --Midnight on the Start date
AND DateCol < DATEADD(dd, DATEDIFF(dd, 0, #DateEnd + 1), 0) --Midnight of the day after End date
and all your IF statement would do is handle null parameters (i.e. IF #DateEnd IS NULL THEN SET #DateEnd = #DateStart)
You probably want to Index on DATEADD(dd, DATEDIFF(dd, 0, DateCol), 0) if your table is large.
the fastest way to truncate a date, previous midnight:
DATEADD(day, DATEDIFF(day, '19010101', LastModifiedDate), '19010101')
next midnight:
DATEADD(day, DATEDIFF(day, '19010101', LastModifiedDate)+1, '19010101')
You can also wrap this up as an inline UDF:
http://sqlblog.com/blogs/alexander_kuznetsov/archive/2008/05/23/reuse-your-code-with-cross-apply.aspx
Try this:
WHERE DATEPART(yyyy, EventDate) = DATEPART(yyyy, getdate())
AND DATEPART(dy, EventDate) = DATEPART(dy, getdate()) --day of year
EDIT To address Tom H's comment:
I've never had any luck with indexes on date fields; what has always worked better for me was extra integer columns to handle the year and day of year values and indexed those instead.
AlexK's is probably the best idea all around.
The only thing I would worry about is performance of using functions in the predicate.
You should consider adding a column called searchdate, or something similar, to your table to contain the date without the time. I'd also suggest indexing this column, especially if you're going to be searching against the column's data a lot.
When you query on this column you will not have any scalar functions in your SQL to rob your indexes of their performance.
The con... well, additional storage space, time during insert to write the data (not much here though).
SQL Server 2008 has better support for date only data types. You could also check in on it.
I think this T-SQL is equivalent to the code you have:
-- set time portion of #DateStart back to midnight
SET #DateStart = CONVERT(DATETIME,CONVERT(VARCHAR(10),#DateStart,20),20)
-- advance time portion of #DateEnd to last instant before next midnight
SET #DateEnd = CONVERT(DATETIME,CONVERT(VARCHAR(11),#DateEnd,20)+'23:59:59.997',21)
The CONVERT function will handle NULLS, so there's no need for a separate test for a NULL value (unless, of course, you are doing some special handling other than what you are showing, and are not passing the NULL values through to query predicate (i.e. WHERE clause). Or, perhaps you are expecting a lot of the arguments to be NULL, and you want to avoid the overhead of the calls to CONVERT.
However, I concur with Tom H.'s recommendation, and avoid messing with subtracting milliseconds, and instead set the #DateEnd to midnight of the following day e.g.
-- advance #DateEnd to midnight of following day
SET #DateEnd = DATEADD(day,1,CONVERT(DATETIME,CONVERT(VARCHAR(10),#DateEnd,20),20))
and change the predicate to do a range test like this:
WHERE (EventDate >= #DateStart AND EventDate < #DateEnd)
You can avoid the separate SET statements, and move the expressions straight into the query, but I don't expect that will improve performance any, and make the SQL statement harder to read, you'd definitely want to keep the comments ...
WHERE (EventDate >= CONVERT(DATETIME,CONVERT(VARCHAR(10),#DateStart,20),20)
AND EventDate

Limiting a date range with exactness in MS SQL / SQL Server 2005

I want to limit a report to return records from Date A through Date B. This is what I have been doing:
declare #startDate varchar(20)
declare #endDate varchar(20)
set #startDate = '01/01/2008'
set #endDate = '04/01/2008'
-- test what are the start and end dates
select min(date),max(date) from view_Inspections
where date between #startDate and #endDate
... which I was told returned records from 12 am Jan 1st through 11:59 pm March 31st (that midnight is the default when no time is indicated). But I noticed a discrepancy, which is if a record has a time of 00:00:00 that it will be part of this set.
Is there a more exact way of doing this so it will return exactly the date range I want?*
I tried using time:
declare #startDate varchar(20)
declare #endDate varchar(20)
set #startDate = '01/01/2008 00:00:00'
set #endDate = '04/01/2008 11:59:59'
-- test what are the start and end dates
select min(date),max(date) from view_Inspections
where date between #startDate and #endDate
... but I noticed something wonky: SQL Server will ROUND the hundreth-second up by half. So I get that April 1st record (ha! April Fool's record! grr) if I use any time later than 11:59:29. Why is that?
(I feel sure there is. I'm new at this. Thanks for your help!)
There's always the easy option:
declare #startDate varchar(20)
declare #endDate varchar(20)
set #startDate = '01/01/2008'
set #endDate = '04/01/2008'
-- test what are the start and end dates
select min(date),max(date) from view_Inspections
where date >= #startDate
and date < #endDate
I suspect that the date column in view_Inspections is a SmallDateTime data type. This data type has 1 minute accuracy, which explains your unexpected results (rounding the seconds to the nearest minute).
The method Roland Shaw suggests is the best way to modify your query to accommodate your requirements.
The BETWEEN operator is inclusive, which is why you're seeing the results that you are in your first query. The rounding that you're seeing in your second query is going to be dependent on what exact datetime data type you are using in your table. (BTW, I think you're confusing seconds with hundredths of seconds). It looks like you're probably using a smalldatetime in your table, in which case the time is rounded to the nearest minute.
If your table is using datetime, try explicitly converting your #startDate and #endDate to DATETIME values (CAST(#endDate AS DATETIME)).
A quick note... even for DATETIME values, SQL Server is only accurate to the 3/100ths of a second, so 11:59:59.999 will get rounded up to 12:00:00.000.
You basically have three choices:
1) BETWEEN CAST('01/01/2008 00:00:00.000' AS DATETIME) AND CAST('03/31/2008 12:59:59.997' AS DATETIME)
2) YEAR(my_date) = 2008 AND MONTH(my_date) BETWEEN 1 AND 3
3) my_date >= CAST('01/01/2008 00:00:00.000' AS DATETIME) AND my_date < CAST('04/01/2008 00:00:00.000' AS DATETIME)
The first method isn't very intuitive and is error-prone in my opinion. The second method kills performance since indexes can't be used and it becomes much more complex if you can have searches that span years or begin/end in the middle of months. The third method, which Rowland suggested, is the best I think.
Simply try removing the time from the date field like so:
declare #startDate varchar(20)
declare #endDate varchar(20)
set #startDate = '01/01/2008'
set #endDate = '04/01/2008'
SELECT min(date),max(date) FROM view_Inspections
WHERE CAST(FLOOR(CAST(date AS FLOAT)) AS DATETIME) BETWEEN CAST(#startDate AS DATETIME) And CAST(#startDate AS DATETIME))
This will return everything from 01/01/2008 00:00:00 to 04/01/2008 11:59:59.999.
If you don't want 04/01 included, change your end date to 03/31/2008.
Your best solution is just create a BIGINT(10) field that called "julian", and store it in YYYYMMDD.
Then do the query
where julian >= '20120103' AND julian <= '20120203'