Showing posts with label PL/SQL. Show all posts
Showing posts with label PL/SQL. Show all posts

Friday, September 06, 2013

Find just one set of columns that uniquely identify a row

If a table has PK, we need to show the list of PK columns. If the table does not have PK, we need to see if we can find one set of columns that can uniquely identify the row. One of my colleagues wrote a PL/SQL script to look at dba_constraints to do that. But I have learned that tables could have unique indexes but don't have unique constraints; on the other hand, if we create a unique constraint, we will have a unique index. For the following test tables t1,t2,t3,t4,t5:


create table t1(c1 number, c2 number, c3 number);
alter table t1 add constraint t1_pk primary key (c1, c2);

create table t2(c1 number, c2 number, c3 number);
alter table t2 add constraint t2_uk unique (c1, c2);


create table t3(c1 number, c2 number, c3 number);
create unique index t3_ix on t3(c1,c2)

create table t4(c1 number, c2 number, c3 number);
create unique index t4_ix1 on t4(c1,c2);
create unique index t4_ix2 on t4(c1,c3);

create table t5(c1 number, c2 number, c3 number);


The original script will output something like:

T5 does not have PK or any unique cols
T4 does not have PK or any unique cols
T3 does not have PK or any unique cols
T2 does not have PK but has unique cols
T2 does not have PK but has unique cols such as C1,C2
T1 has PK
T1 PK cols : C1,C2

You can see that the statements about T3 and T4 are not correct, as they have unique indexes. Below is my modified script to look at dba_indexes in stead of dba_constraints.



-- List PK columns or unique columns
-- find PK columns and if no PK, find one of the unique columns
---


set serveroutput on

declare
cntpk number;
cntuk number;
initial number;
pk varchar2(1000);
uk varchar2(1000);

-- cursor tab_cur is  select upper(source_owner) source_owner,upper(source_object_name) source_object_name from dbscrub.config_table;

cursor tab_cur is  select upper(owner) source_owner,upper(table_name) source_object_name from dba_tables 
 where owner='Vxxxx' and table_name in ('T1','T2','T3','T4', 'T5');

begin

  for tab_rec in tab_cur
  loop

  pk :='';
  uk :='';
  initial := 1;

  select count(1) into cntpk from dba_constraints where owner=tab_rec.source_owner and table_name=tab_rec.source_object_name and constraint_type = 'P';
  -- select count(1) into cntuk from dba_constraints where owner=tab_rec.source_owner and table_name=tab_rec.source_object_name and constraint_type = 'U';
  select count(1) into cntuk from dba_indexes where owner=tab_rec.source_owner and table_name=tab_rec.source_object_name and uniqueness = 'UNIQUE';

  if cntpk != 0  then

    -- update dbscrub.config_table set has_pk_uk='YES' where source_owner=tab_rec.source_owner and source_object_name=tab_rec.source_object_name;
    dbms_output.put_line ( tab_rec.source_object_name  || ' has PK' );

    for i in (SELECT b.table_name, b.column_name, b.position
 FROM dba_constraints a, dba_cons_columns b
       WHERE     a.owner = b.owner
      AND a.constraint_name = b.constraint_name
      AND constraint_type = 'P'
       AND b.owner = tab_rec.source_owner
       AND b.table_name = tab_rec.source_object_name
       ORDER BY b.position)

    loop
    if initial=1 then
      pk:=i.column_name;
      initial:=initial+1;
    else
      pk:=pk||','||i.column_name;
    end if;
    end loop;

    -- update dbscrub.config_table set pk_uk_columns=pk where source_owner=tab_rec.source_owner and source_object_name=tab_rec.source_object_name;
    dbms_output.put_line ( tab_rec.source_object_name  || ' PK cols : ' || pk );

  elsif cntpk = 0 and cntuk != 0 then

   --  update dbscrub.config_table set has_pk_uk='YES' where source_owner=tab_rec.source_owner and source_object_name=tab_rec.source_object_name;
    dbms_output.put_line ( tab_rec.source_object_name  || ' does not have PK but has unique cols' );

    /*  -- seems not correct
  for i in (SELECT b.table_name, b.column_name, b.position
      FROM dba_constraints a, dba_cons_columns b
     WHERE     a.owner = b.owner
    AND a.constraint_name = b.constraint_name
    AND constraint_type = 'U'
     AND b.owner = tab_rec.source_owner
     AND b.table_name = tab_rec.source_object_name
       AND rownum = 1
     ORDER BY b.position)
   */
   -- replaced by :
   /*
   for i in ( 
   SELECT b.table_name, b.column_name, b.position
      FROM ( select * from  dba_constraints a1
              where a1.owner=tab_rec.source_owner
               and  a1.table_name=tab_rec.source_object_name
               and  a1.constraint_type='U'
               and  rownum=1
           ) a,
           dba_cons_columns b
     WHERE     a.owner = b.owner
     AND       a.constraint_name = b.constraint_name
     ORDER BY b.position )
    */

   -- check dba_indexes and dba_ind_columns instead

  for i in (
  SELECT b.table_name, b.column_name, b.column_position
      FROM ( select * from  dba_indexes a1
              where a1.owner=tab_rec.source_owner
               and  a1.table_name=tab_rec.source_object_name
               and  a1.UNIQUENESS='UNIQUE'
               and  rownum=1
           ) a,
           dba_ind_columns b
     WHERE     a.owner = b.index_owner
     AND       a.index_name = b.index_name
  ORDER BY b.column_position )
  
  loop
  if initial=1 then
  uk:=i.column_name;
  initial:=initial+1;
  else
  uk:=uk||','||i.column_name;
  end if;
  end loop;

  --  update dbscrub.config_table set pk_uk_columns=uk where source_owner=tab_rec.source_owner and source_object_name=tab_rec.source_object_name;
    dbms_output.put_line ( tab_rec.source_object_name  || ' do not have PK but has unique cols such as ' || uk );

  else
   

   --    update dbscrub.config_table set has_pk_uk='NO' where source_owner=tab_rec.source_owner and source_object_name=tab_rec.source_object_name;

    dbms_output.put_line ( tab_rec.source_object_name  || ' does not have PK or any unique cols');

  end if;
  end loop;
  commit;
