Optimal way to convert to date - sql

I have legacy system where all date fields are maintained in YMD format. Example:
20101123
this is date: 11/23/2010
I'm looking for most optimal way to convert from number to date field.
Here is what I came up with:
declare #ymd int
set #ymd = 20101122
select #ymd, convert(datetime, cast(#ymd as varchar(100)), 112)
This is pretty good solution but I'm wandering if someone has better way doing it

try this:
CONVERT(DATETIME, CONVERT(NVARCHAR, YYYYMMDD))
For example:
SELECT CONVERT(DATETIME, CONVERT(NVARCHAR, 20100401))
Results in:
2010-04-01 00:00:00.000

What you have is a pretty good soltuion.
Why are you looking for a better way?

I use exactly that, it has been working fine for me

As it is stored as an integer then you could potential extract the year, month and day by dividing by 100, 1000.
e.g.
DECLARE #Date INT
SET #Date = 20100401
DECLARE #Year INT
DECLARE #Month INT
DECLARE #Day INT
SET #Year = #Date / 10000
SET #Month = (#Date - (#Year * 10000)) / 100
SET #Day = #Date - (#Year * 10000) - (#Month * 100)
SELECT #Date, DATEADD(MONTH,((#Year-1900)*12)+#Month-1,#Day-1)
However, I have no idea if that is faster than the string comparison you already have. I think your solution is far cleaner and easier to read and would stick with that.

Related

SQL Convert data time format (yyyy-mm-dd 00:00:00.000) to yyyymmdd as int

It must be very simple, but I don't know SQL language very well.
I need to filter data by date which is in this format:
How to do it right to filter data this way?
FROM [TableName] where
FileDate>=20220505
I've already tried the command LEFT and CAST but with no success
Something like this may work:
declare #now Datetime = getdate();
declare #intNow int = cast(cast(datepart(year, #now) as varchar(4)) + RIGHT('00'+CAST(datepart(month, #now) AS VARCHAR(2)),2) + RIGHT('00'+CAST(datepart(day, #now) AS VARCHAR(2)),2) as int)
Although if you have your date to check against in the right format e.g. using:
declare #dateToCheck Datetime = cast(cast(20220505 as varchar) as datetime)
And then
FileDate>= #dateToCheck
it should work
You can create an integer representation of your datetime by multiplying and adding the date parts:
year * 10000 20220000
month * 100 500
day 5
-------------------------
20220505
...
FROM [TableName]
WHERE (DATEPART(year, [FileDate]) * 10000) + (DATEPART(month, [FileDate]) * 100) + (DATEPART(day, [FileDate])) >= 20220505
However I'd still look into fixing the condition input format instead.
Credit to #Rangani in Yesterday's date in SSIS package setting in variable through expression for "multiply and add instead of string concat" trick

How to format a wildcard query for dates in MS SQL?

I have a certain day range, let's say 8-01 to 8-08. I also have a database that has historical data from 1975-2014. Dates are formatted in yyyy-mm-dd form. So for example, today is 2014-08-06. How can I get historical records from 8-01 to 8-08 for all years? Keep in mind my date range might not span nicely across the same month. Another example would be 7/31 to 8/07.
Basically, I want to be able to do BETWEEN(%-8-01 AND %-8-08) where % is a wildcard. However, wildcards seem incompatible with date objects in MS SQL. Do I need to convert the dates to strings? What is the most efficient way of getting a generic month-day range independent of year?
Thanks.
This is a different approach than my other answer. It will make all dates have a year of 1900. Then you only need to take whatever is between your selected dates.
SELECT *
FROM Table1
WHERE DATEADD
(
year,
-DATEDIFF(year,'19000101',dateField),
dateField
) BETWEEN'19000801' AND'19000818'
You can extract the month and date part and compare those:
where
month(<yourdate>) = 8
and day(<yourdate>) between 1 and 8
Be aware that if you have an index on this column, you won't be using it this way.
The wildcard operator cannot be used in a BETWEEN statement. It's really only for LIKE statements. If you want to be able to modify your year, and that's it, you should pass it in as a parameter. The BETWEEN statement will take that with no problem.
DECLARE #StartMonth INT
DECLARE #Month INT
DECLARE #EndMonth INT
DECLARE #StartDay INT
DECLARE #Day INT
DECLARE #EndDay INT
SET #StartMonth = 8
SET #EndMonth = 8
SET #StartDay = 1
SET #EndDay = 8
IF #EndMonth > #StartMonth
BEGIN
#Month = #EndMonth
#StartMonth = #EndMonth
#EndMonth= #Month
END
IF #EndDay > #StartDay
BEGIN
#Day= #EndDay
#StartDay = #EndDay
#EndDay = #Day
END
SELECT *
FROM TABLE
WHERE MONTH(dateField) BETWEEN #StartMonthAND #EndMonth
AND DAY(dateField) BETWEEN #StartDay AND #EndDay

