Problems extending `format-string` with HH24MISS when converting decimal column based timestamp to actual timestamp - sql

tl;dr
Why am I not able to convert following string timestamp
select timestamp_format('2015-08-21 000000', 'YYYY-MM-DD HH24MISS') as timestamp
from sysibm.sysdummy1;
on an i7.1.0/OS machine?
Especially since I can convert
select timestamp_format('000000' , 'HH24MISS') as timestamp
from sysibm.sysdummy1;
to:
timestamp
-------------------------
2015-08-01 00:00:00.000000
Context
On an i7.1.0/OS machine, I have a table with timestamp data split up in several decimal columns, like
declare global temporary table tstamp
(
year dec(4,0),
month dec(2,0),
day dec(2,0),
time dec(6,0)
);
with data like
insert into session.tstamp
values (2015,8,21,92601),
(2015,8,21,132601);
on which I want to do some date filtering. Given the somewhat inflexible format, I figured that it is probably better if I convert this to a timestamp and use this to query the table. So i consulted the i/OS 7.1 Manual on timestamp_format
I started of with building the date part, ending up with
select
timestamp_format(YEAR || '-' || MONTH || '-' || DAY, 'YYYY-MM-DD') as timestamp
from session.tstamp;
which returns
TIMESTAMP
--------------------------
2015-08-21 00:00:00.000000
2015-08-21 00:00:00.000000
Perfect, let us add the time part and explicit lpad it to contain six characters:
select
timestamp_format(YEAR || '-' || MONTH || '-' || DAY || ' ' || lpad(TIME, 6, '0'), 'YYYY-MM-DD HH24MISS') as timestamp
from session.tstamp;
This results in the following error:
SQL State: 22007
Vendor Code: -20448
Message: [SQ20448] Expression not valid using format string specified for TIMESTAMP_FORMAT. Cause . . . . . : Argument 1 of the TIMESTAMP_FORMAT function can not be interpreted using the format string specified in argument 2 for one of the following reasons: -- The string expression is too short or too long. -- The string expression does not conform to the template specified in the format string. -- Too many digits were specified in the string expression for the corresponding format element in the format string. -- A value in the string expression is not valid for the corresponding format element in the format string. Recovery . . . : Specify a valid format string for the function. Try the request again.
According to the manual regarding the format-string, a separator between fields is optional:
[...]
Two format elements can optionally be separated by one or more of the following separator characters:
[...]
Question
So why are not my values accepted when using 'YYYY-MM-DD HH24MISS' as the format-string, given that I explicit has bound the time length to six characters?
Side note
It is possible to use HH24MISS on its own as format-string, so I'm not really able to wrap my head around this.
select timestamp_format(lpad(TIME, 6, '0'), 'HH24MISS') as timestamp from session.tstamp;
TIMESTAMP
--------------------------
2015-08-01 13:26:01.000000
2015-08-01 09:26:01.000000

The difficulties described, are due to defects with the TIMESTAMP_FORMAT [aka TO_DATE] scalar. The requests shown are tested to have functioned, as expected, with the DB2 for IBM i 7.3 [and as a comment to the OP suggests, also on v7r2]. I had asked a similar question, "Why the failures using my earlier examples?" in SQL convert text mm/dd/yy to date and timestamp, but I have not yet re-visited those examples on the newer release. And FWiW, there may be some updated code on IBM i 7.1 for that feature with the latest code; I do not have that level of maintenance, so I can not test if the [the last of the] enhancements that are coming for that release include the code fixes that apparently exist in newer releases.
Note that the TO_DATE feature is not a true built-in [instead, is a system-supplied User Defined Function (UDF)], so personally, I would recommend an alternative; namely, writing and using a scalar UDF specific to the task, and/or choose a more compatible and easy way to generate the TIMESTAMP from those columns as they are defined. Consider the following expression [that assumes all dates are beyond year 1000, else the expression must change to use DIGITS(YEAR) vs just YEAR]:
timestamp( YEAR concat digits( MONTH ) concat digits( DAY )
concat digits( TIME )
)
A variation of that, is to use arithmetic to achieve the same effect of a 14–character timestamp-string form 'yyyymmddhhmmss':
timestamp( concat( YEAR * 10000 + MONTH * 100 + DAY
, digits ( TIME ) ) )
The following scalar function could be created to avoid coding the expression in [VIEW] queries or other places. As coded, with nothing but an expression on a RETURN statement, should allow in-lining; I did not specify any other potentially performance-related clauses such as parallel or on-null-input:
create function y4m2d2t6TS
( year dec(4, 0)
, month dec(2, 0)
, day dec(2, 0)
, time dec(6, 0)
) returns timestamp
language sql deterministic
return
digits( YEAR ) concat digits( MONTH )
concat digits( DAY ) concat digits( TIME )
; -- this semicolon is a statement separator, not terminator of above CREATE
select
y4m2d2t6TS( year, month, day, time ) as timestamp
from session.tstamp
; -- likeness of report from above query:
TIMESTAMP
2015-08-21-09.26.01.000000
2015-08-21-13.26.01.000000
******** End of data ***