end;
/



The modified script's output looks like:

T5 does not have PK or any unique cols
T4 does not have PK but has unique cols
T4 does not have PK but has unique cols such as C1,C2
T3 does not have PK but has unique cols
T3 does not have PK but has unique cols such as C1,C2
T2 does not have PK but has unique cols
T2 does not have PK but has unique cols such as C1,C2
T1 has PK
T1 PK cols : C1,C2

BTW, the need for this script comes from our tasks of configuring Oracle GoldenGate.

Wednesday, March 24, 2010

Bump up sequences

I got a requirement to bump up more than 200 sequences in a database. Initially I developed SQL scripts to bump up based on the difference between the target value and the dba_sequneces.last_number column for each of the sequences. However, I soon found out that the last_number does not necessarily equal to the seq.nextval. This is apparently due to the cache_size option. For example:

TESTDB> create sequence temp_seq;

Sequence created.

TESTDB> select temp_seq.nextval from dual;

   NEXTVAL
----------
         1

TESTDB> select temp_seq.nextval from dual;

   NEXTVAL
----------
         2

TESTDB> select SEQUENCE_NAME, cache_size, last_number from user_sequences;

SEQUENCE_NAME                  CACHE_SIZE LAST_NUMBER
------------------------------ ---------- -----------
TEMP_SEQ                               20          21


I finally decdied to adopt a PL/SQL solution. I first created a helper table (seq_bump), which stores the target values. Then, I created the following PL/SQL procedure:

declare
  cursor c is 
   select seq_owner, seq_name, bump_number 
   from seq_bump;

  stmt varchar2(300);
  l_seq_owner seq_bump.seq_owner%type;
  l_seq_name seq_bump.seq_name%type;
  l_bump_number seq_bump.bump_number%type;
  l_nextval number;
  l_inc   number;
  l_count number :=0;
begin
  open c;
  loop
   -- l_count := l_count + 1;
   --  dbms_output.put_line('**** conunter = ' || l_count );
    fetch c into l_seq_owner, l_seq_name, l_bump_number;
    stmt:= 'select ' || l_seq_owner || '.' || l_seq_name || '.nextval from dual';
    execute immediate stmt into l_nextval;
   --  dbms_output.put_line('next values is ' || l_nextval);
    l_inc := l_bump_number - l_nextval;
    stmt:= 'alter sequence ' || l_seq_owner || '.' || l_seq_name || ' increment by ' || l_inc ;
   --  dbms_output.put_line(stmt);
    execute immediate stmt;
    stmt:= 'select ' || l_seq_owner || '.' || l_seq_name || '.nextval from dual';
  --  dbms_output.put_line(stmt);
    execute immediate stmt into l_nextval;
    stmt:= 'alter sequence ' || l_seq_owner || '.' || l_seq_name || ' increment by 1 ';
    execute immediate stmt;
    exit when c%notfound;
  end loop;
  close c;
end;
/

I tested above procedure and this will be implemented in production this weekend, hopefully it will work as I expect.

Wednesday, November 25, 2009

Don't use PL/SQL to Do the Job of SQL

Several days ago, I was asked to review scripts from development team for an application consolidation effort. There is a particular script that uses PL/SQL, which run more than 3 hours. After I reviewed it, I believed they can be written by SQL statment.

For example, for the following PL/SQL block, it depends on the EXCEPTION condition to update a table. Though I am not an experienced PL/SQL programmer, I am suspicous this could be considered good practice in PL/SQL.

-- PL/SQL block

BEGIN
   FOR bas_rec IN bas_cur LOOP
      v_my_account_id:=bas_rec.my_account_id;

      BEGIN 
  SELECT DISTINCT ms.state_id INTO v_state_id
    FROM
      my_sch.tab_ms ms
    WHERE
      ms.account_id = v_my_account_id AND
      ms.state_id IS NOT NULL AND
      ms.svc_node_type_id  NOT IN (203,204,206,208,218,402) AND
      ms.is_pq ='N' AND
      ms.svc_status_id = 2;
     
  EXCEPTION
    WHEN TOO_MANY_ROWS THEN
      UPDATE my_sch.conv_2 bas
        SET bas.to_be_converted_status=v_exclusion_flag, 
     bas.exclusion_reason = v_exclusion_reason_multi, 
     bas.is_processed = v_processed_flag
        WHERE bas.my_account_id = v_my_account_id;
        v_is_updated:='Y';
      COMMIT;
    WHEN NO_DATA_FOUND THEN NULL; 
    WHEN OTHERS THEN 
    dbms_output.put_line('Multi State block OTHERS - v_ban:'||sqlerrm ); 
  COMMIT;
      END; 
  END LOOP;
END;


Anyway, I know that " Don't use PL/SQL to Do the Job of SQL". So I translated the above PL/SQL to the following SQL.


-- SQL code 

UPDATE my_sch.conv_2 bas
SET bas.to_be_converted_status='N', 
    bas.exclusion_reason = 'SERVICES IN MULTIPLE STATES', 
    bas.is_processed ='Y' 
WHERE bas.is_processed is null
  AND 1 < ( select count( DISTINCT ms.state_id)
      from my_ord.tab_ms ms
       WHERE ms.account_id =  bas.my_account_id
  AND ms.state_id IS NOT NULL 
  AND ms.svc_node_type_id  NOT IN (203,204,206,208,218,402) 
  AND ms.is_pq ='N' 
  AND ms.svc_status_id = 2 )
;


