saving a VB timespan value in SQL database - sql-server

What data type can I store a VB timespan that is greater than 24 hours?
The following will not save as a SQL timespan value.
Dim span As TimeSpan = New TimeSpan(1, 2, 0, 30, 0)
Output
1.02:00:30
I would like to be able to perform calculations on this later.
SqlDbType.Time overflow. Value '1.02:00:30' is out of range. Must be between 00:00:00.0000000 and 23:59:59.9999999.
This Fiddler sample shows the results I am after, and I would like to store the results in a SQL database.
http://sqlfiddle.com/#!3/c7b0d/53

I initially suggested the SQL Server 2008 "datetimeoffset" datatype but that is wrong.
As previous comments correctly indicated, there is currently no data type in SQL Server that naturally stores and handles timespan values.
The simplest alternative would be to store the offset as a floating point value with the integer portion representing days, and the fractional part representing the portion of the day. This would allow for simple calculations. The math could be done as follows:
select
getdate(), -- Current datetime
CONVERT(date, getdate()), -- Current date
CONVERT(datetime, CONVERT(date, getdate())) +1, -- Add one day
CONVERT(datetime, CONVERT(date, getdate())) +1.75 -- Add a day + 3/4 or 18 hours
getdate() + .75, -- Current Time + 18 hours.
getdate() + (3/4) -- Current Time + 18 hours.
NOTE: You can only do this kind of datetime math with the [datetime] datatype. Therefore the need to convert the [date] datatype back to a [datetime].

Related

Get yesterday start and end datestamp and convert to Unix epoch

I have a Stored Procedure that will run within a time period (between 2-3 am), but not always at the exact same time during that period.
As part of this procedure I need to do 3 steps:
1: Get the start of yesterday's date
So if today is 13/08/2020 13:51:02 I need the query to return 12/08/2020 00:00:00 and do this dynamically
2: Get the end of yesterday's date
In the above this would return 12/08/2020 23:59:59
3: Convert both values into Unix EPOCH timestamps
I have used in the past on a similar issue (that was less time-sensitive) the below bit of Code:
declare #yesterday date
set #yesterday = (SELECT convert(datetime, DATEADD(day, -1 ,getdate()), 23 ))
The Problem here is that this gives a value that is exactly 24 hours in the past, so if the SP is run at 2:15 am - it's time stamp will be different when it's run at 2:23 am or 2:53 am.
Once I've got a method of getting the start and end date to always be correct - I'll then use something like this solution to convert the Datetime into Epoch timestamps, unless someone who answers this question has a snazzy method of doing it all in one (for which I would be eternally grateful)
To get midnight yesterday, do this:
DECLARE #yesterday DATETIME
= DATEADD(DAY, -1, CAST(GETDATE() AS DATE));
To get midnight today do
DECLARE #today DATETIME = CAST(GETDATE() AS DATE);
To filter for times that happened yesterday, do
WHERE ts >= #yesterday AND ts < #today ...
The CAST operation truncates the date/time value returned by GETDATE() to midnight.
The form of WHERE with >= and < copes with the edge case correctly. It also happens to exploit an index on the column I called ts.

Query epoch time using SQL Server to find date range

