Skip to Main Content
  • Questions
  • Handling where exists subquery error

Breadcrumb

Question and Answer

Tom Kyte

Thanks for the question, Franck.

Asked: October 09, 2013 - 9:02 am UTC

Last updated: October 10, 2013 - 5:52 pm UTC

Version: 11.2.0.4

Viewed 1000+ times

You Asked

Hi,

I'm facing a problem (or a understanding problem...) with subquery error in where exists clause.

here's my query:
           SELECT 'Ok' 
               FROM
                  DUAL 
               WHERE
                  EXISTS ( 
                     SELECT
                        to_date('2013-45-45','YYYY-MM-DD')
                     FROM
                        DUAL 
                    ); 


I was hoping for an error raise with the wrong date, but not.
Is it normal?

Thanks in avance.
Franck.

and Tom said...

TO_DATE with the format yyyy-mm-dd is known to be deterministic and 'pure'.

it is deterministic obviously, it will always return the same date with that format given the same inputs. There is no external way to change what it returns via any session/database setting - it would always return the same output.

it is 'pure' in that it has no side effects. It is known by the compiler that the function will not update a row in another table, it will not write to a file, it will not change a global variable. It has NO side effects.

that is might raise an exception is not considered a side effect.

also, the compiler sees that the result of this to_date is not used anywhere, it is not referenced by anything. It is code that can be optimized away.


therefore the optimizer in the sql compiler realizes that this call to to_date can be replaced by any constant - and that is exactly what it does:

ops$tkyte%ORA11GR2> set autotrace on explain
ops$tkyte%ORA11GR2>
ops$tkyte%ORA11GR2> SELECT 'Ok'
  2                 FROM
  3                    DUAL
  4                 WHERE
  5                    EXISTS (
  6                       SELECT
  7                          to_date('2013-45-45','YYYY-MM-DD')
  8                       FROM
  9                          DUAL
 10                      );

'O
--
Ok


Execution Plan
----------------------------------------------------------
Plan hash value: 1685426719

-----------------------------------------------------------------
| Id  | Operation        | Name | Rows  | Cost (%CPU)| Time     |
-----------------------------------------------------------------
|   0 | SELECT STATEMENT |      |     1 |     4   (0)| 00:00:01 |
|*  1 |  FILTER          |      |       |            |          |
|   2 |   FAST DUAL      |      |     1 |     2   (0)| 00:00:01 |
|   3 |   FAST DUAL      |      |     1 |     2   (0)| 00:00:01 |
-----------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

   1 - filter( EXISTS (SELECT 0 FROM "SYS"."DUAL" "DUAL"))

ops$tkyte%ORA11GR2>
ops$tkyte%ORA11GR2> set autotrace off


If you want to safely check if a date (or a number) hidden in a string is a date (or a number), a safe approach will be:

ops$tkyte%ORA11GR2> create or replace
  2  function Is_date(X in varchar2, fmt in varchar default 'yyyy-mm-dd' ) return varchar2
  3  is
  4      procedure Check_date(Y in date)
  5      is
  6      begin
  7          null;
  8      end;
  9  begin
 10         pragma Inline(Check_date, 'No');
 11         Check_date(To_date(X,fmt) );
 12         return 'Y';
 13  exception
 14     when others
 15  then
 16      return 'N';
 17  end Is_date;
 18  /

Function created.



ops$tkyte%ORA11GR2>
ops$tkyte%ORA11GR2> select is_date( '2013-45-45' ) is_d from dual;

IS_D
----------
N



the disabling of function inlining will make it do the call to check_date HAS to be made as a function call - making it so that the DATE has to be pushed onto the call stack. There is no chance for the optimizing compiler to remove the call to to_date in this case. If the call to to_date needed for the call to check_date fails for any reason, we know that the string input was not convertible by that date format.

Rating

  (4 ratings)

Is this answer out of date? If it is, please let us know via a Comment

Comments

Pragma Inline

Rajeshwaran, October 09, 2013 - 4:22 pm UTC

Tom:

I dont understand why you need a pragma Inline declarative? why not a simple routine like this? do you see any flaw with this?