Direct access slower than using functions?

I was conducting some performance testing and have discovered something quite strange. I have set up a short script to time how long it takes to perform certain actions.
declare #date date
declare #someint int
declare #start datetime
declare #ended datetime
set #date = GETDATE()
DECLARE #count INT
SET #count = 0
set #start = GETDATE()
WHILE (#count < 1000)
BEGIN
--Insert test script here
END
set #ended = GETDATE()
select DATEDIFF( MILLISECOND, #start, #ended)
The table I was running tests againsts contains 3 columns, MDay, and CalDate. Every calendar date has a corresponding M(Manufacturing)Day. The table may look something like this:
MDay | CalDate
1 | 1970-01-01
2 | 1970-01-02
I wanted to test how efficient one of our functions was. This function simply takes in a date and returns the int MDay value. I used direct access, basically the same thing without the function, and tests resulted in this method take twice as long! Code I inserted into the loop is provided below. I used a random date in an attempt to eliminate caching (if exist).
Function
select #someint = Reference.GetMDay(DATEADD( D, convert(int, RAND() * 1000) , #date))
Definition for above
create Function [Reference].[GetMDay]
(#pCaLDate smalldatetime
)
Returns int
as
Begin
Declare #Mday int
Select #Mday = Mday
from Reference.MDay
where Caldate = #pCaLDate
Direct
select #someint = MDay from Reference.MDay where CalDate = DATEADD( D, convert(int, RAND() * 1000) , #date)
I even tried using a static #date for my direct code and the difference in times are negligible, so I know the convert call isn't holding it back.
What the heck is going on here?
Take a look at http://msdn.microsoft.com/en-us/library/ms178071%28v=sql.105%29.aspx is the execution plan the same on your sql server for both methods?

Converting JDE Julian date to Gregorian

I'm trying to convert JDE dates, and have amassed a large quantity of information and figured I'd try to do an SQL conversion function to simplify some tasks.
Here's the function I came up with, which I simply call "ToGregorian"
CREATE FUNCTION [dbo].[ToGregorian](#julian varchar(6))
RETURNS datetime AS BEGIN
DECLARE #datetime datetime
SET #datetime = CAST(19+CAST(SUBSTRING(#julian, 1, 1) as int) as varchar(4))+SUBSTRING(#julian, 2,2)+'-01-01'
SET #datetime = DATEADD(day, CAST(SUBSTRING(#julian, 4,3) as int)-1, #datetime)
RETURN #datetime
END
Takes a "julian" string.
Takes the first letter and adds it to century, starting from 19th.
Adds decade and years from the next 2 characters.
Finally adds the days, which are the final 3 characters, and subtracts 1 as it already had 1 day in the first setup. (eg. 2011-01-01)
Result ex: 111186 => 2011-07-05 00:00:00.000
In my opinion this is a bit clumsy and overkill, and I'm hoping there is a better way of doing this. Perhaps I'm doing too many conversions or maybe I should use a different method alltogether?
Any advice how to improve the function?
Perhaps a different, better, method?
Wouldn't mind if it could be more readable as well...
I've also got an inline version, where if for instance, I only have read privileges and can't use functions, which also looks messy, is it possible to make it more readable, or better?
CAST(REPLACE(Convert(VARCHAR, DATEADD(d,CAST(SUBSTRING(CAST([column] AS VARCHAR), 4,3) AS INT)-1, CAST(CAST(19+CAST(SUBSTRING(CAST([column] AS VARCHAR), 1,1) AS INT) AS VARCHAR)+SUBSTRING(CAST([column] AS VARCHAR), 2,2) + '-01-01' AS DATETIME)), 111), '/', '-') AS DATETIME)
The accepted answer is incorrect. It will fail to give the correct answer for 116060 which should be 29th February 2016. Instead it returns 1st March 2016.
JDE seems to store dates as integers, so rather than converting from strings I always go direct from the integer:
DATEADD(DAY, #Julian % 1000, DATEADD(YEAR, #Julian / 1000, '31-dec-1899'))
To go from a varchar(6) I use:
DATEADD(DAY, CAST(RIGHT(#Julian,3) AS int), DATEADD(YEAR, CAST(LEFT(#Julian,LEN(#Julian)-3) AS int), '31-dec-1899'))
I think it is more efficient to use native datetime math than all this switching back and forth to various string, date, and numeric formats.
DECLARE #julian VARCHAR(6) = '111186';
SELECT DATEADD(DAY, SUBSTRING(#julian,4,3)-1,
DATEADD(YEAR, 100 * LEFT(#julian,1)
+ 10 * SUBSTRING(#julian,2,1)
+ SUBSTRING(#julian,3,1),0));
Result:
===================
2011-07-05 00:00:00
Assuming this data doesn't change often, it may be much more efficient to actually store the date as a computed column (which is why I chose the base date of 0 instead of some string representation, which would cause determinism issues preventing the column from being persisted and potentially indexed). Even if you don't index the column, it still hides the ugly calculation away from you, being persisted you only pay that at write time, as it doesn't cause you to perform expensive functional operations at query time whenever that column is referenced...
Corrected for leap year
USE [master]
GO
/****** Object: UserDefinedFunction [dbo].[ToGregorian] Script Date: 08/18/2015 14:33:17 ******/
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
ALTER FUNCTION [dbo].[ToGregorian](#julian varchar(6),#time varchar(6))
RETURNS datetime
AS
BEGIN
DECLARE #datetime datetime,#hour int, #minute int, #second int
set #time = ltrim(rtrim(#time));
set #julian = ltrim(rtrim(#julian));
if(LEN(#julian) = 5)
set #julian = '0' + #julian
IF(LEN(#time) = 6)
BEGIN
SET #hour = Convert(int,LEFT(#time,2));
SET #minute = CONVERT(int,Substring(#time,3,2));
SET #second = CONVERT(int,Substring(#time,5,2));
END
else IF(LEN(#time) = 5)
BEGIN
SET #hour = Convert(int,LEFT(#time,1));
SET #minute = CONVERT(int,Substring(#time,2,2));
SET #second = CONVERT(int,Substring(#time,4,2));
END
else IF(LEN(#time) = 4)
BEGIN
SET #hour = 0;
SET #minute = CONVERT(int,LEFT(#time,2));
SET #second = CONVERT(int,Substring(#time,3,2));
END
else IF(LEN(#time) = 3)
BEGIN
SET #hour = 0;
SET #minute = CONVERT(int,LEFT(#time,1));
SET #second = CONVERT(int,Substring(#time,2,2));
END
else
BEGIN
SET #hour = 0;
SET #minute = 0;
SET #second = #time;
END
SET #datetime = DATEADD(YEAR,100*CONVERT(INT, LEFT(#julian,1))+10*CONVERT(INT, SUBSTRING(#julian, 2,1))+CONVERT(INT, SUBSTRING(#julian,3,1)),0);
SET #datetime = DATEADD(DAY, CONVERT(INT,SUBSTRING(#julian, 4, 3))-1,#datetime);
SET #datetime = DATEADD(hour,#hour,#datetime)
SET #datetime = DATEADD(minute,#minute,#datetime);
SET #datetime = DATEADD(second,#second,#datetime);
RETURN #datetime
END
DATE(CHAR(1900000 + GLDGJ)) where GLDGJ is the Julian date value
I don't think anyone has mentioned it, but JDE has a table just for this.
It's the F00365 data table. As far as I know, it's a translation table just for this issue.
To get a Gregorian date, you join the F00365 table using the ONDTEJ field (which is the Julian date),and you return the ONDATE value, which is Gregorian.
e.g.
SELECT
DateReq.ONDATE
FROM F00101 NamesData
INNER JOIN F00365 DateReq
ON DateReq.ONDTEJ = NamesData.ABUPMJ
No math required. No weird issues with leap years.
To convert Julian date to Gregorian:
DATEADD(DAY, #julian % 1000 - 1, DATEADD(YEAR, #julian / 1000, 0))
To convert Gregorian to Julian date:
(YEAR(#date) - 1900) * 1000 + DATEPART(DAYOFYEAR, #date)
Try it:
DECLARE #julian VARCHAR(6);
SET #julian = N'122129';
SELECT #julian [JulianDate],
DATEADD(YEAR, #julian / 1000, 0) [Year],
#julian % 1000 [DayOfYear],
DATEADD(DAY, #julian % 1000 - 1, DATEADD(YEAR, #julian / 1000, 0)) [Date];
DECLARE #george DATETIME;
SET #george = '2022-5-9';
SELECT #george [Date],
YEAR(#george) [Year],
DATEPART(DAYOFYEAR, #george) [DayOfYear],
(YEAR(#george) - 1900) * 1000 + DATEPART(DAYOFYEAR, #george) [JulianDate];
Here a formula that can be used I have looking all over different site and I had even though I had to twick a bit has work well for me and you do not need to create any special function stuff:
DATEADD(DAY,CONVERT(INT,SUBSTRING(CONVERT(CHAR,(JULIANDDATEFIELD + 1900000)),5,3))-1,DATEFROMPARTS(SUBSTRING(CONVERT(CHAR,(JULIANDDATEFIELD + 1900000)),1,4),01,01))

Transforming nvarchar day duration setting into datetime

I have a SQL Server function which converts a nvarchar day duration setting into a datetime value.
The day duration format is >days<.>hours<:>minutes<, for instance 1.2:00 for one day and two hours.
The format of the day duration setting can not be changed, and we can be sure that all data is correctly formatted and present.
Giving the function a start time and the day duration setting it should return the end time.
For instance: 2010-01-02 13:30 ==> 2010-01-03 2:00
I'm using a combination of charindex, substring and convert methods to calculate the value,
which is kind of slow and akward. Is there any other way to directly convert this day duration setting into a datetime value?
Not from what I can see. I would end up with a similar bit of SQL like you, using charindex etc. Unfortunately it's down to the format the day duration is stored in. I know you can't change it, but if it was in a different format then it would be a lot easier - the way I'd usually do this for example, is to rationalise the duration down to a base unit like minutes.
Instead of storing 1.2:00 for 1 day and 2 hours, it would be (1 * 24 * 60) + (2 * 60) = 1560. This could then be used in a straightforward DATEADD on the original date (date part only).
With the format you have, all approaches I can think of involve using CHARINDEX etc.
One alternative would be to build a string with the calculation. Then you can run the generated SQL with sp_executesql, specifying #enddate as an output parameter:
declare #startdate datetime
declare #duration varchar(10)
declare #enddate datetime
set #startdate = '2010-01-02 13:30'
set #duration = '0.12:30'
declare #sql nvarchar(max)
set #sql = 'set #enddate = dateadd(mi,24*60*' +
replace(replace(#duration,'.','+60*'),':','+') + ', #startdate)'
exec sp_executesql #sql,
N'#startdate datetime, #enddate datetime out',
#startdate, #enddate out
This creates a string containing set #enddate = dateadd(mi,24*60*0+60*12+30, #startdate) and then runs it.
I doubt this is faster than the regular charindex way:
declare #pos_dot int
declare #day int
declare #hour int
declare #minute int
select
#pos_dot = charindex('.',#duration),
#day = cast(left(#duration, #pos_dot-1) as int),
#hour = cast(left(right(#duration, 5), 2) as int),
#minute = cast(right(#duration, 2) as int),
#enddate = dateadd(mi, 24*60*#day + 60*#hour + #minute, #startdate)