I also re-wrote the other part of the script with SQL. In a small scale test, original PL/SQL took 5 min. My SQL code took about 1.5 min. Sadly, developers are more comfortable with their PL/SQL code and are unwilling to do a thoroug test and verification about SQL method. So I will still use their PL/SQL code in the production implementation - just running 5 threads of them to speed up instead of 1 thread previously.

Sunday, December 07, 2008

Cursor Variables - Impementing Variant Records

The power of cursor variables comes from their ability to point to different cursors.
This test example comes from this doc



denis@TEST10G>
denis@TEST10G> create table emp_tab
2 as
3 select
4 rownum empno,
5 object_name ename,
6 mod(rownum, 10) + 1 deptno ,
7 rownum * 100 sal
8 from all_objects
9 where rownum < 100;

Table created.

denis@TEST10G>
denis@TEST10G>
denis@TEST10G> drop table dept_tab purge;

Table dropped.

denis@TEST10G> create table dept_tab
2 as
3 select
4 rownum deptno,
5 'd_' || rownum deptname
6 from all_objects
7 where rownum <=10;

Table created.

denis@TEST10G>
denis@TEST10G>
denis@TEST10G> -- In the following package example, a discriminant is used to
denis@TEST10G> -- open a cursor variable to point to one of two different cursors:
denis@TEST10G>
denis@TEST10G> create or replace package emp_dept_data as
2 type cv_type is ref cursor;
3 procedure open_cv(cv in out cv_type,
4 discrim in positive);
5 end emp_dept_data;
6 /

Package created.

denis@TEST10G>
denis@TEST10G> create or replace package body emp_dept_data as
2 procedure open_cv (cv in out cv_type,
3 discrim in positive) is
4 begin
5 if discrim = 1 then
6 open cv for select * from emp_tab where sal > 2000;
7 elsif discrim = 2 then
8 open cv for select * from dept_tab;
9 end if;
10 end open_cv;
11 end emp_dept_data;
12 /

Package body created.

denis@TEST10G>
denis@TEST10G> -- You can call the open_cv procedure to open the cursor variable
denis@TEST10G> -- and point it to either a query on the emp_tab table or
denis@TEST10G> -- the dept_tab table.
denis@TEST10G>
denis@TEST10G> declare
2 emp_rec emp_tab%rowtype;
3 dept_rec dept_tab%rowtype;
4 cv emp_dept_data.cv_type;
5
6 begin
7 -- emp_dept_data.open_cv(cv,2); -- open cv for emp_tab fetch
8 emp_dept_data.open_cv(cv,1); -- open cv for emp_tab fetch
9 fetch cv into dept_rec; -- but fetch inot dept_tab record
10 -- which raises rowtype_mismatch
11 dbms_output.put(dept_rec.deptno);
12 dbms_output.put_line(' ' || dept_rec.deptname);
13 exception
14 when rowtype_mismatch then
15 begin
16 dbms_output.put_line
17 ('Row type mismatch, fetching emp_tab data ...');
18 fetch cv into emp_rec;
19 dbms_output.put(emp_rec.deptno);
20 dbms_output.put_line(' ' || emp_rec.ename);
21 end;
22 end;
23 /
Row type mismatch, fetching emp_tab data ...
2 C_TS#

PL/SQL procedure successfully completed.


Friday, December 05, 2008

Demonstrate the Usage of Cursor Variable in PL/SQL

Reference: Cursor Variables



denis@TEST10G> @cursor_variables.sql
denis@TEST10G> rem Fectching Data
denis@TEST10G> rem
denis@TEST10G> rem Purpose: demonstrate the usage of cursor variable in PL/SQL
denis@TEST10G> rem
denis@TEST10G> rem A cursor variable is a pointer to a cursor. Because cursor
denis@TEST10G> rem variables are pointers, they can be passed and returned as
denis@TEST10G> rem parameters to procedures and functions. A cursor variable
denis@TEST10G> rem can also refer to different cursors in its life time
denis@TEST10G>
denis@TEST10G>
denis@TEST10G> drop table emp_tab purge;

Table dropped.

denis@TEST10G>
denis@TEST10G> create table emp_tab
2 as
3 select
4 rownum empno,
5 object_name ename,
6 mod(rownum, 10) + 1 deptno ,
7 rownum * 100 sal
8 from all_objects
9 where rownum < 100;

Table created.

denis@TEST10G>
denis@TEST10G>
denis@TEST10G> drop table dept_tab purge;

Table dropped.

denis@TEST10G> create table dept_tab
2 as
3 select
4 rownum deptno,
5 'd_' || rownum deptname
6 from all_objects
7 where rownum <=10;

Table created.

denis@TEST10G>
denis@TEST10G>
denis@TEST10G> -- the following package defines a PL/SQL cursor variable type Emp_Val_cv_type,
denis@TEST10G> -- and two procedures
denis@TEST10G> -- The first procedure, Open_emp_cv, opens the cursor variable using a bind
denis@TEST10G> -- variable in the WHERE clause. The second procedure, Fetch_emp_data, fetches
denis@TEST10G> -- rows from the Emp_tab table using the cursor variable
denis@TEST10G>
denis@TEST10G>
denis@TEST10G> create or replace package emp_data as
2 type emp_val_cv_type is ref cursor return emp_tab%rowtype;
3 procedure open_emp_cv (emp_cv in out emp_val_cv_type,
4 dept_number in integer);
5 procedure fetch_emp_data (emp_cv in emp_val_cv_type,
6 emp_row out emp_tab%rowtype);
7 end emp_data;
8 /

Package created.

denis@TEST10G>
denis@TEST10G> create or replace package body emp_data as
2 procedure open_emp_cv (emp_cv in out emp_val_cv_type,
3 dept_number in integer) is
4 begin
5 open emp_cv for select * from emp_tab where deptno = dept_number;
6 end open_emp_cv;
7 procedure fetch_emp_data (emp_cv in emp_val_cv_type,
8 emp_row out emp_tab%rowtype) is
9 begin
10 fetch emp_cv into emp_row;
11 end fetch_emp_data;
12 end emp_data;
13 /