I have to query an SQL Server database and the table's values use Epoch time (an int. Here's an example - 1438005018). I am wondering how I can write a query so that I can say the following...
select
*
from
tablename
where
epochdate between 'yesterday at 12:00' and 'today at 12:00' --this is the part I'm not sure about.
Ideally, if it's easy, I'd like the query to use non-epoch logic as Epoch time confuses the crap out of me. Maybe there's a quick way of converting in SQL Server?
I posted a link above in the comments that may be a more practical solution if you're able to deploy functions in the database you're working with, but if you're only able to query, this is an option to try as well (this assumes SQL Server 2008 and above):
declare #todayepoch bigint, #yesterdayepoch bigint;
select #todayepoch =
cast((cast(dateadd(hour, 12,
cast(cast(sysutcdatetime() as date) as datetime)) as decimal(24,10))
- cast(cast('1970-01-01' as datetime) as decimal(24,10)))
*60.0*60.0*24.0 as int), -- + 18000, --Eastern time
#yesterdayepoch =
cast((cast(dateadd(hour, -12,
cast(cast(sysutcdatetime() as date) as datetime)) as decimal(24,10))
- cast(cast('1970-01-01' as datetime) as decimal(24,10)))
*60.0*60.0*24.0 as int) -- + 18000 --Eastern time
select #todayepoch, #yesterdayepoch
select
*
from
tablename
where
epochdate between #yesterdayepoch and #todayepoch
I used UTC above as a presumption of comparing based on UTC times, but you could also compare to your time zone, with the appropriate addition/subtraction of your time zone difference in seconds (e.g., add 18000 to each variable to get noon in Eastern Standard Time).
You can test your results by using http://www.epochconverter.com/ to compare your values in your variables.
You query would look like the following:
DECLARE #dt_from DATETIME;
DECLARE #dt_to DATETIME;
SELECT
#dt_from=DATEADD(HH,-12,CAST(FLOOR(CAST(GETUTCDATE() AS FLOAT)) AS DATETIME)), -- strip time of current UTC date/time, and subtract 12 hrs
#dt_to=DATEADD(HH,+12,CAST(FLOOR(CAST(GETUTCDATE() AS FLOAT)) AS DATETIME)); -- strip time of current UTC date/time, and add 12 hrs
SELECT
*
FROM
tablename
WHERE
epochdate BETWEEN DATEDIFF(s,'1970-01-01',#dt_from) AND DATEDIFF(s,'1970-01-01',#dt_to);

Getting today's midnight time as UTC

I have the following query which calculates today's midnight value (UTC) as a datetime:
SELECT CONVERT(DATE,GETDATE())+(GETDATE()-GETUTCDATE())
Result: 2011-11-03 19:00:00.000 (for GMT-5 on Nov. 4, 2011)
Not only that, but on occasion, it returns values like these:
2011-11-03 19:00:00.003
2011-11-03 19:00:00.007
2011-11-03 19:00:00.010
..., which are wrong!
There must be a better way to do this.
I already answered this with a solution using DATEADD and DATEDIFF with GETDATE() and GETUTCDATE(), similar to the example given in the original question, but since then I've discovered the datetimeoffset data type added in SQL Server 2008. This stores a datetime along with a timezone offset.
How you use this type will depend on whether you want to change the data type of your existing data. If you don't want to change anything, the following statement will return a datetime type with the local time of midnight:
SELECT CONVERT(datetime, SWITCHOFFSET(CONVERT(datetimeoffset,
CONVERT(date, GETDATE())),
DATENAME(TzOffset, SYSDATETIMEOFFSET())))
You could also convert any UTC time into local time using:
SELECT CONVERT(datetime, SWITCHOFFSET(CONVERT(datetimeoffset,
#myutctime,
DATENAME(TzOffset, SYSDATETIMEOFFSET())))
The datetimeoffset type is only available using SQL2008 and above. If you need to do this with 2005 and below, you can use a solution similar to the one in the original question, but altered to account for the fact that GETDATE() - GETUTCDATE() is not an atomic operation and will likely involve milliseconds of difference between when the two are executed.
SELECT DATEADD(minute,
DATEDIFF(minute, GETUTCDATE(), GETDATE()),
CONVERT(datetime, CONVERT(date, GETDATE())))
This will take the number minutes between GETDATE() and GETUTCDATE() and add them onto the local midnight time. Unfortunately, you have to convert back from date to datetime as DATEADD won't work with minutes if you give it a date. I'd suggest wrapping this into a user-defined function to make it look less verbose, e.g.
CREATE FUNCTION dbo.MidnightASUTC(#dt as datetime)
RETURNS datetime
AS
BEGIN
RETURN DATEADD(minute,
DATEDIFF(minute, GETUTCDATE(), GETDATE()),
CONVERT(datetime, CONVERT(date, #dt)))
END
SELECT dbo.MidnightAsUTC(GETDATE())
For a specific scenario like the one you've described ("today's midnight value (UTC) as a datetime"), a programmatic approach makes sense, but if you ever need to extend it to a different question (what was midnight UTC for this summer?), you may want to use a calendar table (to account for things like daylight savings time, etc).

SQL Date Subtraction Between Two Years

I am having an issue with a query I am trying to convert from MS Access. The query flags record for removal when it is older than 90 days but when I convert this query to sql server is is removing too many records.
UPDATE DT.SM_T_CountTotals
SET IsActive = 0
WHERE Convert(varchar, DT.SM_T_CountTotals.PostDate, 101) <
Convert(varchar, GetDate()- 90, 101)
When I run this query in MS Access I get a total of 3793 records that are flagged but in SQL server I get 69061 records that are flagged for removal. The GetDate()-90 value is correct at 10/26/2010 but it is flagging everything from this year to be removed.
I am sure it is something easy that I am overlooking. Help please?
I figured it out:
UPDATE DT.SM_T_CountTotals
SET IsActive = 0
WHERE DT.SM_T_CountTotals.PostDate < Convert(varchar, GetDate()- 90, 101)
You're comparing VARCHAR values, not DATEs.
101 converts to MM/DD/YY, so you're comparing month, then day, then year.
You should be using 112 (yymmdd)
Calculations between two dates can be easily done in the native data type rather than convert it to string. One can (and you have) get incorrect answers from such conversions.
Use DateDiff in the where clause to get the records that are more than 90 days old.
http://msdn.microsoft.com/en-us/library/ms189794.aspx
UPDATE DT.SM_T_CountTotals
SET IsActive = 0
WHERE ABS (DATEDIFF (dd, Getdate(), DT.SM_T_CountTotals.PostDate)) > 90

Average a time value in SQL Sever 2005

I've got a varchar field in SQL Sever 2005 that's storing a time value in the format "hh:mm"ss.mmmm".
What I really want to do is take the average using the built in aggregate function of those time values. However, this:
SELECT AVG(TimeField) FROM TableWithTimeValues
doesn't work, since (of course) SQL won't average varchars. However, this
SELECT AVG(CAST(TimeField as datetime)) FROM TableWithTimeValues
also doesn't work. As near as I can tell, SQL doesn't know how to convert a value with only time and no date into a datetime field. I've tried a wide variety of things to get SQL to turn that field into a datetime, but so far, no luck.
Can anyone suggest a better way?
SQL Server can convert a time-only portion of a datetime value from string to datetime, however in your example, you have a precision of 4 decimal places. SQL Server 2005 only recognizes 3 places. Therefore, you will need to truncate the right-most character:
create table #TableWithTimeValues
(
TimeField varchar(13) not null
)
insert into #TableWithTimeValues
select '04:00:00.0000'
union all
select '05:00:00.0000'
union all
select '06:00:00.0000'
SELECT CAST(TimeField as datetime) FROM #TableWithTimeValues
--Msg 241, Level 16, State 1, Line 1
--Conversion failed when converting datetime from character string.
SELECT CAST(LEFT(TimeField, 12) as datetime) FROM #TableWithTimeValues
--Success!
This will convert valid values into a DATETIME starting on 1900-01-01. SQL Server calculates dates based on 1 day = 1 (integer). Portions of days are then portions of the value 1 (i.e. noon is 0.5). Because a date was not specified in the conversion, SQL Server assigned the value of 0 days (1900-01-01), which accommodates our need to average the time portion.
To perform an AVG operation on a DATETIME, you must first convert the DATETIME to a decimal value, perform the aggregation, then cast back. For example
SELECT CAST(AVG(CAST(CAST(LEFT(TimeField, 12) as datetime) AS FLOAT)) AS DATETIME) FROM #TableWithTimeValues
--1900-01-01 05:00:00.000
If you need to store this with an extra decimal place, you can convert the DATETIME to a VARCHAR with time portion only and pad the string back to 13 characters:
SELECT CONVERT(VARCHAR, CAST(AVG(CAST(CAST(LEFT(TimeField, 12) as datetime) AS FLOAT)) AS DATETIME), 114) + '0' FROM #TableWithTimeValues
Try this
AVG(CAST(CAST('1900-01-01 ' + TimeField AS DateTime) AS Float))
You really should store those in a datetime column anyway. Just use a consistent date for that part (1/1/1900 is very common). Then you can just call AVG() and not worry about it.
I used Cadaeic's response to get an answer I was looking for, so I thought I should share the code....
I was looking for a query that would average ALL my times together and give me an overall Turn Around Time for all approvals. Below is a nested statement that gives you the AVG TAT for individual id's and and when nested an overall TAT
SELECT
-- calculates overall TAT for ALL Approvals for specified period of time
-- depending on parameters of query
CONVERT(VARCHAR, CAST(AVG(CAST(CAST(LEFT(Tat_mins, 12) as datetime) AS FLOAT)) AS DATETIME), 108) + '0'
from
(
-- tat is for individual approvals
SELECT
dbo.credit_application.decision_status,
dbo.credit_application.application_id,
cast(dbo.credit_application.data_entry_complete as date) as'Data Entry Date',
cast(dbo.credit_application.decision_date as DATE) as 'Decision Date',
avg(datediff(minute, dbo.credit_application.data_entry_complete, dbo.credit_application.decision_date)) as 'TAT Minutes',
convert (char(5), DateAdd(minute, Datediff(minute,dbo.credit_application.data_entry_complete, dbo.credit_application.decision_date),'00:00:00'),108) as 'TAT_Mins'
FROM dbo.credit_application
where Decision_status not in ('P','N')
group by dbo.credit_application.decision_status,
dbo.credit_application.data_entry_complete,
dbo.credit_application.decision_date
--dbo.credit_application.application_id
)bb
How do you think to average on datetime?
I guess that you need to GROUP BY some period (Hour?), and display Count(*)?
SQL Server stores datetime data as 2 4-byte integers, hence a datetime take 8 bytes. The first is days since the base date and the second is milliseconds since midnight.
You can convert a datetime value to an integer and perform mathematical operations, but the convert only returns the "days" portion of the datetime value e.g. select convert(int,getdate()). It is more difficult to return the "time" portion as an integer.
Is using SQL Server 2008 an option for you? That version has a new dedicated time data type.
Thanks, Andy.
I'd work out the difference between all of the dates and an arbitrary point (01/01/1900), average it and then add it back on to the arbitrary point.

Resources