Monday, March 27, 2017

Oracle 12c PL/SQL Function in the WITH Clause: wrong result

In order to make PL/SQL Function run faster in SQL, Oracle 12c introduced a new feature to integrate PL/SQL Function in the SQL WITH Clause to eliminate SQL-PLSQL context switching.

This Blog is trying to demonstrate that the outcome of this new feature is varied with "ORDER BY" clause.

Note: Tested in Oracle 12.1.0.2.0 on AIX, Solaris, Linux.


1. Build Test



drop table base_tab cascade constraints;

create table base_tab
(
  id    number(12) not null,
  seq   number(9)                    
);

create index base_tab#ix_1 on base_tab (seq, id);

create index base_tab#ix_2 on base_tab (id);

drop table date_tab cascade constraints;

create table date_tab
(
  id          number(12),
  base_id     number(12),
  come_date   date      ,
  constraint date_tab#pk primary key (base_id, come_date) enable validate
) organization index;

insert into base_tab (id, seq) values (1, 11111);
insert into base_tab (id, seq) values (2, 22222);
insert into base_tab (id, seq) values (3, 33333);
insert into base_tab (id, seq) values (4, 4444);

insert into date_tab (id, base_id, come_date) values (11, 1, to_date('2010-01-11', 'yyyy-mm-dd'));
insert into date_tab (id, base_id, come_date) values (33, 3, to_date('2030-03-13', 'yyyy-mm-dd'));
insert into date_tab (id, base_id, come_date) values (44, 4, to_date('2040-04-14', 'yyyy-mm-dd'));

commit;


2. Run Test



--*** test 1: returns 2 rows without order by *** --

with 
  function get_max_date_sqlwith (p_id in number) return date is
    l_date date;
  begin
    select come_date into l_date from (
      select come_date from date_tab where base_id = p_id
       order by come_date desc
       ) where rownum = 1;
    return l_date;
  end;
select b.id
     ,(select get_max_date_sqlwith(b.id) from dual) cdate
  from base_tab b
 where rownum <= 2
--  order by b.id
/

 ID CDATE
--- -----------
  4 14-APR-2040
  1 11-JAN-2010


--*** test 2: returns only 1 row with order by *** --

with 
  function get_max_date_sqlwith (p_id in number) return date is
    l_date date;
  begin
    select come_date into l_date from (
      select come_date from date_tab where base_id = p_id
       order by come_date desc
       ) where rownum = 1;
    return l_date;
  end;
select b.id
     ,(select get_max_date_sqlwith(b.id) from dual) cdate
  from base_tab b
 where rownum <= 2
 order by b.id
/

 ID CDATE
--- -----------
  1 11-JAN-2010

--*** test 3 with_plsql hint: returns 2 rows without order by *** --

select /*+ with_plsql */ * from 
(with 
  function get_max_date_sqlwith (p_id in number) return date is
    l_date date;
  begin
     select come_date into l_date from (
      select come_date from date_tab t where t.base_id = p_id
       order by come_date desc
       ) where rownum = 1;
    return l_date;
  end;
select b.id, get_max_date_sqlwith(b.id) cdate 
  from base_tab b) x
 where rownum <= 2
--  order by x.id
/

 ID CDATE
--- -----------
  4 14-APR-2040
  1 11-JAN-2010

--*** test 4 with_plsql hint: returns only 1 row with order by *** --

select /*+ with_plsql */ * from 
(with 
  function get_max_date_sqlwith (p_id in number) return date is
    l_date date;
  begin
     select come_date into l_date from (
      select come_date from date_tab t where t.base_id = p_id
       order by come_date desc
       ) where rownum = 1;
    return l_date;
  end;
select b.id, get_max_date_sqlwith(b.id) cdate 
  from base_tab b) x
 where rownum <= 2
 order by x.id
/

 ID CDATE
--- -----------
  1 11-JAN-2010


3. Standalone PL/SQL function



--*** test 5: returns 2 rows without order by *** --

create or replace function get_max_date (p_id in number) return date is
  l_date date;
begin
  select come_date into l_date from (
    select come_date from date_tab where base_id = p_id
     order by come_date desc
     ) where rownum = 1;
  return l_date;
end;
/

select b.id
     ,(select get_max_date(b.id) from dual) cdate
  from base_tab b
 where rownum <= 2
--  order by b.id
/

 ID CDATE
--- -----------
  4 14-APR-2040
  1 11-JAN-2010

--*** test 6: returns 2 rows with order by *** --

select b.id
     ,(select get_max_date(b.id) from dual) cdate
  from base_tab b
 where rownum <= 2
 order by b.id
/

 ID CDATE
--- -----------
  1 11-JAN-2010
  2


4. 12c UDF Pragma PL/SQL function



--*** test 7: returns 2 rows without order by *** --

create or replace function get_max_date_UDF (p_id in number) return date is
  l_date date;
  PRAGMA UDF;
begin
  select come_date into l_date from (
    select come_date from date_tab where base_id = p_id
     order by come_date desc
     ) where rownum = 1;
  return l_date;
end;
/

select b.id
     ,(select get_max_date_UDF(b.id) from dual) cdate
  from base_tab b
 where rownum <= 2
--  order by b.id
/

 ID CDATE
--- -----------
  4 14-APR-2040
  1 11-JAN-2010
  
--*** test 8: returns 2 rows with order by *** --

select b.id
     ,(select get_max_date_UDF(b.id) from dual) cdate
  from base_tab b
 where rownum <= 2
 order by b.id
/

 ID CDATE
--- -----------
  1 11-JAN-2010
  2