rajesh@ORA11GR2> create or replace function
  2  f(x varchar2,fmt varchar2 default 'yyyy-mm-dd')
  3  return varchar2
  4  as
  5     l_dt date ;
  6  begin
  7     l_dt := to_date(x,fmt);
  8     return 'Y';
  9  exception
 10             when others then
 11                     return 'N' ;
 12  end;
 13  /

Function created.

rajesh@ORA11GR2>
rajesh@ORA11GR2>
rajesh@ORA11GR2> select f('2013-45-45') from dual;

F('2013-45-45')
-------------------------------------------------------
N

1 row selected.

rajesh@ORA11GR2>

Tom Kyte
October 09, 2013 - 6:22 pm UTC

I alluded to the reason above:

... the disabling of function inlining will make it do the call to check_date HAS to be made as a function call ...

actually, I sort of said it outright :)

we know that l_dt is an unreferenced variable. You do not use it anywhere other than that assignment.

we know that to_date is deterministic.

we know that to_date is pure (no side effects).

therefore the code "l_dt := to_date(x,fmt)" may be optimized away as dead code. You are assigning a deterministic value that is the result of a function with no side effects to a variable you never use.

the code could become simply:

begin
return 'Y';
end;


in fact, it will be, for example:

ops$tkyte%ORA11GR2> connect /
Connected.
ops$tkyte%ORA11GR2>
ops$tkyte%ORA11GR2> create or replace procedure p
  2  authid definer
  3  as
  4    function Is_Number(x in varchar2) return varchar2
  5    is
  6      n number;
  7    begin
  8      n := To_Number(x);
  9      return 'Y';
 10    exception
 11      when value_error then
 12        return 'N';
 13    end Is_Number;
 14
 15  begin
 16    DBMS_Output.Put_Line('1 - ' || Is_Number('1'));
 17    DBMS_Output.Put_Line('a - ' || Is_Number('a'));
 18  end p;
 19  /

Procedure created.

ops$tkyte%ORA11GR2>
ops$tkyte%ORA11GR2> exec p
1 - Y
a - N

PL/SQL procedure successfully completed.

ops$tkyte%ORA11GR2> alter procedure p compile plsql_optimize_level = 3;

Procedure altered.

ops$tkyte%ORA11GR2> exec p
1 - Y
a - Y

PL/SQL procedure successfully completed.

ops$tkyte%ORA11GR2>
ops$tkyte%ORA11GR2>
ops$tkyte%ORA11GR2> connect /
Connected.
ops$tkyte%ORA11GR2>
ops$tkyte%ORA11GR2> create or replace procedure p
  2  authid definer
  3  as
  4    function Is_date(x in varchar2, fmt in varchar2 default 'yyyy-mm-dd' ) return varchar2
  5    is
  6      n date;
  7    begin
  8      n := To_date(x,fmt);
  9      return 'Y';
 10    exception
 11      when others then
 12        return 'N';
 13    end Is_date;
 14
 15  begin
 16    DBMS_Output.Put_Line('jan 1 - ' || Is_date('2013-01-01'));
 17    DBMS_Output.Put_Line('45-45 - ' || Is_date('2013-45-45'));
 18  end p;
 19  /

Procedure created.

ops$tkyte%ORA11GR2>
ops$tkyte%ORA11GR2> exec p
jan 1 - Y
45-45 - N

PL/SQL procedure successfully completed.

ops$tkyte%ORA11GR2> alter procedure p compile plsql_optimize_level = 3;

Procedure altered.

ops$tkyte%ORA11GR2> exec p
jan 1 - Y
45-45 - Y

PL/SQL procedure successfully completed.




this is not a bug, repeat: this is NOT a bug

optimizing compilers do this all of the time - all of the time.


This is documented:
http://docs.oracle.com/cd/E11882_01/appdev.112/e25519/tuning.htm#i53930

"In even rarer cases, PL/SQL might raise an exception earlier than expected or not at all."


the way to code the "is_datatype" function - knowing that to_date/to_number are pure deterministic functions - is to include that non-inlinable function call to make sure the date/number has to be pushed onto the stack (that the date/number is in fact *used*).

another valid approach would be:

ops$tkyte%ORA11GR2> create or replace procedure p
  2  authid definer
  3  as
  4    function Is_date(x in varchar2, fmt in varchar2 default 'yyyy-mm-dd' ) return varchar2
  5    is
  6      n date;
  7    begin
  8      n := To_date(x,fmt);
  9          if (n is not null)
 10          then
 11                  return 'Y';
 12          else
 13                  return NULL;
 14          end if;
 15    exception
 16      when others then
 17        return 'N';
 18    end Is_date;
 19
 20  begin
 21    DBMS_Output.Put_Line('jan 1 - ' || Is_date('2013-01-01'));
 22    DBMS_Output.Put_Line('45-45 - ' || Is_date('2013-45-45'));
 23  end p;
 24  /

Procedure created.

ops$tkyte%ORA11GR2>
ops$tkyte%ORA11GR2> exec p
jan 1 - Y
45-45 - N

PL/SQL procedure successfully completed.

ops$tkyte%ORA11GR2> alter procedure p compile plsql_optimize_level = 3;

Procedure altered.

ops$tkyte%ORA11GR2> exec p
jan 1 - Y
45-45 - N

PL/SQL procedure successfully completed.



Now we have a function that returns Y/N or NULL (which is perhaps better in some respects) and forces N to be used.

Franck LERAY, October 09, 2013 - 4:26 pm UTC


Where have you hidden the real Tom?

Kevin Seymour, October 09, 2013 - 6:43 pm UTC

EXCEPTION WHEN OTHERS without a RAISE!?
Tom Kyte
October 09, 2013 - 7:41 pm UTC

there are exceptions to every rule :) there are very very very very few good times to have a when others not followed by raise. dbms_job being one. an apex application (since plsql is the client language in that case) would be another. surrounding a to_date (notice there is really no other real "code" there) is another.


IS_DATE FUNCTION

A reader, October 10, 2013 - 4:27 pm UTC

Hi Tom,

When compared to your example(the first followup using PRAGMA INLINE), what's your comment on the function below(sk_fun_date)?
Is this less efficient that the PRAGMA INLINE code you have because it switches from PL/SQL to SQL and back to PL/SQL?


SQL> CREATE OR REPLACE FUNCTION sk_fun_date(I_date IN VARCHAR2, I_format varchar2)
  2  RETURN VARCHAR2 IS
  3     L_date    DATE;
  4  BEGIN
  5     SELECT TO_DATE(I_date, I_format)
  6     INTO L_date
  7     FROM dual;
  8  
  9     RETURN 'Y';
 10  EXCEPTION
 11     WHEN OTHERS THEN
 12        RETURN 'N';
 13  END;
 14  /

Function created.

SQL> 
SQL> ALTER FUNCTION SK_FUN_DATE COMPILE PLSQL_OPTIMIZE_LEVEL = 3;

Function altered.

SQL> 
SQL> column dat format a10
SQL> 
SQL> SELECT sk_fun_DATE('2013101A', 'YYYYMMDD') dat
  2  FROM DUAL;

DAT                                                                                                 
----------                                                                                          
N                                                                                                   

SQL> 
SQL> SELECT sk_fun_DATE('20131010', 'YYYYMMDD') dat
  2  FROM DUAL;

DAT                                                                                                 
----------                                                                                          
Y                                                                                                   

Thank you

Tom Kyte
October 10, 2013 - 5:52 pm UTC

do you see that it *could* happen? I showed that it is 100% definitely a possibility. That the optimization did not happen in one specific case does not mean "optimization doesn't happen". It just means "it doesn't happen *yet* in that very specific case"


as stated above

a) to_date is pure and deterministic
b) l_date in your code is assigned to but never used
c) therefore, that code can be (I don't care that it right now, today, 10-oct-2013 isn't) reduced to:

CREATE OR REPLACE FUNCTION sk_fun_date(I_date IN VARCHAR2, I_format varchar2)
RETURN VARCHAR2 IS
BEGIN
   RETURN 'Y';
END;
/




More to Explore

PL/SQL demos

Check out more PL/SQL tutorials on our LiveSQL tool.

PL/SQL docs

PL/SQL reference manual from the Oracle documentation library