Sokrates on Oracle

Yet Another Elementary SQL Bug

Posted by Matthias Rogel on 20. February 2014

Environment

sokrates@12.1 > select banner from v$version;

BANNER
--------------------------------------------------------------------------------
Oracle Database 12c Enterprise Edition Release 12.1.0.1.0 - 64bit Production
PL/SQL Release 12.1.0.1.0 - Production
CORE	12.1.0.1.0	Production
TNS for Linux: Version 12.1.0.1.0 - Production
NLSRTL Version 12.1.0.1.0 - Production

sokrates@12.1 > select value from nls_database_parameters where parameter='NLS_CHARACTERSET';

VALUE
--------------------------------------------------------------------------------
AL32UTF8
sokrates@12.1 > !echo $NLS_LANG
american_america.UTF8

Setup is

create table t ( r varchar2(1 char));
insert into t values('£');
commit;

alter session set nls_length_semantics=BYTE;
alter table t add( v as ( cast(r as varchar2(1))));

alter session set nls_length_semantics=CHAR;
create index t_idx on t(v);

exec dbms_stats.set_table_stats(null, 'T', numrows => 1e7)

We get different result for count(v) when using length-semantics BYTE, depending if a table scan or an index scan is used:

sokrates@12.1 > alter session set nls_length_semantics=BYTE;

Session altered.

sokrates@12.1 > select /* index full scan used */ count(v) from t;

  COUNT(V)
----------
	 1

sokrates@12.1 > select /* full table scan used */ count(*), count(v) from t;

  COUNT(*)   COUNT(V)
---------- ----------
	 1	    0

Same behaviour was observed on 11.2

Posted in 11.2, 12c, Bug, sql | 3 Comments »

How 2 Bytes can fit into 1 Byte

Posted by Matthias Rogel on 20. February 2014

Chris Saxon posted a nice quiz regarding a pitfall when creating tables with VARCHAR2-columns: when you are not explicit in specifying the length-semantics ( CHAR or BYTE ), a session parameter, which may vary, is used.

This is a short follow-up of his story, which shows more pitfalls when creating views and selecting from those.
We end up in a structure which can – by definition – contain only 1 byte, but actually shows containing two bytes !

sokrates@11.2 > alter session set nls_length_semantics=CHAR;

Session altered.

sokrates@11.2 > create table i_can_insert (
  2    into_this varchar2(1)
  3  );

Table created.

sokrates@11.2 > insert into i_can_insert (into_this)
  2  values ('£');

1 row created.

sokrates@11.2 > create view i_can_select (from_that)
  2  as
  3  select cast(into_this as varchar2(1))
  4  from i_can_insert;

View created.

sokrates@11.2 > describe i_can_insert
 Name                                      Null?    Type
 ----------------------------------------- -------- ----------------------------
 INTO_THIS                                          VARCHAR2(1)

sokrates@11.2 > describe i_can_select
 Name                                      Null?    Type
 ----------------------------------------- -------- ----------------------------
 FROM_THAT                                          VARCHAR2(1)

sokrates@11.2 > select * from i_can_select;

FROM_THAT
----------
£

sokrates@11.2 > REM now we change the length semantics ...
sokrates@11.2 > alter session set nls_length_semantics=BYTE;

Session altered.

sokrates@11.2 > REM note that the output of describe changes !
sokrates@11.2 > describe i_can_insert
 Name                                      Null?    Type
 ----------------------------------------- -------- ----------------------------
 INTO_THIS                                          VARCHAR2(1 CHAR)

sokrates@11.2 > describe i_can_select
 Name                                      Null?    Type
 ----------------------------------------- -------- ----------------------------
 FROM_THAT                                          VARCHAR2(1 CHAR)

sokrates@11.2 > create view can_i_select (from_that)
  2  as
  3  select cast(into_this as varchar2(1))
  4  from i_can_insert;

View created.

sokrates@11.2 > describe can_i_select
 Name                                      Null?    Type
 ----------------------------------------- -------- ----------------------------
 FROM_THAT                                          VARCHAR2(1)

