Most Performant Way to Convert DateTime to Int Format - tsql

I need to convert Datetime fields to a specifically formatted INT type. For example, I want
2000-01-01 00:00:00.000 to convert to 20010101.
What is the most performant way to make that conversion for comparison in a query?
Something like:
DATEPART(year, orderdate) * 10000 + DATEPART(month, orderdate) * 100 +
DATEPART(day, orderdate)
or
cast(convert(char(8), orderdate, 112) as int)
What's the most performant way to do this?

Your example of cast(convert(char(8), orderdate, 112) as int) seems fine to me. It quickly gets the date down to the format you need and converted to an int.
From an execution plan standpoint, there seems to be no difference between the two.

You can try with TSQL builtin functions.
It's not .NET tick compatible but it's still FAST sortable and you can pick your GRANULARITY on demand:
SELECT setup.DateToINT(GETDATE(), 4) -- will output 2019 for 2019-06-06 12:00.456
SELECT setup.DateToINT(GETDATE(), 6) -- will output 201906 for 2019-06-06 12:00.456
SELECT setup.DateToINT(GETDATE(), 20) -- will output 20190606120045660 for 2019-05-05 12:00.456
CREATE FUNCTION setup.DateToINT(#datetime DATETIME, #length int)
RETURNS
BIGINT WITH SCHEMABINDING AS
BEGIN
RETURN CONVERT(BIGINT,
SUBSTRING(
REPLACE(REPLACE(
REPLACE(REPLACE(
CONVERT(CHAR(25), GETDATE(), 121)
,'-','')
,':','')
,' ','')
,'.','')
,0
,#length+1)
)
END
GO

Is this what you need
SELECT REPLACE(CONVERT(VARCHAR(10),'2010-01-01 00:00:00.000',101),'-','')

When you pass '2010-01-01 00:00:00.000' directly in your code, the SELECT statement looks at it as a string and not a datetime data type. Its not the same as selecting a datetime field directly.
There is no need to do outer CAST because SQL Server will do implicit conversion, here is a proof.
DECLARE #t DATETIME = '2010-01-10 00:00:00.000',#u INT
SELECT #u = CONVERT(CHAR(8), #t, 112)
IF ISNUMERIC(#u) = 1
PRINT 'Integer'

Related

Conversion failed when converting date and/or time from character string. T-Sql

I have a varchar column in my database called ref003 which stores datetime like the below
2021-04-04 20:01:03
Here, date format is like yyyy-MM-DD
When I execute the below select query I am getting error
SELECT *
FROM FileIndex
WHERE CAST(CONVERT(CHAR(30), CONVERT(DATETIME, Ref003, 105), 101) AS DATE)
BETWEEN CAST(CONVERT(CHAR(10), CONVERT(DATETIME, '01-01-2021', 105), 101) AS DATE)
AND CAST(CONVERT(CHAR(10), CONVERT(DATETIME, '31-12-2021', 105), 101) AS DATE)
And the error is
Msg 241, Level 16, State 1, Line 5 Conversion failed when converting
date and/or time from character string.
What is the problem here and how can I solve this problem?
First, for the conversion you need to convert to VARCHAR, not DATE. Note this expression:
CONVERT(VARCHAR(10), CAST(<your date value> AS DATE), 20)
With that in mind, you can clean your query up like so:
--==== Easily consumable sample data
DECLARE #thetable TABLE (someid INT, thedate DATETIME);
INSERT #thetable
VALUES(1,'2021-04-04 20:01:03'),(2,'2021-06-04 22:01:05'),(1,'2021-04-29 10:31:11');
--==== Solution
SELECT t.*, FormattedDate = fmt.Dt
FROM #thetable AS t
CROSS APPLY (VALUES(CONVERT(VARCHAR(10), CAST(t.thedate AS DATE), 20))) AS fmt(Dt)
WHERE t.thedate BETWEEN '20210401' AND '20210501';
Returns:
someid thedate FormattedDate
----------- ----------------------- -------------
1 2021-04-04 20:01:03.000 2021-04-04
1 2021-04-29 10:31:11.000 2021-04-29

Concatenate Date and Time

I m trying to concatenate Date and Time using the below line but i m getting an error. Any help?
Time column type: Time (0)
CONVERT(date, getdate()) + ' ' + CONVERT(time(0), [Time]) AS Date_Time
The data types date and varchar are incompatible in the add operator.
There are a number of ways to do that - one of them is to convert both parts to DateTime which supports the add (+) operator:
SELECT CAST(CAST(GetDate() As Date) As DateTime) + (CAST([Time] As DateTime) As Date_Time
The casting of GetDate() to Date and back to DateTime resets the time portion to midnight.
I use the below and it is working:
CAST(CONVERT(date, getdate()) AS nvarchar) + ' ' + CAST(CONVERT(time(0), [Time]) AS nvarchar) AS Date_Time
Thanks to the rules for data type precedence you are trying to convert ' ' to a data type compatible with date and time, hence the error.
The next problem is that a date does not have a time component, hence to combine the two you need to use a datetime or similar data type.
declare #Time as Time = '12:30:00';
select #Time as Time,
-- Get today's date as a date .
Cast( GetDate() as Date ) as Today,
-- Get today's date and convert it to a datetime so that the time can be added.
Cast( Cast( GetDate() as Date ) as DateTime ) + Cast( #Time as DateTime ) as DateAndTime;
Note that time values need to be converted to datetime (or another compatible type) before adding the values. Curiously, SQL Server 2008 didn't require that step.

Truncate Datetime to Second (Remove Milliseconds) in T-SQL

What is the best way to shorten a datetime that includes milliseconds to only have the second?
For example 2012-01-25 17:24:05.784 to 2012-01-25 17:24:05
This will truncate the milliseconds.
declare #X datetime
set #X = '2012-01-25 17:24:05.784'
select convert(datetime, convert(char(19), #X, 126))
or
select dateadd(millisecond, -datepart(millisecond, #X), #X)
CAST and CONVERT
DATEADD
DATEPART
The fastest, also language safe and deterministic
DATEADD(second, DATEDIFF(second, '20000101', getdate()), '20000101')
The easiest way now is:
select convert(datetime2(0) , getdate())
convert(datetime, convert(varchar, #datetime_var, 120), 120)
The following has very fast performance, but it not only removes millisecond but also rounds to minute. See (http://msdn.microsoft.com/en-us/library/bb677243.aspx)
select cast(yourdate as smalldatetime) from yourtable
Edit:
The following script is made to compare the scripts from Mikael and gbn I upvoted them both since both answers are great. The test will show that gbn' script is slightly faster than Mikaels:
declare #a datetime
declare #x int = 1
declare #mikaelend datetime
declare #mikael datetime = getdate()
while #x < 5000000
begin
select #a = dateadd(millisecond, -datepart(millisecond, getdate()), getdate()) , #x +=1
end
set #mikaelend = getdate()
set #x = 1
declare #gbnend datetime
declare #gbn datetime = getdate()
while #x < 5000000
begin
select #a = DATEADD(second, DATEDIFF(second, '20000101', getdate()), '20000101') , #x +=1
end
set #gbnend = getdate()
select datediff(ms, #mikael, #mikaelend) mikael, datediff(ms, #gbn, #gbnend) gbn
First run
mikael gbn
----------- -----------
5320 4686
Second run
mikael gbn
----------- -----------
5286 4883
Third run
mikael gbn
----------- -----------
5346 4620
declare #dt datetime2
set #dt = '2019-09-04 17:24:05.784'
select convert(datetime2(0), #dt)
Expanding on accepted answer by #Mikael Eriksson:
To truncate a datetime2(7) to 3 places (aka milliseconds):
-- Strip of fractional part then add desired part back in
select dateadd(nanosecond,
-datepart(nanosecond, TimeUtc) + datepart(millisecond, TimeUtc) * 1e6,
TimeUtc) as TimeUtc
The current max precision of datetime2(p) is (7) (from learn.microsoft.com)
--- DOES NOT Truncate milliseconds
--- 2018-07-19 12:00:00.000
SELECT CONVERT(DATETIME, '2018-07-19 11:59:59.999')
--- Truncate milliseconds
--- 2018-07-19 11:59:59.000
SELECT CONVERT(DATETIME, CONVERT(CHAR(19), '2018-07-19 11:59:59.999', 126))
--- Current Date Time with milliseconds truncated
SELECT CONVERT(DATETIME, CONVERT(CHAR(19), GETDATE(), 126))
SELECT CAST( LEFT( '2018-07-19 11:59:59.999' , 19 ) AS DATETIME2(0) )

T-SQL duration in hours:minutes:seconds

I have average duration between several dates (DATETIME format) ie. 1900-01-01 01:30.00.00.
How can I convert DATETIME to format hours:minutes:seconds where hours can be more that 24 - output format can be VARCHAR.
IE.
1 days 12 hours 5 minutes convert to 36:05:00
2 days 1 hour 10 minutes 5 seconds convert to 49:10:05
etc...
DECLARE #date1 DATETIME = '2011-08-03 13:30'
DECLARE #date2 DATETIME = '2011-08-03 13:00'
DECLARE #date3 DATETIME = '2011-08-03 14:00'
DECLARE #abc DATETIME = '2011-08-03 12:00'
select CAST(AVG(CAST(data-#abc as float)) as datetime)
from
(
select 'data' as label, #date1 as data
union all
select 'data' as label, #date2 as data
union all
select 'data' as label, #date3 as data
) as a
group by label
I would like to get result as 01:30:00 which means that average time is 1 hours and 30 minutes.
I tried it:
CONVERT(VARCHAR(10), CAST(AVG(CAST(data-#abc as float)) as datetime), 108)
but then I get only time portion in HH:MM:SS. When I set #abc = 2011-08-02 then the results will be the same - this is incorrect.
King regards,
Marcin
In T-SQL a datetime is precisely that, a date and a time where the hours can never exceed 24 because that moves it to the next day. You could use datepart to piece the datetime values out and treat them as integers and then rejoin them into the string you want. Depending on your final goal, you may be better of doing this type of work in your application or presentation layers where more general purpose languages often have more robust datetime libraries to work with.
I think you need to write a scalar-valued function that takes an integer argument (time difference in seconds) and format it as needed. For example,
CREATE FUNCTION intToDateTime ( #time_in_secs BIGINT) RETURNS VARCHAR(30)
AS
BEGIN
DECLARE #retval VARCHAR(30);
SET #retval = cast(#time_in_secs/(60*60) as varchar(10))+':'+
cast( (#time_in_secs-#time_in_secs/(60*60)*3600)/60 as varchar(10))+':'+
cast( (#time_in_secs-#time_in_secs/(60)*60) as varchar(10));
return #retval;
END
This function needs some changes - you may want to display leading zero for 0-9(i.g. '00' instead of '0' as this function currently does); also you need to handle negative values in a better way.
Now you can use it with DATEDIFF(second, #val1,#val2).
Hope I pointed you to the right direction.
select cast(cast(cast(t as float) *24 as int) as varchar) + right(convert(varchar,t, 20), 6)
from(
select cast(AVG(CAST(data-#abc as float)) as datetime) t
from
(
select 'data' as label, #date1 as data
union all
select 'data' as label, #date2 as data
union all
select 'data' as label, #date3 as data
) as a
group by label
) a
Result:
1:30:00
You can't convert datetime to handle non-real dates and times.
However, you can get an output that looks like a datetime, simply by concatenating an hours string with ':' with minutes, etc.
Lookup the DATEADD() and DATEDIFF() functions...

Create date efficiently

On Pavel's page is the following function:
CREATE OR REPLACE FUNCTION makedate(year int, dayofyear int)
RETURNS date AS $$
SELECT (date '0001-01-01' + ($1 - 1) * interval '1 year' + ($2 - 1) * interval '1 day'):: date
$$ LANGUAGE sql;
I have the following code:
makedate(y.year,1)
What is the fastest way in PostgreSQL to create a date for January 1st of a given year?
Pavel's function would lead me to believe it is:
date '0001-01-01' + y.year * interval '1 year' + interval '1 day';
My thought would be more like:
to_date( y.year||'-1-1', 'YYYY-MM-DD');
Am looking for the fastest way using PostgreSQL 8.4. (The query that uses the date function can select between 100,000 and 1 million records, so it needs speed.)
Thank you!
I would just use the following, given that year is a variable holding the year, instead of using a function:
(year || '-01-01')::date
Btw. I can't believe that this conversion is your bottleneck. But maybe you should have a look at generate_series here (I don't know your usecase).
select current_date + s.a as dates from generate_series(0,14,7) as s(a);
dates
------------
2004-02-05
2004-02-12
2004-02-19
(3 rows)
Using to_date() is even simpler than you expect:
> select to_date('2008','YYYY');
to_date
------------
2008-01-01
(1 row)
> select to_date(2008::text,'YYYY');
to_date
------------
2008-01-01
(1 row)
Note that you still have to pass the year as a string, but no concatenation is needed.
As suggested by Daniel, in the unlikely case that this conversion is a bottleneck, you might prefer to precompute the function and store in a table. Eg:
select ynum, to_date( ynum ||'-01-01', 'YYYY-MM-DD') ydate
from generate_series(2000,2009) as ynum;
If there are a few years (and hence no need of indexes), you might even create the table dinamically for the scope of each query, with the new WITH.