Package body created.

denis@TEST10G>
denis@TEST10G>
denis@TEST10G> -- The following code shows how to call emp_data package
denis@TEST10G> -- procedures from a PL/SQL bock:
denis@TEST10G>
denis@TEST10G>
denis@TEST10G> declare
2 -- delcare a cursor variable
3 emp_curs emp_data.emp_val_cv_type;
4 dept_number dept_tab.deptno%type;
5 emp_row emp_tab%rowtype;
6 begin
7 dept_number := 6;
8 -- open the cursor using a variable
9 emp_data.open_emp_cv(emp_curs, dept_number);
10 -- fetch the data and display it
11 loop
12 emp_data.fetch_emp_data(emp_curs, emp_row);
13 exit when emp_curs%notfound;
14 dbms_output.put_line(rpad(emp_row.ename, 20) || ' ' || lpad(emp_row.sal, 20));
15 end loop;
16 end;
17 /
C_COBJ# 500
I_CON1 1500
I_UNDO2 2500
C_USER# 3500
I_COL3 4500
I_PROXY_ROLE_DATA$_2 5500
I_SUPEROBJ1 6500
IDL_SB4$ 7500
TRIGGERJAVAM$ 8500
PROPS$ 9500

PL/SQL procedure successfully completed.

Wednesday, October 29, 2008

PL/SQL Exception - a simple test

Continue the effort to learn PL/SQL - This is about EXCEPTION, simple test.



denis@TEST10G> @exception_1
denis@TEST10G> rem This is the test case to test the following situation:
denis@TEST10G> rem
denis@TEST10G> rem 1. Not using any exception, which would result in an ORA-01403 error
denis@TEST10G> rem 2. Uisng predefined Oracle Database exception NO_DATA_FOUND
denis@TEST10G> rem 3. Using user-defined exception
denis@TEST10G> rem
denis@TEST10G>
denis@TEST10G>
denis@TEST10G> drop table t;

Table dropped.

denis@TEST10G>
denis@TEST10G> create table t
2 as
3 select
4 rownum empno,
5 a.object_name ename
6 from all_objects a
7 where rownum < 5000;

Table created.

denis@TEST10G>
denis@TEST10G>
denis@TEST10G> -- Case 1: exception not defined
denis@TEST10G>
denis@TEST10G> declare
2 emp_number integer :=5000;
3 emp_name varchar2(50);
4 begin
5 select ename into emp_name from t
6 where empno = emp_number; -- no such number
7 dbms_output.put_line('Employee name is ' || emp_name);
8 end;
9 /
declare
*
ERROR at line 1:
ORA-01403: no data found
ORA-06512: at line 5


denis@TEST10G>
denis@TEST10G>
denis@TEST10G> -- Case 2: using predefind Oracle exception
denis@TEST10G>
denis@TEST10G> declare
2 emp_number integer :=5000;
3 emp_name varchar2(50);
4 begin
5 select ename into emp_name from t
6 where empno = emp_number; -- no such number
7 dbms_output.put_line('Employee name is ' || emp_name);
8 exception
9 when no_data_found then
10 dbms_output.put_line('No such employee:' || emp_number);
11 end;
12 /
No such employee:5000

PL/SQL procedure successfully completed.

denis@TEST10G>
denis@TEST10G>
denis@TEST10G> -- Case 3: using user-defined exception
denis@TEST10G> -- declare it in the delcaration part of a block
denis@TEST10G> -- define it in the exception part of the block
denis@TEST10G>
denis@TEST10G> declare
2 emp_number integer;
3 emp_name varchar2(50);
4 empno_out_of_range exception;
5 begin
6 emp_number := 5000;
7 if emp_number > 4999 or emp_number < 1 then
8 raise empno_out_of_range;
9 else
10 select ename into emp_name from t
11 where empno = emp_number;
12 dbms_output.put_line('Employee name is ' || emp_name);
13 end if;
14 exception
15 when empno_out_of_range then
16 dbms_output.put_line('Employee number:' || emp_number ||
17 ' is out of range.');
18 end;
19 /
Employee number:5000 is out of range.

PL/SQL procedure successfully completed.

Wednesday, October 22, 2008

Oracle built-in functions: SUBSTR and INSTR

If we want to restore a RMAN backup to a different host and the path in the new host are different from those in the original host, we will have to use the 'set newname for datafile ' statment to change the file name. This test case demonstrated the use of SUBSTR and INSTR function and how to use them to generate the 'set newname for datafile' statement dynamically.


 

denis@TEST10G> @substr_instr.sql
denis@TEST10G> rem substr_instr.sql
denis@TEST10G> rem ref: http://www.psoug.org/reference/substr_instr.html
denis@TEST10G>
denis@TEST10G> set echo on
denis@TEST10G>
denis@TEST10G> SELECT SUBSTR('Take the first four characters', 1, 4) FIRST_FOUR
2 FROM dual;

FIRS
----
Take

denis@TEST10G>
denis@TEST10G> SELECT SUBSTR('Take the first four characters', 16, 4) MIDDLE_FOUR
2 FROM dual;

MIDD
----
four

denis@TEST10G>
denis@TEST10G>
denis@TEST10G> SELECT SUBSTR('Take the first four characters', 16) SIXTEEN_TO_END
2 FROM dual;

SIXTEEN_TO_END
---------------
four characters

denis@TEST10G>
denis@TEST10G> SELECT SUBSTR('Take the first four characters', -4) FINAL_FOUR
2 FROM dual;

FINA
----
ters

denis@TEST10G>
denis@TEST10G>
denis@TEST10G> SELECT INSTR('Take the first four characters', 'a', 1, 1) FOUND_1
2 FROM dual;

FOUND_1
----------
2

denis@TEST10G>
denis@TEST10G> SELECT INSTR('Take the first four characters', 'a', 1, 2) FOUND_2
2 FROM dual;