sokrates@11.2 > REM now, can I select ?
sokrates@11.2 > set null nada
sokrates@11.2 > select * from can_i_select;

FROM_THAT
----------
nada

sokrates@11.2 > REM no I cannot, output would be too long in my current semantics, so it is trimmed ( nothing left )
sokrates@11.2 >
sokrates@11.2 > REM now, can I select from i_can_select ?
sokrates@11.2 > select * from i_can_select;

FROM_THAT
----------
nada

sokrates@11.2 > REM no
sokrates@11.2 > select * from i_can_insert;

INTO_THIS
----------
£

sokrates@11.2 > REM but still from the table itself, I can select !
sokrates@11.2 >
sokrates@11.2 > REM let us change back the semantics
sokrates@11.2 > alter session set nls_length_semantics=CHAR;

Session altered.

sokrates@11.2 > describe can_i_select
 Name                                      Null?    Type
 ----------------------------------------- -------- ----------------------------
 FROM_THAT                                          VARCHAR2(1 BYTE)

sokrates@11.2 > select * from can_i_select;

FROM_THAT
----------
£

sokrates@11.2 > select lengthb(from_that) from can_i_select;

LENGTHB(FROM_THAT)
------------------
                 2

So, CAN_I_SELECT.FROM_THAT can only hold 1 byte, but the byte-length of its content is 2 !
-> looks like a bug to me.

Note, that USER_VIEWS.TEXT shows the same for both views, even DBMS_METADATA is not explicit , only COLS shows the difference

sokrates@11.2 > select text from user_views where view_name='CAN_I_SELECT';

TEXT
--------------------------------------------------------------------------------
select cast(into_this as varchar2(1))
from i_can_insert


sokrates@11.2 > select text from user_views where view_name='I_CAN_SELECT';

TEXT
--------------------------------------------------------------------------------
select cast(into_this as varchar2(1))
from i_can_insert


sokrates@11.2 > select dbms_metadata.get_ddl('VIEW', 'CAN_I_SELECT') from dual;

DBMS_METADATA.GET_DDL('VIEW','CAN_I_SELECT')
--------------------------------------------------------------------------------

  CREATE OR REPLACE FORCE VIEW "SOKRATES"."CAN_I_SELECT" ("FROM_THAT") AS
  select cast(into_this as varchar2(1))
from i_can_insert


sokrates@11.2 > select dbms_metadata.get_ddl('VIEW', 'I_CAN_SELECT') from dual;

DBMS_METADATA.GET_DDL('VIEW','I_CAN_SELECT')
--------------------------------------------------------------------------------

  CREATE OR REPLACE FORCE VIEW "SOKRATES"."I_CAN_SELECT" ("FROM_THAT") AS
  select cast(into_this as varchar2(1))
from i_can_insert


sokrates@11.2 > select table_name, column_name, data_length from cols where table_name in ('I_CAN_SELECT', 'CAN_I_SELECT');

TABLE_NAME                     COLUMN_NAME                    DATA_LENGTH
------------------------------ ------------------------------ -----------
CAN_I_SELECT                   FROM_THAT                                1
I_CAN_SELECT                   FROM_THAT                                4

Posted in sql | 4 Comments »

A Restriction of the Cardinality Hint

Posted by Matthias Rogel on 17. January 2014

Here is a restriction of the cardinality hint in conjunction with the materialize-hint ( note: both are undocumented but sometimes of great use ):
we cannot tell the optimizer in the outer query ( the one that uses the materialized subquery ) about the cardinality of the materialization, this can only – and then not always – be done within the materializing query.


Update 21/01/2014.
Randolf Geist shows in this comment that this is not true and gives techniques how to achive this.

The example to show that is stolen from Tom Kyte’s Presentation S13961_Best_Practices_for_Managing_Optimizer_Statistics_Short.pptx from ukoug 2013.zip:

sokrates@12.1 > create type str2tbltype is table of varchar2(100);
  2  /

Type created.

