SQL Date-Time Conversion with nested converts experiences random conversion errors - sql-server

Looking for assistance with a strange issue if anyone has ideas:
I have a SQL that statement works most of the time in a T-SQL script but crashes occasionally. I have identified the data that a crash occurs on and cannot identify any difference between data rows that work.
The goal of this code is to add the time to an already existing datetime value that has 00:00:00 as the time from the second time column (as outlined below). My goal is to combine both columns into YYYY-MM-DD HH:MM:SS format, but I had to convert them to char first to trim off the orignal 00:00:00.
Columns
LogDate - contains date only in DateTime format (YYYY-MM-DD HH:MM:SS)
LogTime - contains the time of the action and is in varchar format (HH:MM)
SQL Conversion
SELECT CONVERT(DATETIME, CONVERT(CHAR(8), LogDate, 112) + ' ' + CONVERT(CHAR(8), LogTime, 108))
FROM TestTable
WHERE EventSerial = '100001'
However, if I change the EventSerial in the above statement to a different row, such as '100002', the statement works.
The data for each row is below:
EventSerial 100001's values:
LogDate: 2015-04-02 00:00:00.000
LogTime: 10:04
EventSerial 100002's values:
LogDate: 2015-04-02 00:00:00.000
LogTime: 10:48
Running with data set 1 fails, running with data set 2 produces output. Also, running the code without the final datetime conversion works, or if I run the code with the string manually it works (as outlined below:)
SELECT CONVERT(CHAR(8), LogDate, 112) + ' ' + CONVERT(CHAR(8), LogTime, 108)
FROM TestTable
WHERE EventSerial = '100001'
SELECT CONVERT(DATETIME, '20150402 10:48')
SELECT CONVERT(DATETIME, '20150402 10:04')
Any suggestions, I'm sure its something silly that I'm missing (and I probably took the long way around the issue anyway. The desired output would be 2015-04-02 10:04:00

First, datetime has no format. (why?)
Second, you don't need to convert the datetime value to char to add hours and minutes, just use DateAdd:
SELECT DATEADD(Minute,
CAST(RIGHT(LogTime, 2) as int),
DATEADD(Hour,
CAST(LEFT(LogTime, 2) as int),
LogDate
)
)
FROM TestTable
WHERE EventSerial = '100001'
Also, note that convert does not hold a style for yyyymmdd hh:mm
Note: code was written directly here, there might be some mistakes.

I'm not sure why you're getting the error... possibly there are some unseen characters in your varchar time field... like a tab or something maybe? Try this query:
SELECT ascii(substring(LogTime,1,1)) Char1,
ascii(substring(LogTime,2,1)) Char2,
ascii(substring(LogTime,3,1)) Char3,
ascii(substring(LogTime,4,1)) Char4,
ascii(substring(LogTime,5,1)) Char5
FROM TestTable
WHERE EventSerial = '100001'
It should show these results:
Char1 Char2 Char3 Char4 Char5
----------- ----------- ----------- ----------- -----------
49 48 58 48 52
(1 row(s) affected)
This would be a bit more efficient:
select dateadd(minute, datediff(minute,0, LogTime), LogDate)
FROM TestTable
But this assumes that your date field always has 00:00:00 time information. If you want to be sure that is stripped out as well you could use:
select dateadd(minute, datediff(minute,0, LogTime), dateadd(day, datediff(day, 0, Logdate),0))
FROM TestTable

Related

How to convert a date with milliseconds into a date with just 000 instead of the milliseconds?

My incoming data contains a date in the following format:
2019-05-01 15:20:51.920
but after getting loaded into the target system it gets converted into the following :
2019-05-01 15:20:51.000
I would like to convert the source data and compare it with my target system since my target data is as per requirements.
ie 2019-05-01 15:20:51.920 should be converted to 2019-05-01 15:20:51.000
I am looking at convert functions as follows but it is only trimming the ms (using getdate(0 as a example):
select CONVERT(DATETIME2(3),getdate())
--2019-05-01 15:20:51.920
select CONVERT(DATETIME2(2),getdate())
--2019-05-01 15:20:51.92
Could someone tell me how I could achieve this? Thanks!
This should be also a more or less performant way:
SELECT GETDATE() AS OriginalValue
, CONVERT(DATETIME, CONVERT(VARCHAR(19), GETDATE(), 121)) ValueWithoutMilliseconds
The idea behind is to trim milliseconds, by cutting the last 4 characters of a fixed output format and then convert it back to a datetime
An output:
OriginalValue | ValueWithoutMilliseconds
----------------------------------------------------
2019-05-01 21:44:58.473 | 2019-05-01 21:44:58.000
Why not just cast the fractional seconds portion away using datetime2(0)?
declare #dateValue datetime2(3) = SysDateTime();
select Convert(datetime2(3), Convert(datetime2(0), #dateValue));
May be this should be give result what you are looking for,
DECLARE #TodayDatatime datetime
SET #TodayDatatime = DateAdd(DAY, DateDiff(DAY, 0, GetDate()), 0)
select GETDATE()
SELECT DateAdd(SECOND, DateDiff(SECOND, #TodayDatatime, GetDate()), #TodayDatatime)
Using the DATEADD/DATEDIFF method to truncate dates can work for this. Another option is to convert to datetime2(0).
SELECT DATEADD( SS, DATEDIFF(SS, '2020', aDate), '2020'),
CAST( aDate AS datetime2(0))
FROM (VALUES( CAST( '2019-05-01 15:20:51.920' AS datetime2(3))))x(aDate)
Note that the first option will truncate and the second one will round the value.

SQL Query - Average time from datetime field

I'm struggling with what I thought would be a simple SQL query. Running SQL Server 2014
I have an SQL table, "Visits":
Id | EntryTime | Duration
And I want to find the average entry TIME OF DAY between two dates, taking into account all records between those dates.
so if my EntryTime field between my dates is:
2016-04-28 12:00:00
2016-04-20 10:00:00
2016-04-19 08:00:00
2016-04-17 10:00:00
Then the average time returned should just be:
10:00:00
The date should not be taken into account at all, and it should be returned in string format, or a manner which returns ONLY 10:00:00.
create table mytimes(
id int identity,
mydatetime datetime
)
insert into mytimes (mydatetime) values ('2016-04-28 12:00:00')
insert into mytimes (mydatetime) values ('2016-04-20 10:00:00')
insert into mytimes (mydatetime) values ('2016-04-19 08:00:00')
insert into mytimes (mydatetime) values ('2016-04-17 10:00:00')
SELECT Cast(DateAdd(ms, AVG(CAST(DateDiff( ms, '00:00:00', cast(mydatetime as time)) AS BIGINT)), '00:00:00' ) as Time )
from mytimes
-- where mydatetime between XXX and YYY
SELECT convert(varchar(8), Cast(DateAdd(ms, AVG(CAST(DateDiff( ms, '00:00:00', cast(mydatetime as time)) AS BIGINT)), '00:00:00' ) as Time ))
from mytimes
-- where mydatetime between XXX and YYY
output-1 10:00:00.0000000 - this is an actual Time type that you can do more with if needed
output-2 10:00:00 - this is output as a varchar(8)
Add your where clause as you see fit
The steps include
Casting to a Time type from a DateTime.
Using the AVG on Time, this is not supported by type Time so you have to first convert Time to milliseconds.
Converting the milliseconds back to a Time type
To avoid Arithmetic overflow error converting expression to data type int you can cast the result of DateAdd to a BigInt. Alternatively you can use seconds instead of milliseconds in the DateDiff function which should work unless your result set is overly large.
SO Sources:
T-SQL calculating average time
How to get Time from DateTime format in SQL?
Operand data type time is invalid for avg operator…?
SELECT CONVERT(TIME, DATEADD(SECOND, AVG(DATEDIFF(SECOND, 0, CONVERT(TIME, EntryTime ))), 0))
FROM Visits
WHERE EntryTime >= #begin AND EntryTime <= #end
The idea came from here: T-SQL calculating average time
Yap, you can use the Time() to get this done.
Your query becomes like this (modify accordingly)
SELECT COUNT(*) FROM Visits WHERE TIME(EntryTime) > '06:00' AND EntryTime < '18:00';

SQL Server Query - Selecting values BETWEEN 2 Dates

I am getting some problems in getting one query done.
I have 2 strings which are suppose to store time in this format (HH:mm Ex: Date1 -> '20:20' and Date2 -> '21:20'). I want to get the current time (CONVERT(VARCHAR(5),getdate(),108) -> to get only 'HH:mm') and get all rows that are between the current date.
The idea is: Select * where "getdate() between date1 and date2", this is not a query, only the idea.
Edit:
Time1 Time2
08:20 09:05
09:05 09:50
10:05 10:50
10:50 11:35
11:45 12:30
12:30 13:15
13:35 14:20
Thx for the solutions, I will check them, but one question how can i "fix" the problem when it's 9:05 (for example), I will get both rows/records, I assume i have to go to the seconds right?
The following constructs two DATETIMEs, based on TIME only, from your input strings (#TimeStr1 and #TimeStr2) then strips the DATE part from GETDATE(). Then you can easily do the query with BETWEEN.
DECLARE
#CurrentTime DATETIME,
#TimeStr1 VARCHAR(12),
#TimeStr2 VARCHAR(12),
#Time1 DATETIME,
#Time2 DATETIME
SET #TimeStr1 = '20:20'
SET #TimeStr2 = '21:20'
SET #Time1 = CAST('1900-01-01 ' + #TimeStr1 AS DATETIME)
SET #Time2 = CAST('1900-01-01 ' + #TimeStr2 AS DATETIME)
-- The following strips the date from datetime leaving only a time
SET #CurrentTime = DateAdd(ss, DateDiff(ss, DateAdd(dd, DateDiff(dd, 0, GetDate()), 0), GetDate()), 0)
SELECT * FROM YourTable WHERE #CurrentTime BETWEEN #Time1 AND #Time2
This will work for all versions of SQL SERVER. If you are using a version >= 2008 then a simpler version of the above exists.
If you are worried about overlaps (eg your "09:05" problem) use the following query that avoids the problem by using > and <= rather then BETWEEN
SELECT * FROM YourTable WHERE #CurrentTime > #Time1 AND #CurrentTime <= #Time2

Format a string type into a datetime format

i have a column (nvarchar),there are datetimes in there but as a string.I want to change that column's type into smalldatetime but before i want to format all string dates into a same datetime format.But i cant succeed it.Some of them are like this "2007-07-10",some of them are reverse of this "02.07.2008". How can i change them into one format for example dd.mm.yyyy.
(ms sql server 2008)
If you only have those 2 formats, this is how you can do it.
I am assuming
'02.07.2008' has format 'dd.mm.yyyy'
'2007-07-10' has format 'yyyy-mm-dd'
If you have more formats than that, you have to alter the 'case' in the 'select' clause accordingly
declare #t table (a varchar(12))
insert #t values ('02.07.2008')
insert #t values ('2007-07-10')
select convert(date, a, case when charindex('-',a) = 5 then 21 else 103 end)
from #t
Output
2008-07-02
2007-07-10
The format is standard for a date field, but if you want to store it as a varchar, you can format it as dd.mm.yyyy like this instead.
select replace(convert(varchar, convert(date, a, case when charindex('-',a) = 5
then 21 else 103 end), 103), '/', '.')
from #t
Output
02.07.2008
10.07.2007
I have to point out that you should always store a date as a date and not a varchar in the database when possible.
You can't do it unless you know the exact format.
Think about the different formats - in some countries the month comes first, while in the other it's the day. So if you don't know whether 02.07.2008 means July 2th, or it means Feb 7th, then you can't possibly accomplish your task.

SQL Server remove milliseconds from datetime

select *
from table
where date > '2010-07-20 03:21:52'
which I would expect to not give me any results... EXCEPT I'm getting a record with a datetime of 2010-07-20 03:21:52.577
how can I make the query ignore milliseconds?
You just have to figure out the millisecond part of the date and subtract it out before comparison, like this:
select *
from table
where DATEADD(ms, -DATEPART(ms, date), date) > '2010-07-20 03:21:52'
If you are using SQL Server (starting with 2008), choose one of this:
CONVERT(DATETIME2(0), YourDateField)
LEFT(RTRIM(CONVERT(DATETIMEOFFSET, YourDateField)), 19)
CONVERT(DATETIMEOFFSET(0), YourDateField) -- with the addition of a time zone offset
Try:
SELECT *
FROM table
WHERE datetime >
CONVERT(DATETIME,
CONVERT(VARCHAR(20),
CONVERT(DATETIME, '2010-07-20 03:21:52'), 120))
Or if your date is an actual datetime value:
DECLARE #date DATETIME
SET #date = GETDATE()
SELECT CONVERT(DATETIME, CONVERT(VARCHAR(20), #date, 120))
The conversion to style 120 cuts off the milliseconds...
select * from table
where DATEADD(ms, DATEDIFF(ms, '20000101', date), '20000101') > '2010-07-20 03:21:52'
You'll have to trim milliseconds before comparison, which will be slow over many rows
Do one of these to fix this:
created a computed column with the expressions above to compare against
remove milliseconds on insert/update to avoid the read overhead
If SQL Server 2008, use datetime2(0)
Use CAST with following parameters:
Date
select Cast('2017-10-11 14:38:50.540' as date)
Output: 2017-10-11
Datetime
select Cast('2017-10-11 14:38:50.540' as datetime)
Output: 2017-10-11 14:38:50.540
SmallDatetime
select Cast('2017-10-11 14:38:50.540' as smalldatetime)
Output: 2017-10-11 14:39:00
Note this method rounds to whole minutes (so you lose the seconds as well as the milliseconds)
DatetimeOffset
select Cast('2017-10-11 14:38:50.540' as datetimeoffset)
Output: 2017-10-11 14:38:50.5400000 +00:00
Datetime2
select Cast('2017-10-11 14:38:50.540' as datetime2)
Output: 2017-10-11 14:38:50.5400000
For this particular query, why make expensive function calls for each row when you could just ask for values starting at the next higher second:
select *
from table
where date >= '2010-07-20 03:21:53'
Use 'Smalldatetime' data type
select convert(smalldatetime, getdate())
will fetch
2015-01-08 15:27:00
There's more than one way to do it:
select 1 where datediff(second, '2010-07-20 03:21:52', '2010-07-20 03:21:52.577') >= 0
or
select *
from table
where datediff(second, '2010-07-20 03:21:52', date) >= 0
one less function call, but you have to be beware of overflowing the max integer if the dates are too far apart.
One more way I've set up SQL Server queries to ignore milliseconds when I'm looking for events from a particular second (in a parameter in "YYYY-MM-DD HH:TT:SS" format) using a stored procedure:
WHERE
...[Time_stamp] >= CAST(CONCAT(#YYYYMMDDHHTTSS,'.000') as DateTime) AND
...[Time_stamp] <= CAST(CONCAT(#YYYYMMDDHHTTSS,'.999') as DateTime)
You could use something similar to ignore minutes and seconds too.
Please try this
select substring('12:20:19.8470000',1,(CHARINDEX('.','12:20:19.8470000',1)-1))
(No column name)
12:20:19
I'm very late but I had the same issue a few days ago. None of the solutions above worked or seemed fit. I just needed a timestamp without milliseconds so I converted to a string using Date_Format and then back to a date with Str_To_Date:
STR_TO_DATE(DATE_FORMAT(your-timestamp-here, '%Y-%m-%d %H:%i:%s'),'%Y-%m-%d %H:%i:%s')
Its a little messy but works like a charm.
May be this will help..
SELECT [Datetime] = CAST('20120228' AS smalldatetime)
o/p:
2012-02-28 00:00:00
Review this example:
declare #now datetimeoffset = sysdatetimeoffset();
select #now;
-- 1
select convert(datetimeoffset(0), #now, 120);
-- 2
select convert(datetimeoffset, convert(varchar, #now, 120));
which yields output like the following:
2021-07-30 09:21:37.7000000 +00:00
-- 1
2021-07-30 09:21:38 +00:00
-- 2
2021-07-30 09:21:37.0000000 +00:00
Note that for (1), the result is rounded (up in this case), while for (2) it is truncated.
Therefore, if you want to truncate the milliseconds off a date(time)-type value as per the question, you must use:
declare #myDateTimeValue = <date-time-value>
select cast(convert(varchar, #myDateValue, 120) as <same-type-as-#myDateTimeValue>);

Resources