FOUND_2
----------
23

denis@TEST10G>
denis@TEST10G> SELECT INSTR('Take the first four characters', 'four', 1, 1) MCHARS
2 FROM dual;

MCHARS
----------
16

denis@TEST10G>
denis@TEST10G> SELECT INSTR('Take the first four characters', 'a', -1, 1) REV_SRCH
2 FROM dual;

REV_SRCH
----------
25

denis@TEST10G>
denis@TEST10G> SELECT INSTR('Take the first four characters', 'a', -1, 2) REV_TWO
2 FROM dual;

REV_TWO
----------
23

denis@TEST10G>
denis@TEST10G> -- Take up to the character before the first comma
denis@TEST10G> SELECT SUBSTR('abc,def,ghi', 1 ,INSTR('abc,def,ghi', ',', 1, 1)-1)
2 FROM dual;

SUB
---
abc

denis@TEST10G>
denis@TEST10G> -- List parsing center value
denis@TEST10G> -- Take the value between the commas
denis@TEST10G> SELECT SUBSTR('abc,def,ghi', INSTR('abc,def,ghi',',', 1, 1)+1,
2 INSTR('abc,def,ghi',',',1,2)-INSTR('abc,def,ghi',',',1,1)-1)
3 FROM dual;

SUB
---
def

denis@TEST10G>
denis@TEST10G> -- List parsing last value
denis@TEST10G> -- Take the value after the last comma
denis@TEST10G> SELECT SUBSTR('abc,def,ghi', INSTR('abc,def,ghi',',', 1, 2)+1)
2 FROM dual;

SUB
---
ghi

denis@TEST10G>
denis@TEST10G>
denis@TEST10G> -- test the case that restoring a databsase to a different host and with different path name
denis@TEST10G> -- using RMAN. Demonstrate how to create 'set new name' statments
denis@TEST10G>
denis@TEST10G> set linesize 400
denis@TEST10G> drop table t;

Table dropped.

denis@TEST10G> create table t(file_name varchar2(100));

Table created.

denis@TEST10G> insert into t values('/opt/app/wcmdb02/ora01/P3CM1D1/dbf/idx500cP0CMD_7.dbf');

1 row created.

denis@TEST10G> insert into t values('/opt/app/wcmdb02/ora03/P3CM1D1/dbf/usersP0CMD_1.dbf');

1 row created.

denis@TEST10G> commit;

Commit complete.

denis@TEST10G>
denis@TEST10G> select 'set newname for datafile ' || ''''||file_name||'''' ||
2 lpad('to ', 60-length(file_name)) || ''''||
3 '/opt/app/cm2cmdb' || substr(file_name,instr(file_name,'/',1,4), 6) ||'/P0CMD' ||
4 substr(file_name, instr(file_name, '/', -1, 2) ) || '''' || ';'
5 from t;

