Are a CASE statement and a DECODE equivalent? - sql

It seems like the simple CASE expression and the DECODE function are equivalent and that the results returned by them should be identical. Are they?
The documentation has the following to say about the simple CASE expression:
The simple CASE expression returns the first result for which
selector_value matches selector. Remaining expressions are not
evaluated. If no selector_value matches selector, the CASE expression
returns else_result if it exists and NULL otherwise.
Comparing this to the DECODE function, the descriptions seem to be identical.
DECODE compares expr to each search value one by one. If expr is equal
to a search, then Oracle Database returns the corresponding result. If
no match is found, then Oracle returns default. If default is omitted,
then Oracle returns null.
As the searched CASE expression can be equivalent to the simple, this could be construed to be the same as well.
These three statements all seem to return the same result, 0.
select case 1 when 2 then null else 0 end as simple_case
, case when 1 = 2 then null else 0 end as searched_case
, decode(1, 2, null, 0) as decode
from dual
Do the simple CASE expression and the DECODE function (and in specific circumstances the searched CASE expression) always return the same result?

Short answer, no.
The slightly longer answer is nearly.
It only appears that the result obtained from each statement is identical. If we use the DUMP function to evaluate the data types returned you'll see what I mean:
SQL> select dump(case 1 when 2 then null else 0 end) as simple_case
2 , dump(case when 1 = 2 then null else 0 end) as searched_case
3 , dump(decode(1, 2, null, 0)) as decode
4 from dual;
SIMPLE_CASE SEARCHED_CASE DECODE
------------------ ------------------ -----------------
Typ=2 Len=1: 128 Typ=2 Len=1: 128 Typ=1 Len=1: 48
SQL Fiddle
You can see that the data type of the DECODE is 1, whereas the two CASE statements "return" a data type of 2. Using Oracle's Data Type Summary, DECODE is returning a VARCHAR2 (data type 1) whereas the CASE statements are "returning" numbers (data type 2).
I assume this occurs because, as the names suggest, DECODE is a function and CASE isn't, which implies they have been implemented differently internally. There's no real way to prove this.
You might think that this doesn't really affect anything. If you need it to be a number Oracle will implicitly convert the character to a number under the implicit conversion rules, right? This isn't true either, it won't work in a UNION as the data types have to be identical; Oracle won't do any implicit conversion to make things easy for you. Secondly, here's what Oracle says about implicit conversion:
Oracle recommends that you specify explicit conversions, rather than rely on implicit or automatic conversions, for these reasons:
SQL statements are easier to understand when you use explicit data type conversion functions.
Implicit data type conversion can have a negative impact on performance, especially if the data type of a column value is converted to that of a constant rather than the other way around.
Implicit conversion depends on the context in which it occurs and may not work the same way in every case. For example, implicit conversion from a datetime value to a VARCHAR2 value may return an unexpected year depending on the value of the NLS_DATE_FORMAT
parameter.
Algorithms for implicit conversion are subject to change across software releases and among Oracle products. Behavior of explicit conversions is more predictable.
That's not a pretty list; but the penultimate point brings me nicely on to dates. If we take the previous query and convert it into one that uses a date instead:
select case sysdate when trunc(sysdate) then null
else sysdate
end as simple_case
, case when sysdate = trunc(sysdate) then null
else sysdate
end as searched_case
, decode(sysdate, trunc(sysdate), null, sysdate) as decode
from dual;
Once again, using DUMP on this query the CASE statements return data type 12, a DATE. The DECODE has converted sysdate into a VARCHAR2.
SQL> select dump(case sysdate when trunc(sysdate) then null
2 else sysdate
3 end) as simple_case
4 , dump(case when sysdate = trunc(sysdate) then null
5 else sysdate
6 end) as searched_case
7 , dump(decode(sysdate, trunc(sysdate), null, sysdate)) as decode
8 from dual;
SIMPLE_CASE
----------------------------------
Typ=12 Len=7: 120,112,12,4,22,18,7
SEARCHED_CASE
----------------------------------
Typ=12 Len=7: 120,112,12,4,22,18,7
DECODE
----------------------------------
Typ=1 Len=19: 50,48,49,50,45,49,50,45,48,52,32,50,49,58,49,55,58,48,54
SQL Fiddle
Note (in the SQL Fiddle) that the DATE has been converted into a character using the sessions NLS_DATE_FORMAT.
Having a date that's been implicitly converted into a VARCHAR2 can cause problems. If you're intending to use TO_CHAR, to convert your date into a character, your query will break where you're not expecting it.
SQL> select to_char( decode( sysdate
2 , trunc(sysdate), null
3 , sysdate )
4 , 'yyyy-mm-dd') as to_char
5 from dual;
select to_char( decode( sysdate
*
ERROR at line 1:
ORA-01722: invalid number
SQL Fiddle
Equally, date arithmetic no longer works:
SQL>
SQL>
SQL> select decode(sysdate, trunc(sysdate), null, sysdate) + 1 as decode
2 from dual;
select decode(sysdate, trunc(sysdate), null, sysdate) + 1 as decode
*
ERROR at line 1:
ORA-01722: invalid number
SQL Fiddle
Interestingly DECODE only converts the expression to a VARCHAR2 if one of the possible results is NULL. If the default value is NULL then this doesn't happen. For instance:
SQL> select decode(sysdate, sysdate, sysdate, null) as decode
2 from dual;
DECODE
-------------------
2012-12-04 21:18:32
SQL> select dump(decode(sysdate, sysdate, sysdate, null)) as decode
2 from dual;
DECODE
------------------------------------------
Typ=13 Len=8: 220,7,12,4,21,18,32,0
SQL Fiddle
Note that the DECODE has returned a data type of 13. This isn't documented but is, I assume, a type of date as date arithmetic etc. works.
In short, avoid DECODE if you possibly can; you might not necessarily get the data types you're expecting. To quote Tom Kyte:
Decode is somewhat obscure -- CASE is very very clear. Things that are
easy to do in decode are easy to do in CASE, things that are hard or
near impossible to do with decode are easy to do in CASE. CASE, logic
wise, wins hands down.
Just to be complete there are two functional differences between DECODE and CASE.
DECODE cannot be used within PL/SQL.
CASE cannot be used to compare nulls directly
SQL> select case null when null then null else 1 end as case1
2 , case when null is null then null else 1 end as case2
3 , decode(null, null, null, 1) as decode
4 from dual
5 ;
CASE1 CASE2 DECODE
---------- ---------- ------
1
SQL Fiddle

Ben has written a lengthy answer on the differences between DECODE and CASE. He demonstrates that DECODE and CASE may return different datatypes for apparently the same set of values without properly explaining why this happens.
DECODE() is quite prescriptive: it is always the datatype of the first result parameter. Oracle applies implicit conversion to all the other result parameters. It will throw an error , if (say) the first result parameter is numeric and the default value is a date.
ORA-00932: inconsistent datatypes: expected NUMBER got DATE
This is described in the documentation: find out more.
In the first scenario the first result parameter is NULL, which Oracle decides to treat as VARCHAR2. If we change it so that the first result parameter is numeric and the default value is null the DECODE() statement will return a NUMBER; a DUMP() proves that this is so.
Whereas CASE insists that all the returned values have the same datatype, and will throw a compilation error if this is not the case. It won't apply implicit conversion. This is also covered in the documentation. Read it here.
The difference boils down to this. The following DECODE statement will run, the CASE statement won't:
select decode(1, 1, 1, '1') from dual;
select case 1 when 1 then 1 else '1' end from dual;
Obligatory SQL Fiddle.

I know i am too late but posting here because if someone search for that hopefully it could help.
I have created a MsSql script for the same-
Declare #Var varchar(399)='DECODE(MyColumnName,''A'',''Auto'',''M'',''Manual'')'
Begin
Declare #Count int, #Counter int=1
Declare #TempTable table (ID int identity(1,1),Items varchar(500))
Declare #SqlText varchar(max)
Select #Var=Replace(Replace(#Var,'DECODE(',''),')','')
Insert Into #TempTable
Select * FROM [dbo].[Split] ( #Var ,',')
--Select * from #TempTable
Select #Count=Count(ID) from #TempTable
While(#Counter<=#Count)
Begin
If(#Counter=1)
Begin
Select #SqlText='Case ' +Items from #TempTable Where ID=1
End
Else If(#Counter=#Count)
Begin
Select #SqlText+=' Then ' +Items +' End' from #TempTable Where ID=#Counter
End
Else If(#Counter%2=0)
Begin
Select #SqlText +=' When ' +Items from #TempTable Where ID=#Counter
End
Else If(#Counter%2=1)
Begin
Select #SqlText +=' Then ' +Items from #TempTable Where ID=#Counter
End
Set #Counter+=1
End
Select #SqlText SqlServerCaseStatement
End
I used Split function in above script, if you need that function you can refer Romil's answer - How to split a comma-separated value to columns

Related

retrieve different format of date values with time from string (t-sql)

I have a requirement where i have to pull the date/time value from string but the problem is that they can be different formats because of which substring becomes more complicated.
Here's what i came up with but is there any other method where i could simply retreive dates of different format with time and convert them all in single format?
IF OBJECT_ID('tempdb..#temp') IS NOT NULL
DROP TABLE #temp
CREATE TABLE #temp (
comments varchar(500)
)
insert into #temp (comments)
(
select 'Mailed on 1/1/22 at 5 pm'
union
select 'Mailed on 01/2/2222 # 6 am'
union
select 'Mailed on 01/2/22 in night'
union
select 'Mailed on 1/02/2222 at 4 pm'
union
select 'Mailed on 1/1/2222 at 4 pm'
);
select *
from #temp
cross apply (select PATINDEX('%Mailed On%',comments) as start_pos) as start_pos
cross apply (select case when substring(comments,patindex('%Mailed On%',comments)+9,11) like '%[0-9][0-9]/[0-9][0-9]/[0-9][0-9][0-9][0-9]%' then 1
when substring(comments,patindex('%Mailed On%',comments)+9,8) like '%[0-9][0-9]/[0-9]/[0-9][0-9]%' then 2
when substring(comments,patindex('%Mailed On%',comments)+9,10) like '%[0-9]/[0-9][0-9]/[0-9][0-9][0-9][0-9]%' then 3
when substring(comments,patindex('%Mailed On%',comments)+9,9) like '%[0-9][0-9]/[0-9][0-9]/[0-9][0-9]%' then 4
when substring(comments,patindex('%Mailed On%',comments)+9,9) like '%[0-9]/[0-9]/[0-9][0-9][0-9][0-9]%' then 5
when substring(comments,patindex('%Mailed On%',comments)+9,7) like '%[0-9]/[0-9]/[0-9][0-9]%' then 6 else null end as substr) as substr
--cross apply (select case when substring(authcomments,start_pos + 9, 11) like '%[1-9]/[0123][0-9]/[0-9][0-9][0-9][0-9]%' then 1 else null end as substr) as substr
cross apply (select case when substr = 1 then substring(comments,patindex('%Mailed On%',comments)+9,11)
when substr = 2 then substring(comments,patindex('%Mailed On%',comments)+9,8)
when substr = 3 then substring(comments,patindex('%Mailed On%',comments)+9,10)
when substr = 4 then substring(comments,patindex('%Mailed On%',comments)+9,9)
when substr = 5 then substring(comments,patindex('%Mailed On%',comments)+9,9)
when substr = 6 then substring(comments,patindex('%Mailed On%',comments)+9,7)
else null end as maileddate
) as maileddate
#user1672315 ,
Sometimes you get stuff like this and in order to fix it so that you can get the dates and times to store in a table or whatever, ya gotta do what ya gotta do to get it and, contrary to the comments, it certainly CAN be done in SQL. It's just not that difficult. Ya just gotta know some of the "gazintas" ;)
So, using the readily consumable test data that you were nice enough to provide, run the following code against it...
SELECT t.*
,TheDateAndTime = DATEADD(hh,ca4.cHour,ca3.cDate)
FROM #temp t
CROSS APPLY(VALUES(SUBSTRING(comments,PATINDEX('%[0-9]%',comments),500))) ca1(DT)
CROSS APPLY(VALUES(SUBSTRING(ca1.dt,PATINDEX('% [0-9]%',ca1.dt),500))) ca2(TM)
CROSS APPLY(VALUES(TRY_CONVERT(DATETIME,SUBSTRING(ca1.DT,1,PATINDEX('%[0-9] %',ca1.DT))))) ca3(cDate)
CROSS APPLY(VALUES(IIF(ca2.TM LIKE '%night%',23,DATEPART(hh,TRY_CONVERT(DATETIME,ca2.TM)))))ca4(cHour)
;
... and see that you CAN do it in SQL... BUT, see the warnings below the graphic below.
You also need to figure out what hour "night" is going to be assigned. I assigned "23" as the hour.
Results are as follows:
I'm thinking that your "2222" years are in error, though. :D
One thing I do agree on is that the format needs to be somewhat consistent. No code in the world, Python or otherwise, will be able to distinguish between a mm-dd-yy and dd-mm-yy format when dd and mm are both less than 13. The code I posted assumes (m)m-(d)d-yy and is based on the current LANGUAGE and DATEFORMAT that I'm using. It WILL return NULLs where the mm part isn't between 1 and 12 or if the dd part isn't between 1 and 31 or if the date is an "illegal date" like 2/29/2021, etc, though.
It also assumes that the format will always contain the numeric date as the first set of numeric values it comes across and that the time will always be the last thing in the string. We can add more checks, if needed but, like I said, unless mm is >=13, it cannot (nor can anything else) determine if it should be mm-dd-yy or dd-mm-yy because there's simply no other information in the string to indicate which format is being used. You MUST check your date format to use this, as well. If the strings are supposed to be in the dd-mm-yy format, we may have to make a change (although I believe SQL server will auto-magically accommodate that if the DATEFORMAT matches the intention of the string).

Any function in Oracle similar to COALESCE, but instead of NULL works on ERRORs

I have a situation where I need to convert a numeric column value into time. It's a 6 digit field, but unfortunately, different processes over the years inserted data in different format, some HHMM, and others HHMMSS. Let's call this column colTime. I use colTime in combination with another 8 digits numeric field which contains a date in YYYYMMDD format, let's call this colDate.
It is being used as below to construct a TIMESTAMP in UTC zone:
select TO_CHAR(SYS_EXTRACT_UTC(TO_TIMESTAMP(CONCAT(NULLIF(colDate,0), LPAD(NULLIF(colTime,0),4,0)), 'YYYY-MM-DD HH24:MI')), 'YYYY-MM-DD"T"HH24:MI:SS.FFTZR') from tab1;
The problem here obviously is that the colTime may contain 4 OR 6 digit data so I cannot know the correct LPAD number in advance. When the above statement encounters a 6 digit field it throws an error.
I was thinking if I have a function similar to COALESCE that can execute the second argument if the first one returns an error then I'd be able to accommodate LPAD 4 and 6 cases.
I can use a CASE statement, but was hoping for something more graceful.
You can still use COALESCE if you also use the DEFAULT NULL ON CONVERSION ERROR syntax in TO_TIMESTAMP:
select
coalesce
(
to_timestamp('2021-01-01' || ' ' ||the_time default null on conversion error, 'YYYY-MM-DD HHMI'),
to_timestamp('2021-01-01' || ' ' ||the_time default null on conversion error, 'YYYY-MM-DD HHMISS')
)
from
(
select '0102' the_time from dual union all
select '010203' the_time from dual
);
You can use regular expressions to find out which format the data comes from and then you can convert it appropriately.
For example:
with
d (col_time) as (
select '0215' from dual
union all select '183207' from dual
union all select '12:34:56' from dual
union all select 'really-bad-data' from dual
)
select d.*,
case when regexp_like(col_time, '^[0-9]{4}$') then 'Short Time Format'
when regexp_like(col_time, '^[0-9]{6}$') then 'Long Time Format'
else 'Other Time Format'
end as guessed_format
from d;
Result:
COL_TIME GUESSED_FORMAT
---------------- -----------------
0215 Short Time Format
183207 Long Time Format
12:34:56 Other Time Format
really-bad-data Other Time Format

All rows with date <= 90 days in oracle based on varchar2 date string [duplicate]

I have the following query that I am attempting to use as a COMMAND in a crystal report that I am working on.
SELECT * FROM myTable
WHERE to_date(myTable.sdate, 'MM/dd/yyyy') <= {?EndDate}
This works fine, however my only concern is that the date may not always be in the correct format (due to user error). I know that when the to_date function fails it throws an exception.. is it possible to handle this exception in such a way that it ignores the corresponding row in my SELECT statement? Because otherwise my report would break if only one date in the entire database is incorrectly formatted.
I looked to see if Oracle offers an isDate function, but it seems like you are supposed to just handle the exception. Any help would be greatly appreciated. Thanks!!
Echoing Tony's comment, you'd be far better off storing dates in DATE columns rather than forcing a front-end query tool to find and handle these exceptions.
If you're stuck with an incorrect data model, however, the simplest option in earlier versions is to create a function that does the conversion and handles the error,
CREATE OR REPLACE FUNCTION my_to_date( p_date_str IN VARCHAR2,
p_format_mask IN VARCHAR2 )
RETURN DATE
IS
l_date DATE;
BEGIN
l_date := to_date( p_date_str, p_format_mask );
RETURN l_date;
EXCEPTION
WHEN others THEN
RETURN null;
END my_to_date;
Your query would then become
SELECT *
FROM myTable
WHERE my_to_date(myTable.sdate, 'MM/dd/yyyy') <= {?EndDate}
Of course, you'd most likely want a function-based index on the MY_TO_DATE call in order to make this query reasonably efficient.
In 12.2, Oracle has added extensions to the to_date and cast functions to handle conversions that error
SELECT *
FROM myTable
WHERE to_date(myTable.sdate default null on conversion error, 'MM/dd/yyyy') <= {?EndDate}
You could also use the validate_conversion function if you're looking for all the rows that are (or are not) valid dates.
SELECT *
FROM myTable
WHERE validate_conversion( myTable.sdate as date, 'MM/DD/YYYY' ) = 1
If your data is not consistent and dates stored as strings may not be valid then you have 3 options.
Refactor your DB to make sure that the column stores a date datatype
Handle the exception of string to date in a stored procedure
Handle the exception of string to date in a (complex) record selection formula
I would suggest using the first option as your data should be consistent.
The second option will provide some flexibility and speed as the report will only fetch the rows that are needed.
The third option will force the report to fetch every record in the table and then have the report filter down the records.
I have the same problem... an old legacy database with varchar fields for dates and decades of bad data in the field. As much as I'd like to, I can't change the datatypes either. But I came up with this solution to find if a date is current, which seems to be what you're doing as well:
select * from MyTable
where regexp_like(sdate, '[0-1][0-9].[0-3][0-9].[0-9][0-9][0-9][0-9]')
-- make sure it's in the right format and ignore rows that are not
and substr(sdate,7,10) || substr(sdate,1,2) || substr(sdate,4,5) >= to_char({?EndDate}, 'YYYYMMDD')
-- put the date in ISO format and do a string compare
The benefit of this approach is it doesn't choke on dates like "February 30".
Starting from Oracle 12c there is no need to define a function to catch the conversion exception.
Oracle introduced an ON CONVERSION ERROR clause in the TO_DATE function.
Basically the clause suppress the error in converting of an invalid date string (typical errors are ORA-01843, ORA-01841, ORA-011861, ORA-01840) and returns a specified default value or null.
Example of usage
select to_date('2020-99-01','yyyy-mm-dd') from dual;
-- ORA-01843: not a valid month
select to_date('2020-99-01' default null on conversion error,'yyyy-mm-dd') from dual;
-- returns NULL
select to_date('2020-99-01' default '2020-01-01' on conversion error,'yyyy-mm-dd') from dual;
-- 01.01.2020 00:00:00
Solution for the Legacy Application
Let's assume there is a table with a date column stored as VARCHAR2(10)
select * from tab;
DATE_CHAR
----------
2021-01-01
2021-99-01
Using the above feature a VIRTUAL DATE column is defined, that either shows the DATE or NULL in case of the conversion error
alter table tab add (
date_d DATE as (to_date(date_char default null on conversion error,'yyyy-mm-dd')) VIRTUAL
);
select * from tab;
DATE_CHAR DATE_D
---------- -------------------
2021-01-01 01.01.2021 00:00:00
2021-99-01
The VIRTUAL column can be safely used because its format is DATE and if required an INDEX can be set up on it.
select * from tab where date_d = date'2021-01-01';
Since you say that you have "no access" to the database, I am assuming that you can not create any functions to help you with this and that you can only run queries?
If that is the case, then the following code should get you most of what you need with the following caveats:
1) The stored date format that you want to evaluate is 'mm/dd/yyyy'. If this is not the case, then you can alter the code to fit your format.
2) The database does not contain invalid dates such as Feb 30th.
First, I created my test table and test data:
create table test ( x number, sdate varchar2(20));
insert into test values (1, null);
insert into test values (2, '01/01/1999');
insert into test values (3, '1999/01/01');
insert into test values (4, '01-01-1999');
insert into test values (5, '01/01-1999');
insert into test values (6, '01-01/1999');
insert into test values (7, '12/31/1999');
insert into test values (8, '31/12/1999');
commit;
Now, the query:
WITH dates AS (
SELECT x
, sdate
, substr(sdate,1,2) as mm
, substr(sdate,4,2) as dd
, substr(sdate,7,4) as yyyy
FROM test
WHERE ( substr(sdate,1,2) IS NOT NAN -- make sure the first 2 characters are digits
AND to_number(substr(sdate,1,2)) between 1 and 12 -- and are between 0 and 12
AND substr(sdate,3,1) = '/' -- make sure the next character is a '/'
AND substr(sdate,4,2) IS NOT NAN -- make sure the next 2 are digits
AND to_number(substr(sdate,4,2)) between 1 and 31 -- and are between 0 and 31
AND substr(sdate,6,1) = '/' -- make sure the next character is a '/'
AND substr(sdate,7,4) IS NOT NAN -- make sure the next 4 are digits
AND to_number(substr(sdate,7,4)) between 1 and 9999 -- and are between 1 and 9999
)
)
SELECT x, sdate
FROM dates
WHERE to_date(mm||'/'||dd||'/'||yyyy,'mm/dd/yyyy') <= to_date('08/01/1999','mm/dd/yyyy');
And my results:
X SDATE
- ----------
2 01/01/1999
The WITH statement will do most of the validating to make sure that the sdate values are at least in the proper format. I had to break out each time unit month / day / year to do the to_date evaluation because I was still getting an invalid month error when I did a to_date on sdate.
I hope this helps.
Trust this reply clarifies...
there is no direct EXCEPTION HANDLER for invalid date.
One easy way is given below once you know the format like DD/MM/YYYY then below given REGEXP_LIKE function will work like a charm.
to_date() also will work, when invalid_date is found then cursor will goto OTHERS EXCEPTION. given below.
DECLARE
tmpnum NUMBER; -- (1=true; 0 = false)
ov_errmsg LONG;
tmpdate DATE;
lv_date VARCHAR2 (15);
BEGIN
lv_date := '6/2/2018'; -- this will fail in *regexp_like* itself
lv_date := '06/22/2018'; -- this will fail in *to_date* and will be caught in *exception WHEN OTHERS* block
lv_date := '07/03/2018'; -- this will succeed
BEGIN
tmpnum := REGEXP_LIKE (lv_date, '[0-9]{2}/[0-9]{2}/[0-9]{4}');
IF tmpnum = 0
THEN -- (1=true; 0 = false)
ov_errmsg := '1. INVALID DATE FORMAT ';
DBMS_OUTPUT.PUT_LINE (ov_errmsg);
RETURN;
END IF;
tmpdate := TO_DATE (lv_date, 'DD/MM/RRRR');
--tmpdate := TRUNC (NVL (to_date(lv_date,'DD/MM/RRRR'), SYSDATE));
tmpnum := 1;
EXCEPTION
WHEN OTHERS
THEN
BEGIN
tmpnum := 0;
ov_errmsg := '2. INVALID DATE FORMAT ';
DBMS_OUTPUT.PUT_LINE (ov_errmsg || SQLERRM);
RETURN;
END;
-- continue with your other query blocks
END;
-- continue with your other query blocks
DBMS_OUTPUT.PUT_LINE (tmpnum);
END;

DB2 Exclude Nulls or set to arbitrary date

I am having a very difficult time handling null returns in DB2. I've tried IFNULL AND
COALESCE functions, but I still end up getting values in the return.
Here is the relevant segment of the query:
COALESCE (
DATE (
SUBSTR (DIGITS (PODATE),1,2) || char ('/') ||
SUBSTR (DIGITS (PODATE),3,2) || char ('/') ||
SUBSTR (DIGITS (PODATE),5,2)
),
DATE ('12/31/99')
) AS PODATE
This returns, but I still get nulls. Any suggestions?
I would, for a start, not coalesce a per-row function like date(). You're better off testing the actual field for NULL as in the following example:
create table a (podate varchar(8));
insert into a (podate) values ('20090101');
insert into a (podate) values ('20090102');
insert into a (podate) values ('20090103');
insert into a (podate) values (null);
commit;
select
case when podate is null
then date('2000-01-01')
else date(substr(podate,1,4) || '-' ||
substr(podate,5,2) || '-' ||
substr(podate,7,2))
end as mydate
from a;
drop table a;
commit;
which outputs:
---------
MYDATE
----------
2009-01-01
2009-01-02
2009-01-03
2000-01-01
DSNE610I NUMBER OF ROWS DISPLAYED IS 4
In your case, the code would look something like:
select case when podate is null
then date('12/31/99')
else date (substr (podate,1,2) || char ('/') ||
substr (podate,3,2) || char ('/') ||
substr (podate,5,2)
end
I haven't tested your specific case since my DB/2 is not set up for US date formats. That's something else to look out for as well. This may not work that well on platforms not using US date formats.
Ideally, dates should be stored in the database as dates, not strings. In other words, the conversion costs should be once when the data enters a table, not the millions of times you're going to extract it. Then your code becomes a simpler and faster:
select case when podate is null
then date('12/31/99')
else podate
end
or, better if you're never going to have that sentinel date as a real date, don't store NULLs at all. Instead, convert them to date('12/31/99') as they go into the tables, and use:
select podate
It doesn't get much simpler or faster than that (assuming you're comfortable with a non-NULL sentinel).
You might try with an ISO-formatted string as Pax implicitly suggested instead of the locale-dependent one you used. This SQL statement will always work, regardless of your locale date format setting.
SELECT DATE('2009-04-01')
FROM SYSIBM.SYSDUMMY1
On a other side, to breakup a numeric field, using division and modulo is sometime a nice trick. You might then use numeric comparison on it.
With the sample data Pax provided :
SELECT
MOD(PODATE / 10000, 100) AS YEAR,
MOD(PODATE / 100, 100) AS MONTH,
MOD(PODATE, 100) AS DAY
FROM (VALUES (991231), (090101), (null)) AS A (PODATE)
But as Pax said : Dates should be stored in the database as dates, not strings.
Same for numerical quantities as numerical types, etc.

Can NVL Function be Cascaded?

can nvl() function be cascaded,...it was asked me in IBM interview .....and why????
Better yet, use COALESCE
My answer:
Yes, NVL can be cascaded. It's something I'd expect to see on code ported from Oracle versions up to and including 8i, because COALESCE wasn't supported until Oracle 9i.
COALESCE is an ANSI SQL 99 standard, and works like a CASE/SWITCH statement in how it evaluates each expression in order and does not continue evaluating expressions once it hits a non-null expression. This is called short-circuiting. Another benefit of using COALESCE is that the data types do not need to match, which is required when using NVL.
This:
SELECT COALESCE( 1.5 / NULL,
SUM(NULL),
TO_CHAR(SYSDATE, 'YYYY') ) abc
FROM DUAL
...will return: 2009 (for the next ~32 days, anyways)
Why not? For example:
select NVL( null, NVL( null, 1 )) from dual
It can be something like:
select NVL( delete_date, NVL( edit_date, create_date )) AS last_change
from Table
May be they wanted you to say that it is deterministic function. So it is reentrant.
The most common reason that I have seen for cascaded NVL's is that databases change over time.
The original design had a table that later was changed to have more columns in the table.
Alter statements created the new columns as NULL allowed and this was taken advantage of with view so that the
code on top did not need to change. The problem was that a single column, change_date,
was replaced with multiple columns. Update_Date, Comment_Date, and Approval_date. Each of the
3 new columns is now combined to get a "change_date" in the view with
create or replace view OldTableNmae as
select other_columns
, nvl ( update_date, nvl ( comment_date, approval_date ) ) change_date
, more_columns
from new_table
/
As others said, NVL can be cascaded, but the preferred solution would be to use COALESCE instead. However, the two functions are not entirely interchangeable:
1) as mentioned elsewhere, COALESCE only evaluates the arguments from the first until it meets one that does not evaluate to null
2) however, COALESCE requires all arguments to be of the same data type, thus being STRICTER than NVL, which will first attempt an implicit conversion.
E.g.
SELECT COALESCE( 1.5 / NULL,
SUM(NULL),
TO_CHAR(SYSDATE, 'YYYY') ) abc
FROM DUAL
throws in fact the error ORA-00932: inconsistent datatypes: expected NUMBER got CHAR
NVL would instead return the current year
SELECT NVL( 1.5 / NULL,
NVL( SUM(NULL),
TO_CHAR(SYSDATE, 'YYYY') ) ) abc
FROM DUAL
Other examples:
select coalesce('some text',sysdate) from dual;
throws ORA-00932: inconsistent datatypes: expected CHAR got DATE, while
select nvl('some text',sysdate) from dual;
returns some text, but
select nvl(sysdate,'some text') from dual;
throws ORA-01841: (full) year must be between -4713 and +9999, and not be 0 (because the implicit conversion attempt of 'some text' to a date has failed)