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

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

Related

Convert two fields (Month and Year) into a YYYY-MM-DD Field

I am using SAP HANA SQL (Through Alteryx) via an in-DB formula.
I have two fields (Month and YEAR) and I need to convert/combine these into one field shown as YYYY-MM-DD. I am able to do this succesfully locally in Alteryx but I need to make this happen within the DB via SQL.
See image for succesful local conversion in Alteryx:
There seem to be two goals here:
construct a valid date from year and month information.
represent this date in a specific format, ie. YYYY-MM-DD
The first part can be done in HANA like this:
to_date( "<year_column>" || "<month_column>", 'YYYYMM') as newDate
The double-pipe || operator concatenates strings, which means, that <year_column> and <month_column> data will be first converted into strings if these are not already string-values.
The concatenated string is then turned into a date data type. The to_date conversion function takes the pattern string YYYYMM and since the day information is missing, it makes it up on the fly and sets the day to the first day of the month.
This to_date conversion also checks for that only valid dates are created.
If, for example, the MM would not be a value between 01 and 12 then the conversion would fail with an error.
This brings me to the next potential obstacle to look out for: the conversion string pattern YYYYMM requires that there will be exactly four digits denoting the year and exactly two digits for the month.
While this may be fine for the existing year data as most dates are denoted with four digits nowadays, there is a good chance that the month data does not have a leading zero (e.g. when the data is currently stored in a numeric field).
To "fix" this issue, we can just add the leading zero for all values that only have a single digit so far. There's a couple of ways to do this in HANA, and as this does not seem to be in an ABAP context, I'd go with a way that works on most SQL databases:
LPAD ("<month_column>", 2, '0')
This gets us to the following expression for step 1:
to_date( "<year_column>" || LPAD ("<month_column>", 2, '0'), 'YYYYMM') as newDate
Step 2 now is relatively easy: turn the date-data that we constructed in step 1 and represent it in a specific format.
Since date-data per se does not have a specific output format (ie. you can display or print the same date format any way you like - it doesn't change the data), it needs to be converted to a string for that.
The conversion function for that is called TO_NVARCHAR() and can also take a conversion pattern:
to_nvarchar( "<date_data>", 'YYYY-MM-DD') as fixedFormatDate
is what we're looking for this question.
Putting it all together into a single expression:
to_nvarchar(to_date( "<year_column>"
|| LPAD ("<month_column>", 2, '0')
, 'YYYYMM')
, 'YYYY-MM-DD') as fixedFormatDate
While this is a long answer to a seemingly simple question, I believe it is important to understand all the involved steps that are necessary for this conversion.

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 =?;

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

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

1st Day of Current Year in Date Range Criteria in PS Query

I know how to select the first day of the first month of the current year in a number of different formats. The following works fine: '01-JAN-' || TO_CHAR(TO_DATE(SYSDATE),'YYYY').
However, I need to use January 1, of the current year in a date range criteria in a YTD PSoft Query:
WHERE A.effdt BETWEEN (January 1, Current_Year) AND SYSDATE.
When I use the expression '01-JAN-' || TO_CHAR(TO_DATE(SYSDATE),'YYYY') in the criteria, I get the following error:
A SQL error occurred. Please consult your system log for details.
Error in running query because of SQL Error, Code=1858, Message=ORA-01858:
a non-numeric character was found where a numeric was expected (50,380)`
You should NEVER compare LITERAL with DATE. Since, Oracle will do an IMPLICIT conversion. And, sooner or later, it would become a performance issue.
Explicitly convert the literal to date using TO_DATE.
For example,
Depending on the date value input method,
1. If you are passing the literal via some program
BETWEEN TO_DATE('01-01-2014','DD-MM-YYYY') and SYSDATE
2. If you already have the date value in table, then use TRUNC
BETWEEN TRUNC(SYSDATE, 'YYYY') and SYSDATE

Do the TO_DATE function in Oracle 11g PL/SQL have upper/lower limits

I am new to Oracle development and am still discovering a lot of it's capabilities and the differences between PL/SQL and SQL Server's T-SQL.
So my 'project' is to create a function that would generate a random date from either the future or the past. The user should be able to specify the upper/lower bounds in which the date should be created.
I may be too late but in the interest of breivity I will skip the details of why the function does what it does and how. What is important and pertinent to the actual questions is the following behavoir.
BEGIN
SYS.DBMS_OUTPUT.PUT_LINE(TO_DATE(TRUNC(SYSDATE + 6469), 'yyyy/mm/dd')); -- Dec 31, 2031
SYS.DBMS_OUTPUT.PUT_LINE(TO_DATE(TRUNC(SYSDATE - 4853), 'yyyy/mm/dd')); -- Jan 01, 2001
SYS.DBMS_OUTPUT.PUT_LINE(TO_DATE(TRUNC(SYSDATE + 7000), 'yyyy/mm/dd')); -- ERROR
END
Simply put, when I add enough days to the SYSTEM date to exceed Dec 31, 2031 I get the following error message ... "a non-numeric character was found where a numeric was expected" ... . I experience the same problem when I subtract enough days from the SYSTEM date to pre-date Jan 01, 2001.
When I remove the format it's doesn't break but returns an impossible date ...
BEGIN
-- returns 15-JUN-33
-- June 33rd 2015!!
SYS.DBMS_OUTPUT.PUT_LINE(TO_DATE(TRUNC(SYSDATE + 7000)));
END
So it appears to me that there is some sort of date range limit on this function ... JAN 01, 2001 - DEC 31, 2031 ... Is that true or am I doing something wrong here?
Thanks!
Let's work through the data types here.
sysdate returns a date. Adding a number to a date returns a date that many days in the future. So, for example, sysdate + 7000 is June 15, 2033. trunc(sysdate + 7000) also returns a date, it just sets the time to midnight. So far, so good.
The problem comes when you take that date and pass it to the to_date function. Logically, that doesn't make sense. You already have a date, there is no need to convert it to a date. Practically, to_date does not accept a date as a parameter. It only accepts a string. Now, Oracle can implicitly convert the date you have to a string using your session's nls_date_format setting which is what it does here. Best case, you're taking a date, implicitly converting that to a string, then explicitly converting that string back to exactly the same date that you started with. If your session's nls_date_format happens not to match the format mask that you're providing to the to_date, however, you'll likely get an error which is what you're seeing here.
Walking through an example, let's use the date of midnight on June 15, 2033. If you call to_date on that, Oracle has to convert the date to a string using your session's nls_date_format. If you're in the United States and you haven't changed anything about your client, your nls_date_format is probably DD-MON-RR. That means that your date gets converted to the string 15-JUN-33 when it is passed in to to_date. So, logically, you're trying to do something like
dbms_output.put_line( to_date( '15-JUN-33', 'yyyy/mm/dd' ));
When you look at it this way, it's obvious that the format mask doesn't match the format of the string which causes an error. If your nls_date_format is closer to the format mask in your to_date, it is possible that the to_date call will run successfully but return a different date than you expect (switching the month and the day for example).
The simple answer is that you should never call to_date on a day. You should only call to_date on a string. If you want to convert a date into a string in a particular format for display, use to_char not to_date.
Going back to the original question, yes there are limits to what constitutes a valid date in Oracle. A valid date must be between Jan 1, 4712 BC (6700 years ago) to Dec 31, 9999 (7900 years from now). It doesn't appear that you are anywhere near exceeding those limits.