Showing posts with label pl/sql. Show all posts
Showing posts with label pl/sql. Show all posts

Thursday, January 05, 2012

Oracle - SQL - Evenly dividing a range of Data using NTILE, MIN, and MAX

Problem: Over a widely dispersed set of IDs, create 10 evenly divided ranges each in terms of count per group. Determine the ID range for each group.
Practical: We needed to scrub a bunch of data in parallel streams. The easist answer was to create 10 simultaneous UPDATEs, but we didn't want the updates stepping on each other.
Solution: Use NTILE to create 10 evenly "divided" temporary groups [from a 'rowcount' standpoint]. Use MIN and MAX against each temporary group to determine the starting and ending points. Query:

SELECT 
   Quartile,
   MIN("ID") "StartingID",
   MAX("ID") "EndingID",
   COUNT(*) "CountPerGroup"
FROM
   ( 
      SELECT 
         a.contract_payment_id "ID",
         NTILE(10) OVER(ORDER BY a.contract_payment_id) AS quartile
      FROM   
         contract_payment a 
      WHERE
         a.transaction_type_code = 'CC' and 
         a.credit_card_type_code = 'VISA'
         and rownum <= 10000  -- Reduce resultset
 ) 
GROUP BY 
   quartile
ORDER BY 
   1;
Results:
QUARTILE  StartingID  EndingID  CountPerGroup
    1        59261      93271       1000
    2        93281     110492       1000
    3       110512     122382       1000
    4       122391     134062       1000
    5       134072     144371       1000
    6       144372     154481       1000
    7       154482     162741       1000
    8       162752     169163       1000
    9       169173     176913       1000
   10       176921     183833       1000

Wednesday, August 17, 2011

Compiling PL/SQL and Recompiling Dependent Objects

set echo on;
SELECT * FROM user_objects WHERE status = 'INVALID';
set echo off;

Prompt 
Prompt Compiling c:\dispatch_data_obj_body.sql
@c:\dispatch_data_obj_body.sql

Prompt 
Prompt Attempting to recompile any invalid objects 
exec sys.UTL_RECOMP.recomp_serial(schema => user);

set echo on;
Prompt 
SELECT * FROM user_objects WHERE status = 'INVALID';
set echo off;

Thursday, June 09, 2011

Burning an Oracle Sequence value to raise it above Table Max

We have a log table that we use to track miscellaneous behaviors via some triggers for "audit" purposes. The design is fairly simple... on the occurrence of a "monitoring" behavior, INSERT a row into the AUDIT_LOG table. The INSERT grabs a new sequence value and poof, we're good.

Well, not always. Periodically, the MAX table value exceeds the sequence value. I don't know why/how it happens, but it causes our applications to start throwing errors [as expected]. I wrote a simple query to get the sequence value back above the table ID maximum.

Note: you may need to establish the "currval" of the session by burning a "nextval".

select
	-- audit_seq.nextval "Sequence NextVal",
	audit_seq.currval "Sequence CurrVal",
	(select max(AUDIT_LOG.SEQ_NO) from AUDIT_LOG) "Table Max ID",
	case
		when audit_seq.currval >= (select max(AUDIT_LOG.SEQ_NO) from AUDIT_LOG)
			then 'Sequence good'

		when audit_seq.currval < (select max(AUDIT_LOG.SEQ_NO) from AUDIT_LOG)
			then 'Sequence bad, Table Higher by '||( (select max(AUDIT_LOG.SEQ_NO) from AUDIT_LOG) - audit_seq.nextval)
		else
			'wtf'
	end "Sequence Status"
from
	dual

You may want to reference: http://download.oracle.com/docs/cd/B19306_01/server.102/b14200/pseudocolumns002.htm#sthref806

ORA-02287: sequence number not allowed here

Thursday, September 02, 2010

PL/SQL Date Calculations

Recently, I needed to do some Oracle PL/SQL / SQL Date range manipulation. I thought that I would share what I used after a quick web search returns some overly verbose results based on conversions using TO_CHAR.
Queries:

SELECT 
   SYSDATE "Today",
   TRUNC(SYSDATE) "Start of Day",
   TRUNC(SYSDATE+1) - 1/(24*60*60) "End of Day (EOD)",
   TRUNC(SYSDATE,'MM') "First Day of the Month",
   TRUNC(SYSDATE,'YEAR') "First Day of the Year"
FROM 
   DUAL
Results: (Formated for readability)

Today                   9/2/2010 5:17:51 PM
Start of Day            9/2/2010
End of Day              9/2/2010 11:59:59 PM
First Day of the Month  9/1/2010
First Day of the Year   1/1/2010

Tuesday, April 14, 2009

PL/SQL Cursor For Loop with Bulk Collect and Limit

Here is an cursor example of how to use a BULK COLLECT with a LIMIT clause. DevNote: %NOTFOUND check must be at the end of the loop

declare

   PROCEDURE bulk_with_limit (
      n_start_in     IN    CONTRACT.CONTRACT_ID%TYPE,
      n_end_in       IN    CONTRACT.CONTRACT_ID%TYPE,
      limit_in       IN    PLS_INTEGER := 100
   )
   IS
      CURSOR contracts_cur
      IS
         SELECT *
         FROM contract
         WHERE contract_id between n_start_in and n_end_in;

      TYPE contract_trec IS TABLE OF contracts_cur%ROWTYPE INDEX BY PLS_INTEGER;

      l_contracts   contract_trec;

   BEGIN
      OPEN contracts_cur;

      LOOP
         FETCH contracts_cur
            BULK COLLECT 
            INTO l_contracts 
            LIMIT limit_in;

         -- Perform business logic ... 
         FOR i IN 1 .. l_contracts.COUNT LOOP
            dbms_output.put_line(lpad(' ',5 - length(i))||i||' : '||l_contracts(i).contract_id);
         END LOOP;

         -- DevNote: %NOTFOUND check must be at the end of the loop 
         EXIT WHEN contracts_cur%NOTFOUND;

      END LOOP;

      CLOSE contracts_cur;
   END bulk_with_limit;

begin
   bulk_with_limit(n_start_in => 10, n_end_in => 100000, limit_in => 100);
end;

Thursday, April 10, 2008

Feuerstein's article on BULK COLLECT and %NOTFOUND