You can use this in DB2 :
values(VARCHAR_FORMAT(current_date,'YYYY-MM-DD HH24:MI:SS'))
Bye

Related

Convert varchar/timestamp col to Date field

I have a varchar2 datatype field (lmp_date) that can return either null or what looks like a timestamp value. Changing the database data_type to DATE isn't a possibility, so now I'm needing to convert this to a date, but with the values this column returns, I'm having some problems.
Returned values for lmp_date =
null or 2021-06-11-00.00.00
Date format needed: MM/DD/YYYY
I've tried cast, convert, substr+instr to no avail
ETA - A couple example attempts (because there have been 10+:
select order_no, to_date(lmp_date) lmp_date from table_a - with error message of 'ORA-01861: literal does not match format string'
select order_no, to_date(substr(lmp_date, 1, instr(lmp_date, '00' -15))) lmp_date from table_a - since lmp_date has null value possibilities, this doesn't work successfully
select order_no, cast(lmp_date as date) lmp_date from table_a - with same error message of 'ORA-01861: literal does not match format string'
select order_no, to_date(lmp_date, 'YYYY-MM-DD') lmp_date from table_a - ORA-01830: date format picture ends before converting entire input string
There have been more attempts, this is all I can remember
To convert a string to a date, use the to_date() function with a suitable format mask:
to_date(lmp_date, 'YYYY-MM-DD-HH24:MI:SS')
The format model elements are in the documentation.
The result of that is a date data type, which is an internal 7-byte representation. Your client or application will format that for display, which may be based on your NLS_DATE_FORMAT setting, so you can modify that to change hot all dates are displayed; or use to_char() to convert the date back to a string, e.g.:
to_char(to_date(lmp_date, 'YYYY-MM-DD-HH24:MI:SS'), 'MM/DD/YYYY')
although if you want it as that string you can just use string manipulation with substr() and concatenation:
case when lmp_date is not null then
substr(lmp_date, 6, 2) || '/' || substr(lmp_date, 9, 2) || '/' || substr(lmp_date, 1, 4)
end
db<>fiddle
When you do either of these:
to_date(lmp_date)
cast(lmp_date as date)
this also relies on your session NLS_DATE_FORMAT; and the "literal does not match format string" error indicates that it doesn't match the string, e.g. if you have the still-default 'DD-MON-RR' setting. It would actually work - for you in your current session - if you changed that setting. I've shown that here just for info. But to work for anyone regardless of their session settings, you should use to_date() with an explicit format mask, and don't rely on or assume anything session-specific.
You were nearly there with:
to_date(lmp_date, 'YYYY-MM-DD')
and again the "date format picture ends before converting entire input string" message tells you what is wrong - your string carries on past the YYYY-MM-DD elements. Expanding the format mask to match all of the string, as I did above, means it knows what each part means.
If you were really only interested in the date part then you could cut the end off the string:
to_date(substr(lmp_date, 1, 10), 'YYYY-MM-DD')
but that's only really useful if you have a mix of string values where some have times and some do not. (The resulting date will always have a time; it will just be midnight.) And if you have dates with different formats then it gets a bit complicated - partly why you shouldn't store dates as strings.

How to use strftime() function in sqlite for extracting Year where datetime is in a different format than what would normally work

While learning SQL in my course, I have used the function YEAR(Start_Time) AS Year
Start_Time is a column header. The date time example is in this format: 26/12/2017 09:00:00
YEAR() function works in SQL.
However, when I went on SQLite (on DB Browser), the function is not working. I used the strftime('%Y', 'Start_Time'). I understand that strftime goes from left to right. Does anyone know what would be the equivalent function to YEAR() in sqlite with the date time format given above?
In SQL:
YEAR(Start_Time) AS Year
Answer:
2017
In sqlite:
YEAR(Start_Time)
Answer:
function not recognized
strftime('%Y', 'Start_time')
Answer:
function not recognized
SQlite is pretty different than other RDBMS : It doesn't have a date/time type. You can only store dates as numbers or text.
The function strftime() is able to extract date/time info from a date stored in a text column, but it only works if you respect the ISO8601 notation: YYYY-MM-DD HH:MM:SS.SSS , which is not the case with your date.
If you want to extract the Year from it using strftime() , then you need to convert your date first
For your case, if you convert only the date part and not the time part, that works too.
Lets go:
SELECT Start_Time,
(substr(Start_Time,7,4) || '-' || substr(Start_Time,4,2) || '-' || substr(Start_Time,1,2)) as dateconverted,
strftime('%Y', (substr(Start_Time,7,4) || '-' || substr(Start_Time,4,2) || '-' || substr(Start_Time,1,2)) ) as year
FROM test;
Results
Start_Time dateconverted year
26/12/2017 09:00:00 2017-12-26 2017
If you want to avoid this mess, you just have to store your dates/times in the right format from the start, there's no other workaround.
The way to do it it's your third choice. strftime('%Y', 'time string'). Make sure that time string it's a string in one of the accepted formats. You may see: (https://sqlite.org/lang_datefunc.html).

DB2 Convert Number to Date

For some reason (I have no control over this) dates are stored as Integers in an iSeries AS400 DB2 system that I need to query. E.g. today will be stored as:
20,171,221
Being in the UK I need it to be like the below in Date format:
21/12/2017
This is from my query: (OAORDT = date field)
Select
Date(SUBSTR( CHAR( OAORDT ),7,2) ||'/' || SUBSTR(CHAR ( OAORDT ),5,2) || '/' || SUBSTR(CHAR (OAORDT ),1,4)) AS "Order Date"
from some.table
However, all I get is Nulls. If I remove the Date function, then it does work but its now a string, which I don't want:
Select
SUBSTR( CHAR( OAORDT ),7,2) ||'/' || SUBSTR(CHAR ( OAORDT ),5,2) || '/' || SUBSTR(CHAR (OAORDT ),1,4) AS "Order Date"
from some.table
How do I convert the OAORDT field to Date?
Just to update - I will be querying this from MS SQL Server using an OpenQuery
Thanks.
1) How do I convert the OAORDT field to Date?
Simplest is to use TIMESTAMP_FORMAT :
SELECT DATE(TIMESTAMP_FORMAT(CHAR(OAORDT),'YYYYMMDD'))
2) Being in the UK I need it to be [...] in Date format 21/12/2017 :
SELECT VARCHAR_FORMAT(DATE(TIMESTAMP_FORMAT(CHAR(OAORDT),'YYYYMMDD')),'DD/MM/YYYY')
Note, you didn't specify where you are doing this, but since you tagged as ibm-midrange, I am answering for embedded SQL. If you want JDBC, or ODBC, or interactive SQL, the concept is similar, just the means of achieving it is different.
Make sure SQL is using dates in the correct format, it defaults to *ISO. For you it should be *EUR. In RPG, you can do it this way:
exec sql set option *datfmt = *EUR;
Make sure that set option is the first SQL statement in your program, I generally put it immediately between D and C specs.
Note that this is not an optimal solution for a program. Best practice is to set the RPG and SQL date formats both to *ISO. I like to do that explicitly. RPG date format is set by
ctl-opt DatFmt(*ISO);
SQL date format is set by
exec sql set option *datfmt = *ISO;
Now all internal dates are processed in *ISO format, and have no year range limitation (year can be 0001 - 9999). And you can display or print in any format you please. Likewise, you can receive input in any format you please.
Edit Dates are a unique beast. Not every language, nor OS knows how to handle them. If you are looking for a Date value, the only format you need to specify is the format of the string you are converting to a Date. You don't need to (can't) specify the internal format of the Date field, and the external format of a Date field can be mostly anything you want, and different each time you use it. So when you use TIMESTAMP_FORMAT() as #Stavr00 mentioned:
DATE(TIMESTAMP_FORMAT(CHAR(OAORDT),'YYYYMMDD'))
The format provided is not the format of the Date field, but the format of the data being converted to a Timestamp. Then the Date() function converts the Timestamp value into a Date value. At this point format doesn't matter because regardless of which external format you have specified by *DATFMT, the timestamp is in the internal timestamp format, and the date value is in the internal date format. The next time the format matters is when you present the Date value to a user as a string or number. At that point the format can be set to *ISO, *EUR, *USA, *JIS, *YMD, *MDY, *DMY, or *JUL, and in some cases *LONGJUL and the *Cxxx formats are available.
Since none of variants suited my needs I've came out with my own.
It is as simple as:
select * from yourschema.yourtable where yourdate = int(CURRENT DATE - 1 days) - 19000000;
This days thing is leap year-aware and suits most needs fine.
Same way days can be turned to months or years.
No need for heavy artillery like VARCHAR_FORMAT/TIMESTAMP_FORMAT.
Below worked for me:
select date(substring(trim(DateCharCol), 1, 2)||'/'||substring(trim(DateCharCol), 3, 2)||'/'||'20'||substring(trim(DateCharCol), 5, 2)) from yourTable where TableCol =?;