'SETNEWNAMEFORDATAFILE'||''''||FILE_NAME||''''||LPAD('TO',60-LENGTH(FILE_NAME))||''''||'/OPT/APP/CM2CMDB'||SUBSTR(FILE_NAME,INSTR(FILE_NAME,'/',1,4),6
------------------------------------------------------------------------------------------------------------------------------------------------------
------------------------------------------------------------------------------------------------------------------------------------------------------

set newname for datafile '/opt/app/wcmdb02/ora01/P3CM1D1/dbf/idx500cP0CMD_7.dbf' to '/opt/app/cm2cmdb/ora01/P0CMD/dbf/idx500cP0CMD_7.dbf';
set newname for datafile '/opt/app/wcmdb02/ora03/P3CM1D1/dbf/usersP0CMD_1.dbf' to '/opt/app/cm2cmdb/ora03/P0CMD/dbf/usersP0CMD_1.dbf';

denis@TEST10G>

Thursday, September 18, 2008

Using Regular Expressions with Oracle Database - two examples

Reference: http://download.oracle.com/docs/cd/B19306_01/appdev.102/b14251/adfns_regexp.htm



denis@test10g> ho ls

denis@test10g> @regexp_like.sql
denis@test10g> rem regexp_like.sql
denis@test10g> rem - familar with REGEXP_LIKE
denis@test10g> rem using regular expression to enfore data integrity
denis@test10g>
denis@test10g> drop table t;

Table dropped.

denis@test10g> create table t
2 (
3 l_name varchar2(30),
4 p_number varchar2(30)
5 constraint p_number_format
6 check ( REGEXP_LIKE(p_number, '^\(\d{3}\) \d{3}-\d{4}$' ) )
7 );

Table created.

denis@test10g>
denis@test10g> -- first two statements use valid phone number format
denis@test10g> INSERT INTO t (p_number)
2 VALUES( '(650) 555-5555' );

1 row created.

denis@test10g> INSERT INTO t (p_number)
2 VALUES( '(215) 555-3427' );

1 row created.

denis@test10g> -- remaining statements generate check contraint errors
denis@test10g> INSERT INTO t (p_number)
2 VALUES( '650 555-5555' );
INSERT INTO t (p_number)
*
ERROR at line 1:
ORA-02290: check constraint (DENIS.P_NUMBER_FORMAT) violated


denis@test10g> INSERT INTO t (p_number)
2 VALUES( '650 555 5555' );
INSERT INTO t (p_number)
*
ERROR at line 1:
ORA-02290: check constraint (DENIS.P_NUMBER_FORMAT) violated


denis@test10g> INSERT INTO t (p_number)
2 VALUES( '650-555-5555' );
INSERT INTO t (p_number)
*
ERROR at line 1:
ORA-02290: check constraint (DENIS.P_NUMBER_FORMAT) violated


denis@test10g> INSERT INTO t (p_number)
2 VALUES( '(650)555-5555' );
INSERT INTO t (p_number)
*
ERROR at line 1:
ORA-02290: check constraint (DENIS.P_NUMBER_FORMAT) violated


denis@test10g> INSERT INTO t (p_number)
2 VALUES( ' (650) 555-5555' );
INSERT INTO t (p_number)
*
ERROR at line 1:
ORA-02290: check constraint (DENIS.P_NUMBER_FORMAT) violated


denis@test10g> /
INSERT INTO t (p_number)
*
ERROR at line 1:
ORA-02290: check constraint (DENIS.P_NUMBER_FORMAT) violated


denis@test10g>
denis@test10g> ho ls

denis@test10g> @back_references.sql
denis@test10g> rem back_references.sql
denis@test10g> rem
denis@test10g> rem Using Back Referencs to Reposition Characters
denis@test10g> rem -- back references store matched subexpressions in a temporary buffer,
denis@test10g> rem thereby enabling you to reposition characters
denis@test10g> rem
denis@test10g> rem Explanation of the Regular Expression Elements in the Example
denis@test10g> rem
denis@test10g> rem ^ Matches the beginning of the string.
denis@test10g> rem $ Matches the end of the string.
denis@test10g> rem (\S+) Matches one or more non-space characters. The parentheses are not escaped so
denis@test10g> rem they function as a grouping expression.
denis@test10g> rem \s Matches a whitespace character.
denis@test10g> rem \1 Substitutes the first subexpression, that is, the first group of parentheses
denis@test10g> rem in the matching pattern.
denis@test10g> rem \2 Substitutes the second subexpression.
denis@test10g> rem \3 Substitutes the third subexpression.
denis@test10g> rem , Inserts a comma character.
denis@test10g>
denis@test10g>
denis@test10g> create table famous_people
2 ( names varchar2(30)) ;

Table created.

denis@test10g>
denis@test10g> -- populate table with data
denis@test10g> INSERT INTO famous_people
2 VALUES ('John Quincy Adams');

1 row created.

denis@test10g> INSERT INTO famous_people
2 VALUES ('Harry S. Truman');

1 row created.

denis@test10g> INSERT INTO famous_people
2 VALUES ('John Adams');

1 row created.

denis@test10g> INSERT INTO famous_people
2 VALUES (' John Quincy Adams');

1 row created.

denis@test10g> INSERT INTO famous_people
2 VALUES ('John_Quincy_Adams');

1 row created.

denis@test10g> COMMIT;

Commit complete.

denis@test10g>
denis@test10g>
denis@test10g> col "names" format a40
denis@test10g> col "names after regexp" format a40
denis@test10g>
denis@test10g> SELECT names "names",
2 REGEXP_REPLACE(names,
3 '^(\S+)\s(\S+)\s(\S+)$',
4 '\3, \1 \2')
5 AS "names after regexp"
6 FROM famous_people;

names names after regexp
---------------------------------------- ----------------------------------------
John Quincy Adams Adams, John Quincy
Harry S. Truman Truman, Harry S.
John Adams John Adams
John Quincy Adams John Quincy Adams
John_Quincy_Adams John_Quincy_Adams

denis@test10g>
denis@test10g> drop table famous_people;

Table dropped.

denis@test10g>
denis@test10g> spool off

Thursday, June 12, 2008

A test case - Compare two methods to archive a table

In this test, our goal is to archive a table called archsim_smt under schema dennis. We delete the rows from this table and insert the rows into another table under dennis_arch schema. The row that is selected to be delete/insert is based on column order_id. Each order_id has 100 rows in the table archsim_smt. Order_id has a FK constraint with reference to the table archsim_ssd. Each master_order_id in the table archsim_ssd has 50 order_id. So when we input a particular master_order_id, we will end up archiving 5000 rows in the table archsim_smt.

The objects used in the test are set up by following script:

----- setup.sql ---------------

 
-- create two tables in dennis
create table dennis.archsim_ssd
nologging
as
select
rownum order_id,
1+ mod(rownum,20) master_order_id,
rpad(rownum,10) small_vc_ssd,
rpad(rownum,&m_pad) padding_ssd
from all_objects
where rownum <= 1000
;

/* one master_order_id has 50 order_id;
one order_id has 100 id_smt;
*/
create table dennis.archsim_smt
nologging
as
with generator as (
select --+ materialize
rownum id
from all_objects
where rownum <= 1000
)
select
rownum id_smt,
1+mod(rownum,1000) order_id,
trunc((rownum-1)/5) small_num_smt,
rpad(rownum,10) small_vc_smt,
rpad(rownum,&m_pad) padding_smt
from
generator v1,
generator v2
where rownum <= 100000
;

-- create two empty table in dennis_arch
create table dennis_arch.archsim_ssd
as select * from dennis.archsim_ssd
where 1=0;

create table dennis_arch.archsim_smt
as select * from dennis.archsim_smt
where 1=0;

create unique index dennis.archsim_ssd_pk on dennis.archsim_ssd(order_id);
create unique index dennis.archsim_smt_pk on dennis.archsim_smt(id_smt);
alter table dennis.archsim_ssd add constraint archsim_ssd_pk primary key(order_id);
alter table dennis.archsim_smt add constraint archsim_smt_pk primary key(id_smt);
alter table dennis.archsim_smt add constraint archsim_smt_fk foreign key(order_id) references dennis.archsim_ssd;

begin
dbms_stats.gather_table_stats(
user,
'archsim_ssd',
cascade => true,
estimate_percent => null,
method_opt => 'for all columns size 1'
);
end;
/

begin
dbms_stats.gather_table_stats(
user,
'archsim_smt',
cascade => true,
estimate_percent => null,
method_opt => 'for all columns size 1'
);
end;
/





This test was performed by executing a script: comp_test.sql, which is shown below.

-------------- Script: comp_test.sql -------------------

rem comp_test.sql
rem
rem Comparing two methods to archive a table archsim_smt in
rem the dennis schema.
rem
rem
rem Two methods are different base on how to select the rows for archiving:
rem
rem Method 1 - single SQL
rem
rem insert into dennis_arch.archsim_smt
rem select * from archsim_smt
rem where order_id in (
rem select order_id
rem from archsim_ssd
rem where master_order_id = 1 );
rem
rem delete from dennis.archsim_smt
rem where order_id in (
rem select order_id
rem from archsim_ssd
rem where master_order_id = 1 );
rem
rem commit;
rem
rem Method 2 - PLSQL LOOP
rem ( similar approach is used in the production database right now)
rem declare
rem inorderid number;
rem cursor allorderids is
rem select distinct order_id
rem from dennis.archsim_ssd
rem where master_order_id = 1;
rem begin
rem open allorderids;
rem
rem FETCH allorderids INTO inorderid;
rem
rem WHILE allorderids % FOUND
rem LOOP
rem INSERT
rem INTO dennis_arch.archsim_smt
rem SELECT *
rem FROM dennis.archsim_smt
rem WHERE order_id = inorderid;
rem
rem delete from dennis.archsim_smt
rem where order_id = inorderid;
rem
rem inorderid := NULL;
rem FETCH allorderids INTO inorderid;
rem END LOOP;
rem
rem commit;
rem close allorderids;
rem end;
rem /
rem
rem A tool called Runstat is used to compare two methods.
rem
rem Runstats is a tool developed by Tom Kyte to compare two
rem different methods of doing the same thing and show which
rem one is superior. (http://asktom.oracle.com/tkyte/runstats.html)
rem
rem Test was done in a 11 g database.
rem

spool comp_test

exec runstats_pkg.rs_start;

-- method 1

@@setup
@@method_1
select count(*) from dennis.archsim_smt;
select count(*) from dennis_arch.archsim_smt;

exec runstats_pkg.rs_middle;

-- method 2

@@setup
@@method_2
select count(*) from dennis.archsim_smt;
select count(*) from dennis_arch.archsim_smt;

exec runstats_pkg.rs_stop;



 


Results



Run1 ran in 1339 hsecs
Run2 ran in 3333 hsecs
run1 ran in 40.17% of the time

Name Run1 Run2 Diff
STAT...write clones created in 0 1 1
LATCH.PL/SQL warning settings 123 122 -1
LATCH.cp sga latch 1 0 -1
LATCH.ncodef allocation latch 1 0 -1
LATCH.threshold alerts latch 0 1 1
LATCH.begin backup scn array 301 300 -1
LATCH.dml lock allocation 13 12 -1
LATCH.sort extent pool 60 59 -1
LATCH.deferred cleanup latch 1 0 -1

...

STAT...redo size 7,364,552 7,400,712 36,160
LATCH.object queue header oper 33,375 70,180 36,805
LATCH.simulator lru latch 7,650 69,629 61,979
LATCH.simulator hash latch 13,851 125,011 111,160
STAT...consistent gets from ca 55,489 665,732 610,243
STAT...no work - consistent re 53,477 665,735 612,258
STAT...table scan blocks gotte 45,247 657,746 612,499
STAT...consistent gets from ca 60,627 674,250 613,623
STAT...consistent gets 60,627 674,250 613,623
STAT...session logical reads 100,356 714,181 613,825
LATCH.cache buffers chains 197,356 851,159 653,803
STAT...table scan rows gotten 726,518 10,526,397 9,799,879
STAT...physical read total byt 109,912,064 182,550,528 72,638,464
STAT...physical read bytes 109,502,464 182,140,928 72,638,464

Run1 latches total versus Run2 -- different and pct
Run1 Run2 Diff Pct
391,297 1,279,691 888,394 30.58%




  


It can be seen from the the Runstat tool, the method 1 has more than 10 times less consistent gets than method 2 ( 60,627 vs 674,250); and also method 1 has much less latches than method 2 (391,297 vs 1,279,691 ). So it is clear that Method 1 is superior. As Tom Kyte said, if someting can be done in simple sql statements,it will be better than PL/SQL precedures generally.

Monday, October 22, 2007

Definer rights vs Invoker rights

Definer rights and invoker rights are described in the Oracle docs as shown in
http://download.oracle.com/docs/cd/B10501_01/appdev.920/a96624/08_subs.htm#15186

I took the following notes from the above docs to help me understand these concepts:

1. Assuming both Scott and Blake has a table called dept.
2. Scott created a procedure as follows:

CREATE PROCEDURE create_dept (
my_deptno NUMBER,
my_dname VARCHAR2,
my_loc VARCHAR2) AS
BEGIN
INSERT INTO dept VALUES (my_deptno, my_dname, my_loc);
END;

3. Scott grant the execute privilege to Blake, e.g.
scott@mydb> grant execute on create_dept to Blake;

4. Blake execute the procedure create_dept, e.g.
blake@mydb> execute create_dept(10, 'sales', 'New York');

5. Blake will create a department 'sales' in the table dept residing in Scott, not in Blake.

This is due to the definer's rights - the procedure gets executed with the privileges and schema context of the owner.

6. If in step 2, Scott create an invoker-rights procedure as follows

CREATE PROCEDURE create_dept (
my_deptno NUMBER,
my_dname VARCHAR2,
my_loc VARCHAR2) AUTHID CURRENT_USER AS
BEGIN
INSERT INTO dept VALUES (my_deptno, my_dname, my_loc);
END;

7. Then, if Blake repeats the step 4, Blake will create a a department 'sales' in the table dept residing in Blake, not in Scott.

This is due to the invoker's right - the procedure gets executed with the privileges and schema context of the current user.

Friday, July 27, 2007

Table function - pipleined vs non-pipelined

Env: Oracle 10g

1. Create a table called ORDERS as follows:

create table orders (
order_number,
create_date,
assign_date,
close_date,
region_id )
as select
object_id,
created,
last_ddl_time,
last_ddl_time +7,
mod(object_id,8)
from dba_objects
/

scott@ORCL> select count(*) from orders;

COUNT(*)
----------
50377

2. Create a non-pipelined table function:

CREATE TYPE order_date_o AS object (
order_number number,
date_type char(1), -- O, A, C
year number,
quarter number,
month number
)
/

create type order_date_t as table of order_date_o
/


CREATE OR REPLACE FUNCTION date_parse ( p_curs SYS_REFCURS
RETURN order_date_t AS
v_order_rec orders%ROWTYPE;
v_ret_val order_date_t := order_date_t( );
BEGIN
-- for every order in the cursor ...
LOOP
FETCH p_curs INTO v_order_rec;
EXIT WHEN p_curs%NOTFOUND;
-- extend the array by 3 and populate with component
-- orders creation, assignment and close date
v_ret_val.EXTEND(3);
v_ret_val(v_ret_val.LAST-2) :=
order_date_o(v_order_rec.order_number,
'O',
TO_CHAR(v_order_rec.create_date,'YYYY'),
TO_CHAR(v_order_rec.create_date,'Q'),
TO_CHAR(v_order_rec.create_date,'MM'));
v_ret_val(v_ret_val.LAST - 1) :=
order_date_o(v_order_rec.order_number,
'A',
TO_CHAR(v_order_rec.assign_date,'YYYY'),
TO_CHAR(v_order_rec.assign_date,'Q'),
TO_CHAR(v_order_rec.assign_date,'MM'));
v_ret_val(v_ret_val.LAST) :=
order_date_o(v_order_rec.order_number,
'C',
TO_CHAR(v_order_rec.close_date,'YYYY'),
TO_CHAR(v_order_rec.close_date,'Q'),
TO_CHAR(v_order_rec.close_date,'MM'));
END LOOP;
RETURN(v_ret_val);
END;
/


3. Create a pipelined table function

CREATE OR REPLACE FUNCTION date_parse_pl ( p_curs SYS_REFCURSOR )
RETURN order_date_t
PIPELINED AS

v_order_rec orders%ROWTYPE;

BEGIN
-- for every order in the cursor...
LOOP
FETCH p_curs INTO v_order_rec;
EXIT WHEN p_curs%NOTFOUND;

-- pipe out the components of the orders open date
PIPE ROW(order_date_o(v_order_rec.order_number,
'O',
TO_CHAR(v_order_rec.create_date,'YYYY'),
TO_CHAR(v_order_rec.create_date,'Q'),
TO_CHAR(v_order_rec.create_date,'MM')));

-- pipe out the components of the orders assign date
PIPE ROW(order_date_o(v_order_rec.order_number,
'A',
TO_CHAR(v_order_rec.assign_date,'YYYY'),
TO_CHAR(v_order_rec.assign_date,'Q'),
TO_CHAR(v_order_rec.assign_date,'MM')));

-- pipe out the components of the orders close date
PIPE ROW(order_date_o(v_order_rec.order_number,
'C',
TO_CHAR(v_order_rec.close_date,'YYYY'),
TO_CHAR(v_order_rec.close_date,'Q'),
TO_CHAR(v_order_rec.close_date,'MM')));

END LOOP; -- every order in the cursor

RETURN;

END;
/


4. Comparing of the execution time for the following two statments in different session::

(a)
SELECT *
FROM TABLE(date_parse(CURSOR(SELECT * FROM orders)))
WHERE ROWNUM <=10; scott@ORCL> /

ORDER_NUMBER D YEAR QUARTER MONTH
------------ - ---------- ---------- ----------
20 O 2005 3 8
20 A 2005 3 8
20 C 2005 3 9
44 O 2005 3 8
44 A 2005 3 8
44 C 2005 3 9
28 O 2005 3 8
28 A 2005 3 8
28 C 2005 3 9
15 O 2005 3 8

10 rows selected.

Elapsed: 00:00:07.51

(b)
/* test pipelined table function */
SELECT *
FROM TABLE(date_parse_pl(CURSOR(SELECT * FROM orders)))
WHERE ROWNUM <=10; scott@ORCL> @date_parse_pl.tst

ORDER_NUMBER D YEAR QUARTER MONTH
------------ - ---------- ---------- ----------
20 O 2005 3 8
20 A 2005 3 8
20 C 2005 3 9
44 O 2005 3 8
44 A 2005 3 8
44 C 2005 3 9
28 O 2005 3 8
28 A 2005 3 8
28 C 2005 3 9
15 O 2005 3 8

10 rows selected.
Elapsed: 00:00:00.04


0.04 vs 7.51
~




Thursday, July 19, 2007

A simple test of table function

scott@TOY10G> ho cat rowset.sql
CREATE TYPE rowset_o AS OBJECT ( col1 NUMBER,
col2 VARCHAR2(30));
/
CREATE TYPE rowset_t AS TABLE OF rowset_o;
/

CREATE OR REPLACE FUNCTION simple RETURN rowset_t AS
v_rowset rowset_t := rowset_t( );
BEGIN
v_rowset.EXTEND(3);
v_rowset(1) := rowset_o(1,'Value 1');
v_rowset(2) := rowset_o(2,'Value 2');
v_rowset(3) := rowset_o(3,'Value 3');
RETURN(v_rowset);
END;
/

scott@TOY10G> select * from table(simple);

COL1 COL2
---------- ------------------------------
1 Value 1
2 Value 2
3 Value 3

scott@TOY10G> select col2 from table(simple);

COL2
------------------------------
Value 1
Value 2
Value 3

Tuesday, July 25, 2006

Suggestions for standard file extensions in SQL, PL/SQL

Contents of file Extension
---------------- -------------
Package specification .pks
Package body .pkb
Package sepc and body .pkg
Procdueure .pro (or .sp for stored procedure)
Function .fun (or .sf for stored function)
Create table script(s) .tab or .ddl
Synonym creation statements .syn
Index definition .ind
Constraint definitions .con
Test scripts .tst