sokrates@12.1 > create function str2tbl( p_str in varchar2 ) return str2tblType
  as
  l_str   long default p_str || ',';
  l_n	     number;
  l_data    str2tblType := str2tblType();
  begin
  loop
  l_n := instr( l_str, ',' );
  exit when (nvl(l_n,0) = 0);
 l_data.extend;
 l_data( l_data.count ) := ltrim(rtrim(substr(l_str,1,l_n-1)));
 l_str := substr( l_str, l_n+1 );
 end loop;
 return l_data;
 end;
  2    3    4    5    6    7    8    9   10   11   12   13   14   15   16  /

Function created.

sokrates@12.1 > create table t as select object_id, object_name from dba_objects;

Table created.

sokrates@12.1 > create index t_idx on t( object_name );

Index created.

sokrates@12.1 > exec dbms_stats.gather_table_stats( user, 'T' );

PL/SQL procedure successfully completed.

sokrates@12.1 > variable in_list varchar2(100)
sokrates@12.1 > exec :in_list := 'DBMS_OUTPUT,UTL_FILE,DBMS_PIPE'

PL/SQL procedure successfully completed.

sokrates@12.1 > select count(*) from table(cast( str2tbl( :in_list) as str2tblType) ) t;

  COUNT(*)
----------
	 3

The optimizer does know nothing about the cardinality of this “table(cast( str2tbl( :in_list) as str2tblType) )”.
A clever human could prove that the cardinality of this “table” can never exceed 33.000, so humans sometimes are more clever than the optimizer in estimating cardinalities. In our example, we want to tell the optimizer that the cardinality of this table is approximately 10, which will influence the execution plan of a select which joins this table to a real table.

Without cardinality-hint, the optimizer uses a default cardinality and chooses the wrong hash join:

sokrates@12.1 > with data as
( select *
   from table(cast( str2tbl( :in_list) as str2tblType) ) t
)
select t.object_id, t.object_name
  from data, t
 where t.object_name = data.column_value
  2    3    4    5    6    7    8  /

Execution Plan
----------------------------------------------------------
Plan hash value: 386533642

----------------------------------------------------------------------------------------------
| Id  | Operation			   | Name    | Rows  | Bytes | Cost (%CPU)| Time     |
----------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT		   |	     | 14005 |	 437K|	 154   (1)| 00:00:01 |
|*  1 |  HASH JOIN			   |	     | 14005 |	 437K|	 154   (1)| 00:00:01 |
|   2 |   COLLECTION ITERATOR PICKLER FETCH| STR2TBL |	8168 | 16336 |	  29   (0)| 00:00:01 |
|   3 |   TABLE ACCESS FULL		   | T	     | 90964 |	2664K|	 124   (0)| 00:00:01 |
----------------------------------------------------------------------------------------------

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

   1 - access("T"."OBJECT_NAME"=VALUE(KOKBF$))

When we tell the optimizer via cardinality hint the correct magnitude of the real cardinality, the right access path “nested loops and index” is used

sokrates@12.1 > with data as
( select /*+cardinality(t, 10) */ *
   from table(cast( str2tbl( :in_list) as str2tblType) ) t
)
select t.object_id, t.object_name
  from data, t
 where t.object_name = data.column_value
  2    3    4    5    6    7    8  /

Execution Plan
----------------------------------------------------------
Plan hash value: 2392632293

-----------------------------------------------------------------------------------------------
| Id  | Operation			    | Name    | Rows  | Bytes | Cost (%CPU)| Time     |
-----------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT		    |	      |    17 |   544 |    59	(0)| 00:00:01 |
|   1 |  NESTED LOOPS			    |	      |       |       | 	   |	      |
|   2 |   NESTED LOOPS			    |	      |    17 |   544 |    59	(0)| 00:00:01 |
|   3 |    COLLECTION ITERATOR PICKLER FETCH| STR2TBL |    10 |    20 |    29	(0)| 00:00:01 |
|*  4 |    INDEX RANGE SCAN		    | T_IDX   |     2 |       |     2	(0)| 00:00:01 |
|   5 |   TABLE ACCESS BY INDEX ROWID	    | T       |     2 |    60 |     3	(0)| 00:00:01 |
-----------------------------------------------------------------------------------------------

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

   4 - access("T"."OBJECT_NAME"=VALUE(KOKBF$))