oracle 12c select to_date(' ',' ') dt from dual; returns 2017-10-01

somehow select to_date('space','space') dt from dual;
returns some date 2017-10-01,
but to_date('','space') or to_date('space','') returns null as expected.
where "space" is chr(32)
Any idea?
Thanks
I wasn't able to find all the defaults in the documentation, but this should suffice:
If you specify a date value without a time component, then the default
time is midnight. If you specify a date value without a date, then the
default date is the first day of the current month.
https://docs.oracle.com/cd/B28359_01/server.111/b28286/functions191.htm#SQLRF06132
(This is for Oracle 11.1, but you will find the same for other versions).
In your code, you specify a date without a time component and without a date component. Apply both defaults from above and you will get your answer.
Another small piece of the puzzle is the treatment of spaces. You can take any valid TO_DATE() and add generous spaces around any of the date and time elements, in both arguments, and you will see they are ignored. In your case, the single space is ignored (but not "collapsed" - your inputs aren't converted to empty strings, meaning null in Oracle).
According to Tom Kyte on the AskTom forums, there are some default values that are used with TO_DATE when a format and input date cannot be determined.
https://asktom.oracle.com/pls/apex/f?p=100:11:0::::P11_QUESTION_ID:4843459400346642911
default year = current year
default month = current month
default day = 1
default hour = 0
default minute = 0
default second = 0
This means that using a TO_DATE(' ', ' ') means that Oracle cannot work out what format or input date is being used, so it uses the 1st day of the current month and year, which is 2017-10-01 (yyyy-mm-dd format).
It also means your second query of TO_DATE('', ' ') returns NULL, because the value you're supplying as a date is empty. This is different from a space - I think Oracle sees the space and thinks "oh I don't know what this is, I'll show the default".
There is a brief mention of this on the Oracle Format Models documentation page here, but I couldn't find a mention of what the default values are.
Update: as mathguy mentioned below:
if the day is missing from BOTH the string to be converted AND from the format model, then by default it is assumed to be 1. Etc. `to_date('15', 'dd') will return midnight on the 15th of the current month and year. To the extreme, the OP omitted ALL the elements from BOTH arguments, so all are assigned the defaults. It's not that Oracle "can't work out what format", but instead it's all defaults

In Oracle, convert number(5,10) to date

When ececute the following SQL syntax in Oracle, always not success, please help.
40284.3878935185 represents '2010-04-16 09:18:34', with microsecond.
an epoch date of 01 January 1900 (like Excel).
create table temp1 (date1 number2(5,10));
insert into temp1(date1) values('40284.3878935185');
select to_date(date1, 'yyyy-mm-dd hh24:mi:ssxff') from temp1
Error report: SQL Error: ORA-01861: literal does not match format
string
01861. 00000 - "literal does not match format string"
*Cause: Literals in the input must be the same length as literals in
the format string (with the exception of leading whitespace). If the
"FX" modifier has been toggled on, the literal must match exactly,
with no extra whitespace.
*Action: Correct the format string to match the literal.
Thanks to Mark Bannister
Now the SQL syntax is:
select to_char(to_date('1899-12-30','yyyy-mm-dd') +
date1,'yyyy-mm-dd hh24:mi:ss') from temp1
but can't fetch the date format like 'yyyy-mm-dd hh24:mi:ss.ff'. Continue look for help.
Using an epoch date of 30 December 1899, try:
select to_date('1899-12-30','yyyy-mm-dd') + date1
Simple date addition doesn't work with timestamps, at least if you need to preserve the fractional seconds. When you do to_timestamp('1899-12-30','yyyy-mm-dd')+ date1 (in a comment on Mark's answer) the TIMESTAMP is implicitly converted to a DATE before the addition, to the overall answer is a DATE, and so doesn't have any fractional seconds; then you use to_char(..., '... .FF') it complains with ORA-01821.
You need to convert the number of days held by your date1 column into an interval. Fortunately Oracle provides a function to do exactly that, NUMTODSINTERVAL:
select to_timestamp('1899-12-30','YYYY-MM-DD')
+ numtodsinterval(date1, 'DAY') from temp3;
16-APR-10 09.18.33.999998400
You can then display that in your desired format, e.g. (using a CTE to provide your date1 value):
with temp3 as ( select 40284.3878935185 as date1 from dual)
select to_char(to_timestamp('1899-12-30','YYYY-MM-DD')
+ numtodsinterval(date1, 'DAY'), 'YYYY-MM-DD HH24:MI:SSXFF') from temp3;
2010-04-16 09:18:33.999998400
Or to restrict to thousandths of a second:
with temp3 as ( select 40284.3878935185 as date1 from dual)
select to_char(to_timestamp('1899-12-30','YYYY-MM-DD')+
+ numtodsinterval(date1, 'DAY'), 'YYYY-MM-DD HH24:MI:SS.FF3') from temp3;
2010-04-16 09:18:33.999
An epoch of 1899-12-30 sounds odd though, and doesn't correspond to Excel as you stated. It seems more likely that your expected result is wrong and it should be 2010-04-18, so I'd check your assumptions. Andrew also makes some good points, and you should be storing your value in the table in a TIMESTAMP column. If you receive data like this though, you still need something along these lines to convert it for storage at some point.
Don't know the epoch date exactly, but try something like:
select to_date('19700101','YYYYMMDD')+ :secs_since_epoch/86400 from dual;
Or, cast to timestamp like:
select cast(to_date('19700101', 'YYYYMMDD') + :secs_since_epoch/86400 as timestamp with local time zone) from dual;
I hope this doesn't come across too harshly, but you've got to totally rethink your approach here.
You're not keeping data types straight at all. Each line of your example misuses a data type.
TEMP1.DATE1 is not a date or a varchar2, but a NUMBER
you insert not the number 40284.3878935185, but the STRING >> '40284.3878935185' <<
your SELECT TO_DATE(...) uses the NUMBER Temp1.Date1 value, but treats it as a VARCHAR2 using the format block
I'm about 95% certain that you think Oracle transfers this data using simple block data copies. "Since each Oracle date is stored as a number anyway, why not just insert that number into the table?" Well, because when you're defining a column as a NUMBER you're telling Oracle "this is not a date." Oracle therefore does not manage it as a date.
Each of these type conversions is calculated by Oracle based on your current session variables. If you were in France, where the '.' is a thousands separator rather than a radix, the INSERT would completely fail.
All of these conversions with strings are modified by the locale in which Oracle thinks your running. Check dictionary view V$NLS_PARAMETERS.
This gets worse with date/time values. Date/time values can go all over the map - mostly because of time zone. What time zone is your database server in? What time zone does it think you're running from? And if that doesn't spin your head quite enough, check out what happens if you change Oracle's default calendar from Gregorian to Thai Buddha.
I strongly suggest you get rid of the numbers ENTIRELY.
To create date or date time values, use strings with completely invariant and unambiguous formats. Then assign, compare and calculate date values exclusively, e.g.:
GOODFMT constant VARCHAR2 = 'YYYY-MM-DD HH24:MI:SS.FFF ZZZ'
Good_Time DATE = TO_DATE ('2012-02-17 08:07:55.000 EST', GOODFMT);