Steven Feuerstien wrote an informative article (http://www.oracle.com/technology/oramag/oracle/08-mar/o28plsql.html) on using BULK COLLECT with LIMIT in a "reasonable" manner. Much of this content is drawn from that article. Basically, the use the LIMIT construct requires a secondary loop to process the smaller "batch". Our DBAs require us to do this kind of "batching" to put less strain on Replication. Smaller COMMITs going across the pipe rather than a 100,000 COMMIT. In otherwords, "commit every x number of rows." [Search engine fodder]

PROCEDURE process_all_rows (limit_in IN PLS_INTEGER DEFAULT 100)
IS
   CURSOR employees_cur
   IS
       SELECT * FROM employees;

   TYPE employees_aat IS TABLE OF employees_cur%ROWTYPE
       INDEX BY PLS_INTEGER;

   l_employees employees_aat;
BEGIN  
   OPEN employees_cur;
   LOOP
       FETCH employees_cur
           BULK COLLECT INTO l_employees LIMIT limit_in;

       FOR indx IN 1 .. l_employees.COUNT LOOP
           update_compensation (l_employees(indx));
       END LOOP;

       COMMIT; -- Commit batch of size LIMIT 

       EXIT WHEN l_employees.COUNT < limit_in;

   END LOOP;

   CLOSE employees_cur;
END process_all_rows;
To summarize his second article, don't use %NOTFOUND with BULK COLLECT and LIMIT. Use .COUNT. Secondary Example
set serveroutput on;
clear;

declare
  CURSOR contracts_cur
   IS
       SELECT * FROM contract where rownum <= 50;

   TYPE t_contract IS TABLE OF contracts_cur%ROWTYPE
       INDEX BY PLS_INTEGER;

   l_contract t_contract;
   
BEGIN  
   OPEN contracts_cur;
   LOOP
       FETCH contracts_cur
           BULK COLLECT INTO l_contract LIMIT 10;

       FOR indx IN 1 .. l_contract.COUNT LOOP
        common_func.display_output(indx||' : '||l_contract(indx).contract_id);
       END LOOP;

       EXIT WHEN l_contract.COUNT = 0;

   END LOOP;

   CLOSE contracts_cur;
   
   EXCEPTION 
    WHEN OTHERS THEN 
       common_func.display_output(SQLERRM);
   
END;

Thursday, March 20, 2008

Handling ORA-04068: Existing state of packages has been discarded

How to deal with the frustrating ORA-04068: existing state of packages has been discarded error that crops up while in the circular cycle of "code..execute..debug".

ORA-04068: existing state of packages has been discarded
ORA-04061: existing state of package "PKG.PROC" has been invalidated
ORA-04065: not executed, altered or dropped package "PKG.PROC"
ORA-06508: PL/SQL: could not find program unit being called
Previously, the only thing I knew to do was logout and then log back in. This works, but it is kind of a waste of time. I knew there had to be a better way because my PL/SQL Editor (PL/SQL Developer) doesn't require this step. I probably could have asked the guys at AllAround, but I think that I found the answer:
begin
   DBMS_SESSION.RESET_PACKAGE;
   -- execute routine... 
end;
Addendum:
To respond to Abida's comment, you can run into this issue even if all the packages under your ID are valid. Your debug/test session however may not "see" the valid state of these packages. The snippet above fixes the issue with our debug/test session. Thanks, Jason

Tuesday, January 15, 2008

Upsert / Merge within the Same Table (Oracle)

Okay, I'm a huge fan of using Oracle's %ROWTYPE to define records structures. I love writing code like the following:

DECLARE

   rec_contract  CONTRACT%ROWTYPE := Null;

BEGIN

   -- ... retrieval logic and business logic ...

   -- "Set" logic
   UPDATE CONTRACT
   SET ROW = rec_contract
   WHERE CONTRACT_ID = rec_contract.CONTRACT_ID;

   IF (SQL%ROWCOUNT = 0) THEN
      INSERT INTO CONTRACT
      VALUES rec_contract;
   END IF;
END;
Code like the above is really convenient for writing "setters". It can easily be extended to handle DELETE logic too. Now, recently I needed to copy a bunch of data within the same table but with a different primary key. I started to write a bunch of code like the above, but I just needed some of code. To cut to the chase, the code below is an example of how to do an "UPSERT" (like a MERGE) but within the same table [which is impossible with the MERGE command]. Source:
drop table test;
clear;

create table test(
   id number(10),
 name varchar2(50)
);

insert into test(id,name) values (1,'First row');

insert into test
   using select 2,'Row two' from dual;

insert into test
   using select 3,'Row three - unmodified' from dual;

commit;

select * from test;

update test set (id,name) = (select 3,'Row three - modified' from test where id = 3) where id = 3;

commit;

select * from test;
Results:
Table created
 
1 row inserted
 
1 row inserted
 
1 row inserted
 
Commit complete
 
         ID NAME
----------- --------------------------------------------------
          1 First row
          2 Row two
          3 Row three - unmodified
 
1 row updated
 
Commit complete
 
         ID NAME
----------- --------------------------------------------------
          1 First row
          2 Row two
          3 Row three - modified

Monday, November 27, 2006

PL/SQL : Example using SysTimeStamp (milliseconds for SysDate)

Oracle's SYSDATE only goes to the second. If you want more precision, then you need to use the new SYSTIMESTAMP (available starting in Oracle 9). Source :

declare

ltimestamp_start       timestamp;
ltimestamp_stop        timestamp;
linterval_diff         interval day to second;

ldt_temp               date;

begin

ltimestamp_start := systimestamp;
dbms_output.put_line(ltimestamp_start);

  -- Routine to performance test... 
  for i in 1 .. 10000 loop
    select sysdate into ldt_temp from dual;
  end loop;

ltimestamp_stop := systimestamp;
dbms_output.put_line(ltimestamp_stop);

linterval_diff := ltimestamp_stop - ltimestamp_start;

dbms_output.put_line(CHR(10)||LPAD('=',22,'=')||CHR(10));
dbms_output.put_line('  Runtime Difference'||CHR(10)||LPAD('=',22,'='));
dbms_output.put_line(
     '     Days : '||EXTRACT(DAY FROM linterval_diff)||CHR(10)||
     '    Hours : '||EXTRACT(HOUR FROM linterval_diff)||CHR(10)||
     '  Minutes : '||EXTRACT(MINUTE FROM linterval_diff)||CHR(10)||
     '  Seconds : '||EXTRACT(SECOND FROM linterval_diff) );
end;
Results :
27-NOV-06 01.26.50.118424 PM
27-NOV-06 01.26.51.125141 PM

======================
Runtime Difference
======================
Days : 0
Hours : 0
Minutes : 0
Seconds : 1.006717

PL/SQL: Joinrows - Convert multiple [returned cursor] rows into one row

This routine converts multiple rows into a single row. The trick is to pass the data via a SYS_REFCURSOR (see the CURSOR call in the example). Example Usage :

select joinrows(cursor(select contract_id from contract where rownum < 10 )) from dual;
CREATE OR REPLACE FUNCTION joinrows(
 sysref_cursor_in      sys_refcursor,
 s_delimiter_in        VARCHAR2 := ',' )
RETURN VARCHAR2
IS
   s_value            VARCHAR2(32767) := Null;
   s_result           VARCHAR2(32767) := Null;
   s_delimiter        VARCHAR2(100) := NVL(s_delimiter_in,',');
BEGIN

   LOOP
      FETCH sysref_cursor_in
         INTO s_value;

      EXIT WHEN sysref_cursor_in%NOTFOUND;

      IF (s_result IS NOT NULL) THEN
       s_result := s_result||s_delimiter;
      END IF;

      s_result := s_result||s_value;

   END LOOP;

   RETURN s_result;

END joinrows;

Results:

0,2,3,9,37,404,818,991,1178

Tuesday, November 21, 2006

Ruby: Invoking a PL/SQL Package with Array args

I'm fairly new to Ruby (and subsequently to Rails). I would be normally be labeled an "Enterprise Developer"... yada, yada. Anyway, I'm trying to invoke a Oracle PL/SQL Package from Ruby that has IN and OUT arguments that are arrays (TABLE OF VARCHAR2 INDEX BY BINARY-INTEGER, in Oracle PL/SQL terms). We have a fairly extensive library of PL/SQL that I want to reuse. This technique also works with Oracle Types (CREATE OR REPLACE TYPE PROD_TYPES.TYPE_STRING_ARRAY AS TABLE OF VARCHAR2(2000);).

Related Posts :

I've spent several weeks trying to come up with a solution. First, the best solution would be for Oracle to write the OCI driver for Ruby. OCI8 [http://rubyforge.org/projects/ruby-oci8/] (by Kubo Takehiro) is wonderful, but he is still a person. For companies to adopt and support Ruby/Rails, the support needs to be more robust. Upper Management and Operations resist open source. Single person supported software is easier for them to dismiss. I would prefer that Oracle had an OCI expert write and maintain the library (along the lines of their support for PHP). If this were the case, then the driver could support returning arrays natively.

My PL/SQL Packages :

CREATE OR REPLACE PACKAGE common_func IS
TYPE string_table IS TABLE OF VARCHAR2(2000) INDEX BY BINARY_INTEGER;
END common_func;
/
CREATE OR REPLACE PACKAGE BODY common_func
IS
BEGIN
   NULL;
END common_func;
/
create or replace package ruby_test is

function f_ruby(s in number,t out varchar2,st out common_func.string_table)

end ruby_test;
/
create or replace package body ruby_test is

function f_ruby(s in number,t out varchar2,st out common_func.string_table)
return varchar2
is
begin
   t := 'outta here';
   st(1) := 'array 1';
   st(2) := 'array 2';
   return 'Ruby rocks '||TO_CHAR(NVL(s,5))||' times!';
end;

begin
   null;
end ruby_test;
Desired Ruby :
cursor = conn.parse("BEGIN :result := ruby_test.f_ruby(s => :in,t => :out,st => st); END;")

cursor.bind_param(':result', nil, String, 100)
cursor.bind_param(':in', 10)
cursor.bind_param(':out', nil, String, 100)
# cursor.bind_param(':out_array', Array, 100) <= I tried this too!
cursor.bind_param(':out_array', String[], 100)

cursor.exec()

p cursor[':result'] # => 'Ruby rocks 10 times!'
p cursor[':out'] # => 'outta here'
p cursor[':out_array'] # => 'st(1) = array 1, st(2) = array 2' !!! Fails
First Solution : Convert the array to a string, bind to that string, and then split the delimited string apart on the Ruby side. I don't care for this solution for a couple of reasons :
  • Size issue at 32K
  • What delimiter should I use? How do I know that it will be the "right" delimiter?
First Solution (Revised) : I could switch from a string bind argument to a CLOB. OCI8 supports CLOBs, but I couldn't get it to work. The documentation is incomplete, and I'm not fluent enough in Ruby. Hints back to my issue with the library being solely maintained.

Current Solution : Convert the string array to a reference cursor (SYS_REFCURSOR) and use OCI8's bind to OCI::CURSOR support. Type :

CREATE OR REPLACE TYPE PROD_TYPES.TYPE_STRING_ARRAY AS TABLE OF VARCHAR2(2000)
Cursor Package :
CREATE OR REPLACE PACKAGE cursor_func IS
/**
Converts an array into a SYS_REFCURSOR (System Reference Cursor)

@Return
{*} SysRefCursor Success
{*} Exception Error
*/
FUNCTION f_array_to_SYSREFCURSOR(
   st_array_in       IN   common_func.STRING_TABLE )
RETURN SYS_REFCURSOR;
PRAGMA RESTRICT_REFERENCES(f_array_to_sysrefcursor,WNDS,TRUST);

END cursor_func;
/
CREATE OR REPLACE PACKAGE BODY cursor_func IS

/**
Converts an array into a SYS_REFCURSOR (System Reference Cursor)

@Return
{*} SysRefCursor Success
{*} Exception Error
*/
FUNCTION f_array_to_SYSREFCURSOR(
   st_array_in       IN   common_func.STRING_TABLE )
RETURN SYS_REFCURSOR
IS
   lsysrefcursor_array  SYS_REFCURSOR;
   le_error      EXCEPTION;

   lst_prodtypes    prod_types.type_string_array;

BEGIN

   lst_prodtypes := common_func.convert_table(st_array_in);

  OPEN lsysrefcursor_array FOR
     SELECT column_value FROM TABLE(cast(lst_prodtypes AS prod_types.type_string_array));

   RETURN lsysrefcursor_array;

END f_array_to_SYSREFCURSOR;

BEGIN
   Null;
END cursor_func;
Ruby Source :
plsql = conn.parse(
"DECLARE "+
"   st common_func.string_table; " +
"BEGIN "+
"   :result := ruby_test.f_ruby(s => :in,t => :out, st => st); " +
"   :cst := cursor_func.f_array_to_SYSREFCURSOR(st_array_in => st); " +
"END;")

plsql.bind_param(':result', nil, String, 100)
plsql.bind_param(':in', 10)
plsql.bind_param(':out', nil, String, 100)
plsql.bind_param(':cst', OCI8::Cursor)

plsql.exec()

puts "\nResults from returning a SysRefCursor\n"

p plsql[':result'] # => 'Ruby rocks 10 times!'
p plsql[':out'] # => 'outta here'

cursor = plsql[':cst']

plsql.close

x = ''
while r = cursor.fetch()
x = x + r.join(', ') + "\n"
end
cursor.close() # <= Don't forgot this

puts x
And success, finally!
Results from returning a SysRefCursor
"Ruby rocks 10 times"
"outta here"
array 1
array 2
Warning : There are two potential gotchas to this solution :
  • If the developer forgets the cursor.close statement, the transaction could be jeopardized if there are too many cursors opened. I don't remember which ORA-##### this is.
  • A single database session could run into issues with just having too many reference cursors open at any one instant.
Native support for arrays would be a blessing.

Posts of Interest :

Friday, November 03, 2006

Oracle: SQL - Converting one row into two rows [multiple rows]

Problem : A single row needs to be converted into two rows.

Solution 1 :

Prerequite :

CREATE OR REPLACE TYPE PROD_TYPES.TYPE_STRING_ARRAY AS TABLE OF VARCHAR2(2000)
Function : This routine handles an unlimited number of desired rows as opposed to Solution 2.
CREATE OR REPLACE FUNCTION split (
 s_delimited_list_in     VARCHAR2,
 s_delimiter_in          VARCHAR2 := ',')
RETURN prod_types.type_string_array PIPELINED
IS
/*
@Usage Example:
select * from table(split('one,two,three'));
*/
 l_idx               PLS_INTEGER;
 l_list              VARCHAR2(32767) := s_delimited_list_in;
 l_value             VARCHAR2(32767);
 ls_delimiter        VARCHAR2(100) := NVL(s_delimiter_in,',');
BEGIN
LOOP
 l_idx := INSTR(l_list,ls_delimiter);

 IF (l_idx > 0) THEN

    PIPE ROW(SUBSTR(l_list,1,l_idx-1));
    l_list := SUBSTR(l_list,l_idx+LENGTH(ls_delimiter));
  
 ELSE

    PIPE ROW(l_list);
    EXIT;
  
 END IF;
END LOOP;

RETURN;

END SPLIT;

Sorry, there was a typo in the above script. It was fixed on 2/9/2009. Jason

The reverse of "split" is "joinrows".

Solution 2 :

Here is a simple example of converting one row into multiple rows. "atomic_contract" is returning a single row, but we need two columns from it to be returned as two rows.

select *
from (
select ac.*,
decode(t.row_count,1,ac.sk_seller,
                  2,ac.sk_buyer, null) sk_customer
from atomic_contract ac,
( select 1 row_count from dual
union all
select 2 row_count from dual) t
where contract_id = 3562777)

Tuesday, October 10, 2006

Oracle VARRAY Example

(Revised 09/20/2010, I've revised this example to be more pertinent.)
VARRAYs provide the interesting ability to store multiple values in a single column. Note, "interesting" doesn't mean I'm necessarily encouraging its use. I expect there are some potentially significant performance penalties using VARRAYs.

set serveroutput on;

column id FORMAT A5;
column url FORMAT A20;
column Tag(s) FORMAT A20;

clear;

-- Create a VARRAY that can hold 10 "objects" [cells] of type VARCHAR2 (i.e. 10 Tags of up to 50 characters)
create or replace type vcarray_tags as VARRAY(10) OF VARCHAR2(50);
/

-- Each URL is stored in a single row regardless of the number of tags
create table site_tags (id number, url varchar2(100), tag_list vcarray_tags);

insert into site_tags values (1, 'www.bing.com', vcarray_tags('search', 'microsoft'));
insert into site_tags values (2, 'www.google.com', vcarray_tags('search', 'google'));
commit;

set echo on;

-- Note, The VARRAY column is of type "object"
select 
   *
from
   site_tags;

-- Display each tag on a separate row 
select 
   site_tags.id, 
   site_tags.url,
   tags.column_value "Tag(s)"
from
   site_tags,
   table(site_tags.tag_list) tags;

-- Show all the URLs "tagged" with 'search'
select 
   site_tags.id, 
   site_tags.url,
   tags.column_value "Tag(s)"
from
   site_tags,
   table(site_tags.tag_list) tags
where
   tags.column_value = 'search';
/
-- PL/SQL Example 
begin
   for c in (select * from site_tags) loop
      dbms_output.put_line(c.id||' : '||c.url);
      for i in c.tag_list.first .. c.tag_list.last loop
         dbms_output.put_line('      Tag: '||c.tag_list(i));
      end loop;
   end loop;
end;
Results:
create or replace type vcarray_tags as VARRAY(10) OF VARCHAR2(50);
/
Type created
create table site_tags (id number, url varchar2(100), tag_list vcarray_tags);
 
Table created
insert into site_tags values (1, 'www.bing.com', vcarray_tags('search', 'microsoft'));
 
1 row inserted
insert into site_tags values (2, 'www.google.com', vcarray_tags('search', 'google'));
 
1 row inserted
commit;
 
Commit complete
set echo on;

select
 *
from
 site_tags;
 
   ID URL                  TAG_LIST
----- -------------------- --------
    1 www.bing.com         <Object>
    2 www.google.com       <Object>

select
 site_tags.id,
 site_tags.url,
 tags.column_value "Tag(s)"
from
 site_tags,
 table(site_tags.tag_list) tags;
 
   ID URL                  Tag(s)
----- -------------------- --------------------
    1 www.bing.com         search
    1 www.bing.com         microsoft
    2 www.google.com       search
    2 www.google.com       google

select
 site_tags.id,
 site_tags.url,
 tags.column_value "Tag(s)"
from
 site_tags,
 table(site_tags.tag_list) tags
where
 tags.column_value = 'search';
 
   ID URL                  Tag(s)
----- -------------------- --------------------
    1 www.bing.com         search
    2 www.google.com       search

begin
 for c in (select * from site_tags) loop
  dbms_output.put_line(c.id||' : '||c.url);
  for i in c.tag_list.first .. c.tag_list.last loop
   dbms_output.put_line('      Tag: '||c.tag_list(i));
  end loop;
 end loop;
end;
/
 
1 : www.bing.com
      Tag: search
      Tag: microsoft
2 : www.google.com
      Tag: search
      Tag: google

Tuesday, September 05, 2006

Oracle ORA-03117: two-task save area overflow

Feel free to skip to the bottom and read the "Update"...

Recently, I made some changes in Development to an Oracle Package. The package compiled cleanly and all the unit tests passed successfully.

However upon testing in one of our client applications, we started getting an "ORA-03117: two-task save area overflow" error. It is similar to an ORA-00600 error, useless to us, and usually results in Oracle saying "upgrade to x" [Oracle 10g, in this case].

What is particularly distressing about this particular case is that the Powerbuilder code errors at the package level. The changes were not even being referenced directly by PowerBuilder application.


Update I've discovered that I could recreate this error solely within SQL-Plus by "desc <complex packagename>". If the "complex" package has a reasonable number of nested records/types, you can get this error.

DESC DBMS_METADATA;
We were using either Oracle 8 or Oracle 9 SQL-Net clients for the communication layer. If you are using the 10.2.x SQL-Net client, you don't encounter an error. Upgrading to a newer version of SQL-Net fixes this problem. It is NOT an application code issue.

Monday, September 04, 2006

Oracle and getting Milliseconds from Sysdate... [Systimestamp]

First, you can't.

Sysdate doesn't support going to milliseconds. However, starting with Oracle 9, you can getting milliseconds from another system source : SYSTIMESTAMP.
SELECT to_char(sysdate, 'HH24:MI:SS'), to_char(systimestamp, 'HH24:MI:SS.FF6') FROM dual;