Note, that we can “pull this hint into the outer query” and still get the exact same execution plan ( even the same plan hash value ):

sokrates@12.1 > with data as
( select *
   from table(cast( str2tbl( :in_list) as str2tblType) ) t
)
select /*+cardinality(data, 10) */ t.object_id, t.object_name
  from data, t
 where t.object_name = data.column_value
  2    3    4    5    6    7    8  /

Execution Plan
----------------------------------------------------------
Plan hash value: 2392632293

-----------------------------------------------------------------------------------------------
| Id  | Operation			    | Name    | Rows  | Bytes | Cost (%CPU)| Time     |
-----------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT		    |	      |    17 |   544 |    59	(0)| 00:00:01 |
|   1 |  NESTED LOOPS			    |	      |       |       | 	   |	      |
|   2 |   NESTED LOOPS			    |	      |    17 |   544 |    59	(0)| 00:00:01 |
|   3 |    COLLECTION ITERATOR PICKLER FETCH| STR2TBL |    10 |    20 |    29	(0)| 00:00:01 |
|*  4 |    INDEX RANGE SCAN		    | T_IDX   |     2 |       |     2	(0)| 00:00:01 |
|   5 |   TABLE ACCESS BY INDEX ROWID	    | T       |     2 |    60 |     3	(0)| 00:00:01 |
-----------------------------------------------------------------------------------------------

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

   4 - access("T"."OBJECT_NAME"=VALUE(KOKBF$))

This behaviour changes when we materialize the inner query.
First we hint the cardinality in the materializing query:

sokrates@12.1 > with data as
( select /*+materialize cardinality(t, 10) */*
   from table(cast( str2tbl( :in_list) as str2tblType) ) t
)
select t.object_id, t.object_name
  from data, t
 where t.object_name = data.column_value
  2    3    4    5    6    7    8  /

Execution Plan
----------------------------------------------------------
Plan hash value: 2115576147

-----------------------------------------------------------------------------------------------------------------
| Id  | Operation			    | Name			| Rows	| Bytes | Cost (%CPU)| Time	|
-----------------------------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT		    |				|    17 |  1394 |    61   (0)| 00:00:01 |
|   1 |  TEMP TABLE TRANSFORMATION	    |				|	|	|	     |		|
|   2 |   LOAD AS SELECT		    | SYS_TEMP_0FD9D666D_268859 |	|	|	     |		|
|   3 |    COLLECTION ITERATOR PICKLER FETCH| STR2TBL			|    10 |    20 |    29   (0)| 00:00:01 |
|   4 |   NESTED LOOPS			    |				|	|	|	     |		|
|   5 |    NESTED LOOPS 		    |				|    17 |  1394 |    32   (0)| 00:00:01 |
|   6 |     VIEW			    |				|    10 |   520 |     2   (0)| 00:00:01 |
|   7 |      TABLE ACCESS FULL		    | SYS_TEMP_0FD9D666D_268859 |    10 |    20 |     2   (0)| 00:00:01 |
|*  8 |     INDEX RANGE SCAN		    | T_IDX			|     2 |	|     2   (0)| 00:00:01 |
|   9 |    TABLE ACCESS BY INDEX ROWID	    | T 			|     2 |    60 |     3   (0)| 00:00:01 |
-----------------------------------------------------------------------------------------------------------------

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

   8 - access("T"."OBJECT_NAME"="DATA"."COLUMN_VALUE")

Still the right plan.

This changes when we pull the cardinality-hint into the outer query:

sokrates@12.1 > with data as
( select /*+materialize */ *
   from table(cast( str2tbl( :in_list) as str2tblType) ) t
)
select /*+cardinality(data, 10) */ t.object_id, t.object_name
  from data, t
 where t.object_name = data.column_value
  2    3    4    5    6    7    8  /

Execution Plan
----------------------------------------------------------
Plan hash value: 4042153407

-----------------------------------------------------------------------------------------------------------------
| Id  | Operation			    | Name			| Rows	| Bytes | Cost (%CPU)| Time	|
-----------------------------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT		    |				| 14005 |  1121K|   157   (1)| 00:00:01 |
|   1 |  TEMP TABLE TRANSFORMATION	    |				|	|	|	     |		|
|   2 |   LOAD AS SELECT		    | SYS_TEMP_0FD9D666F_268859 |	|	|	     |		|
|   3 |    COLLECTION ITERATOR PICKLER FETCH| STR2TBL			|  8168 | 16336 |    29   (0)| 00:00:01 |
|*  4 |   HASH JOIN			    |				| 14005 |  1121K|   128   (1)| 00:00:01 |
|   5 |    VIEW 			    |				|  8168 |   414K|     3   (0)| 00:00:01 |
|   6 |     TABLE ACCESS FULL		    | SYS_TEMP_0FD9D666F_268859 |  8168 | 16336 |     3   (0)| 00:00:01 |
|   7 |    TABLE ACCESS FULL		    | T 			| 90964 |  2664K|   124   (0)| 00:00:01 |
-----------------------------------------------------------------------------------------------------------------

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

   4 - access("T"."OBJECT_NAME"="DATA"."COLUMN_VALUE")

The cardinality-hint was ignored and the wrong plan was chosen.

This is a pity because when the materialized subquery is more complicated than in this example ( think of a multi-join where the developer just “knows” the magnitude of the result set for some reason the optimizer cannot be aware of – there are always such cases -), we have no chance in helping the optimizer to find the “right” plan just via cardinality hint.

Posted in sql | 4 Comments »

Issue with updatable views

Posted by Matthias Rogel on 7. January 2014

It’s sometimes amazing, how many bugs there are still with elementary SQL.

Here is one concerning updatable views:

sokrates@12.1 > create table t ( v varchar2(30) );

Table created.

sokrates@12.1 > create view v as
  2  select v as dontdothatman, v as canbelostwheninserted
  3  from t; 

View created.

sokrates@12.1 > insert /* this is fine */ into v 
  2  values('fine', 'fine');

1 row created.

sokrates@12.1 > select * from v;

DONTDOTHATMAN		       CANBELOSTWHENINSERTED
------------------------------ ------------------------------
fine			       fine

sokrates@12.1 > insert /* exception expected because 1st value is lost */ into v
  2  values('this one is lost', 'why isnt that one lost ?');

1 row created.

sokrates@12.1 > select * from v;

DONTDOTHATMAN		       CANBELOSTWHENINSERTED
------------------------------ ------------------------------
fine			       fine
why isnt that one lost ?       why isnt that one lost ?

Posted in 12c, Bug, sql | 4 Comments »

Best Practice in 12c

Posted by Matthias Rogel on 4. December 2013

Since PL/SQL now is closely integrated into SQL, we hence can happily state

sokrates@12.1 > with function bestpractice return varchar2
  2  is
  3  begin
  4     return 'Do not use PL/SQL when it can be done with SQL alone !';
  5  end bestpractice;
  6  select bestpractice() from dual
  7  /

BESTPRACTICE()
--------------------------------------------------------------------------------
Do not use PL/SQL when it can be done with SQL alone !

Posted in 12c, Allgemein, fun, sql | Tagged: | 2 Comments »

Strange ORA-14196

Posted by Matthias Rogel on 7. October 2013

It seems that sometimes you need a non-unique index to enforce a unique constraint even if this constraint is declared as not deferrable.

sokrates@11.2 > create table strange(i int not null, j int not null);

Table created.

sokrates@11.2 > alter table strange add constraint unique_i unique(i) not deferrable
  2  using index ( create unique index struix on strange ( i, j ) )
  3  /
alter table strange add constraint unique_i unique(i) not deferrable
*
ERROR at line 1:
ORA-14196: Specified index cannot be used to enforce the constraint.

WTF ?
We have to create a non-unique index here !

sokrates@11.2 > alter table strange add constraint unique_i unique(i) not deferrable
  2  using index ( create  index struix on strange ( i, j ) )
  3  /

Table altered.

Also reproduced on 12.1.
Who can explain this behaviour to me ( I suppose it is a bug ) ?

Posted in Allgemein | Tagged: | 3 Comments »

Partition Info in V$SESSION_LONGOPS

Posted by Matthias Rogel on 10. May 2013

Oracle’s advanced partitioning has some deficiencies. For example, partition info is missing in V$SESSION_LONGOPS for scan-operations ( full table scans, full index scans ). V$SESSION_LONGOPS.TARGET only shows OWNER.TABLE_NAME in these cases, even when the underlying table/index is partitioned, though the longop doesn’t refer to the whole segment but only to one (sub-)partition of it.
I filed an enhancement request several years ago concerning this matter, but never received any feedback.
However, there is a workaround to that. In many cases, we can find out on which (sub-) partition the longop is working on: V$SESSION_WAIT’s P1- and P2-info can be used for that in case the session is waiting mainly on I/O ( which might be most likely for many systems. )
Here is an extension to V$SESSION_LONGOPS which tries to figure out this additional info.

Update 27/02/2014
Note that the original version has been improved by Jonathan Lewis. I have marked the relevant part with a corresponding comment.
I haven’t observed so far that I wasn’t able to get the partition information from v$session.row_wait_obj# ( as suggested by him ), but from the part marked as “superfluous most likely” ( my original version ). However, I have no proof that this is not possible.

select
   coalesce(
        (
            select 'does not apply'
            from dual
            where slo.TARGET not like '%.%'
            or slo.TARGET is null
        ),
        (
            select 'does not apply'
            from dba_tables dt
            where dt.OWNER=substr(slo.target, 1, instr(slo.target, '.') - 1)
            and dt.TABLE_NAME=substr(slo.target, instr(slo.target, '.') + 1)
            and dt.PARTITIONED='NO'       
        ),
        (
            -- Jonathan Lewis, see http://jonathanlewis.wordpress.com/2014/01/01/nvl-2/#comment-62048
            select
               ob.subobject_name || ' (' || ob.object_type || ')'
            from v$session s, dba_objects ob
            where
              ob.object_id = s.row_wait_obj#
            and s.sid = slo.sid
            and ob.OBJECT_TYPE like '%PARTITION%'            
        ),
        (
            -- superfluous most likely
            select
               de.partition_name || ' (' || de.segment_type || ') NOT SUPERFLUOUS IF YOU SEE THAT'
            from v$session_wait sw, dba_extents de
            where
              sw.sid=slo.sid
            and slo.opname like '%Scan%'
            and sw.P1TEXT like 'file%'
            and sw.P1 = de.FILE_ID and sw.P2 between de.BLOCK_ID and de.BLOCK_ID + de.BLOCKS - 1
            and de.owner = substr(slo.target, 1, instr(slo.target, '.') - 1)
            and de.segment_type in
            (
               'TABLE PARTITION', 'TABLE SUBPARTITION',
               'INDEX PARTITION', 'INDEX SUBPARTITION'            
            )
            and de.segment_name in
            (
                 -- table
                 select
                    substr(slo.target, instr(slo.target, '.') + 1)
                 from dual
                 union all
                 -- index
                 select di.index_name
                 from dba_indexes di
                 where di.owner=substr(slo.target, 1, instr(slo.target, '.') - 1)
                 and di.TABLE_NAME = substr(slo.target, instr(slo.target, '.') + 1)
            )
         ),
        'unknown'
      )
   as partition_info,     
   slo.*
from v$session_longops slo
where slo.TIME_REMAINING > 0

Note that this might take a bit longer than a simple

select slo.*
from v$session_longops slo
where slo.TIME_REMAINING > 0

, though due to coalesce’s short circuiting it is quite efficient.

Posted in Allgemein, sql | Tagged: | Leave a Comment »

(UTL_RAW.)CAST_TO_DATE

Posted by Matthias Rogel on 29. April 2013

Tim wrote
… the UTL_RAW package has a bunch of casting functions for RAW values (CAST_TO_BINARY_DOUBLE, CAST_TO_BINARY_FLOAT, CAST_TO_BINARY_INTEGER, CAST_TO_NUMBER, CAST_TO_NVARCHAR2, CAST_TO_VARCHAR2). Note the absence of a CAST_TO_DATE function.

Bertrand Drouvot also misses it, see Bind variable peeking: Retrieve peeked and passed values per execution in oracle 11.2

Here is a try to write one, fixes and improvements are welcome !

create or replace function CAST_TO_DATE(bdr in raw) return date deterministic is
begin
  return
     date'1-1-1'
     + NUMTOYMINTERVAL(
         100 * (to_number(substr(bdr,1,2), 'xx') - 100) + 
         to_number(substr(bdr,3,2), 'xx') - 101, 
       'year')
     + NUMTOYMINTERVAL(to_number(substr(bdr,5,2), 'xx')-1, 'month')
     + NUMTODSINTERVAL(to_number(substr(bdr,7,2), 'xx')-1, 'day')
     + NUMTODSINTERVAL(to_number(substr(bdr,9,2), 'xx') - 1, 'hour')   
     + NUMTODSINTERVAL(to_number(substr(bdr,11,2), 'xx') - 1, 'minute')   
     + NUMTODSINTERVAL(to_number(substr(bdr,13,2), 'xx') - 1, 'second');
  exception when others then return to_date(1, 'J');
end CAST_TO_DATE;   
/

Posted in Allgemein | Tagged: | 4 Comments »

A simple pipelined version of print_table

Posted by Matthias Rogel on 10. April 2013

Tom Kyte’s print_table procedure, available on
http://asktom.oracle.com/pls/apex/f?p=100:11:0::::P11_QUESTION_ID:1035431863958#14442395195806
seems to be very popular and there exist tricky variations on the theme, for example the following nice xml-trick by Sayan Malakshinov.

Please note that it is very easy to use the existing print_table-code to generate a pipelined version which can be used in SQL.
I use the following code since ages and it always does me a great job, so probably it is worth sharing.

create or replace function fprint_table
( p_query in varchar2,
  p_date_fmt in varchar2 default 'dd-mon-yyyy hh24:mi:ss' )
return sys.odcivarchar2list 
authid current_user
pipelined
   is
l varchar2(4000);
s integer default 1;
begin
  dbms_output.enable(buffer_size => null);
  
  print_table(
     p_query => p_query,
     p_date_fmt => p_date_fmt
  );

  loop
     dbms_output.get_line(line => l, status => s);
     exit when s != 0;
     begin
        pipe row(l);
     exception when no_data_needed then exit;
     end;
  end loop;
    
  return;

end fprint_table;
/

sokrates@11.2 > select * from table(fprint_table('select user,sysdate from dual'));

USER                          : SOKRATES
SYSDATE                       : 10-apr-2013 12:27:50
-----------------
1 row selected.

Posted in Allgemein | Tagged: | Leave a Comment »

An undocumented restriction in Workspace Manager – exporting tables with valid time support

Posted by Matthias Rogel on 7. February 2013

If you are using Workspace Manager, it could be probably useful to know, that there is an undocumented restriction concerning import/export.
Due to Import and Export Considerations,
…Workspace Manager supports the import and export of version-enabled tables in one of the following two ways: a full database import and export, and a workspace-level import and export through Workspace Manager procedures. No other export modes, such as schema, table, or partition level, are currently supported….

However, this does not hold for tables with valid time support:

sokrates@11.2 > CREATE TABLE d (id NUMBER PRIMARY KEY);

Table created.

sokrates@11.2 > EXECUTE DBMS_WM.EnableVersioning (table_name=>'D', validTime=>TRUE, hist => 'NONE');

PL/SQL procedure successfully completed.

sokrates@11.2 >  EXECUTE DBMS_WM.Export(table_name => 'D',staging_table => 'D_STG', workspace => 'LIVE');
BEGIN DBMS_WM.Export(table_name => 'D',staging_table => 'D_STG', workspace => 'LIVE'); END;

*
ERROR at line 1:
ORA-20171: WM error: Export not supported on a table with valid time
ORA-06512: at "WMSYS.LT", line 13185
ORA-06512: at line 1

Support confirmed, that in this case only full db import/export (!) is supported, documentation would be updated somewhen.

Posted in Allgemein | Tagged: , , | Leave a Comment »

 
Follow

Get every new post delivered to your Inbox.