Showing posts with label Metadata. Show all posts
Showing posts with label Metadata. Show all posts

Thursday, September 11, 2014

DESCRIBE ref cursor - How to display the metadata of the ref cursor.



In  most of the development houses , the database API ( aka  package ) are developed by the database development team and then the API's signature handed over the application / web development team. 

For the most part , it is easier to print the ref cursor contents ; but to get the column name and its datatypes , it is not that obvious in sqlplus ( Some of the tools has features ) .  In Oracle 11g , we can use dbms_sql package to display the column type , size , precision and so on. 

Let us see an example . 
First , let us create a package and package body .  This API will display 10 zipcodes for a given state code. 



SQL> desc zips
 Name                                      Null?    Type
 ----------------------------------------- -------- ----------------------------
 ZIPCODE                                            VARCHAR2(5)
 PO_NAME                                            VARCHAR2(255)
 COUNTY                                             VARCHAR2(255)
 STATE                                              VARCHAR2(2)

SQL> create or replace PACKAGE listzipcode_pkg
  2  AS
  3    PROCEDURE list_by_statecode_p(
  4        statecode_p VARCHAR2 ,
  5        zipcode_list_rc OUT sys_refcursor) ;
  6  END listzipcode_pkg;
  7  /

Package created.

SQL> CREATE OR REPLACE PACKAGE body listzipcode_pkg
  2  AS
  3  PROCEDURE list_by_statecode_p(
  4      statecode_p VARCHAR2 ,
  5      zipcode_list_rc OUT sys_refcursor)
  6  AS
  7  BEGIN
  8    OPEN zipcode_list_rc FOR SELECT * FROM
  9    (
 10      SELECT
 11        state ,
 12        zipcode ,
 13        to_number(zipcode) AS ZipCode_Number
 14      FROM
 15        zips
 16      WHERE
 17        STATE = statecode_p
 18      ORDER BY
 19        zipcode
 20    )
 21    WHERE rownum < 11 ;
 22  END list_by_statecode_p;
 23  END listzipcode_pkg;
 24  /

Package body created.


We can use the ref cursor to display the contents as shown below. 

SQL> variable x refcursor;
SQL> exec listzipcode_pkg.list_by_statecode_p( 'NJ' , :x) ;

PL/SQL procedure successfully completed.

SQL> print :x ;

ST ZIPCO ZIPCODE_NUMBER
-- ----- --------------
NJ 07001           7001
NJ 07002           7002
NJ 07003           7003
NJ 07004           7004
NJ 07005           7005
NJ 07006           7006
NJ 07007           7007
NJ 07008           7008
NJ 07009           7009
NJ 07010           7010


10 rows selected.


To describe ( aka to list the column names , data types ) , let us create an anonymous PL/SQL block . 

Here we go.

SQL> SET serveroutput ON
SQL> DECLARE
  2    l_rcursor sys_refcursor;
  3    l_statecode   VARCHAR2(2);
  4    l_col_type    VARCHAR2(100) ;
  5    l_cursor_name VARCHAR2(100) ;
  6    l_colCnt      NUMBER;
  7    l_descTbl dbms_sql.desc_tab;
  8  BEGIN
  9    l_statecode := 'NJ';
 10    listzipcode_pkg.list_by_statecode_p( statecode_p => l_statecode ,
 11    zipcode_list_rc => l_rcursor) ;
 12    l_cursor_name := 'list_by_statecode_p';
 13    dbms_sql.describe_columns ( c => dbms_sql.to_cursor_number(l_rcursor) ,
 14    col_cnt => l_colCnt, desc_t => l_descTbl );
 15    dbms_output.put_line( RPAD( 'Name' , 25 , ' ' ) || 'Type' );
 16    dbms_output.put_line( RPAD( '----' , 25 , ' ' ) || '----' );
 17    FOR i IN 1 .. l_colCnt
 18    LOOP
 19      IF l_descTbl(i).col_type    = 2 THEN
 20        l_col_type               := 'NUMBER';
 21      ELSIF l_descTbl(i).col_type = 1 THEN
 22        l_col_type               := 'VARCHAR2';
 23      ELSE
 24        l_col_type := l_descTbl(i).col_type ;
 25      END IF;
 26      dbms_output.put_line( RPAD( l_descTbl(i).col_name, 25 , ' ' ) || l_col_type
 27      );
 28    END LOOP;
 29  END ;
 30
 31  /
Name                     Type
----                     ----
STATE                    VARCHAR2
ZIPCODE                  VARCHAR2
ZIPCODE_NUMBER           NUMBER


PL/SQL procedure successfully completed.



In the PL/SQL block , we use dbms_sql to get the metadata contents of the ref cursor . The above shows only the column name , column's datatype . We can extend the above block to show the maximum length , precision , scale and so forth. 

Another thing to note is that the return value of "col_type" is an integer . You need to look it up against the associated name . This information can be found in ALL_SOURCE view. 


SQL> select   text   from   all_source
  2  where   owner = 'SYS' and name = 'DBMS_TYPES' and type = 'PACKAGE';

TEXT
--------------------------------------------------------------------------------
PACKAGE dbms_types AS
  TYPECODE_DATE            PLS_INTEGER :=  12;
  TYPECODE_NUMBER          PLS_INTEGER :=   2;
  TYPECODE_RAW             PLS_INTEGER :=  95;
  TYPECODE_CHAR            PLS_INTEGER :=  96;
  TYPECODE_VARCHAR2        PLS_INTEGER :=   9;
  TYPECODE_VARCHAR         PLS_INTEGER :=   1;
  TYPECODE_MLSLABEL        PLS_INTEGER := 105;
  TYPECODE_BLOB            PLS_INTEGER := 113;
  TYPECODE_BFILE           PLS_INTEGER := 114;
  TYPECODE_CLOB            PLS_INTEGER := 112;
  TYPECODE_CFILE           PLS_INTEGER := 115;
  TYPECODE_TIMESTAMP       PLS_INTEGER := 187;
  TYPECODE_TIMESTAMP_TZ    PLS_INTEGER := 188;
  TYPECODE_TIMESTAMP_LTZ   PLS_INTEGER := 232;
  TYPECODE_INTERVAL_YM     PLS_INTEGER := 189;
  TYPECODE_INTERVAL_DS     PLS_INTEGER := 190;

  TYPECODE_REF             PLS_INTEGER := 110;
  TYPECODE_OBJECT          PLS_INTEGER := 108;
  TYPECODE_VARRAY          PLS_INTEGER := 247;            /* COLLECTION TYPE */
  TYPECODE_TABLE           PLS_INTEGER := 248;            /* COLLECTION TYPE */
  TYPECODE_NAMEDCOLLECTION PLS_INTEGER := 122;
  TYPECODE_OPAQUE          PLS_INTEGER := 58;                 /* OPAQUE TYPE */

  /* NOTE: These typecodes are for use in AnyData api only and are short forms
     for the corresponding char typecodes with a charset form of SQLCS_NCHAR.
  */
  TYPECODE_NCHAR           PLS_INTEGER := 286;
  TYPECODE_NVARCHAR2       PLS_INTEGER := 287;
  TYPECODE_NCLOB           PLS_INTEGER := 288;

  /* Typecodes for Binary Float, Binary Double and Urowid. */
  TYPECODE_BFLOAT          PLS_INTEGER := 100;
  TYPECODE_BDOUBLE         PLS_INTEGER := 101;
  TYPECODE_UROWID          PLS_INTEGER := 104;

  SUCCESS                  PLS_INTEGER := 0;
  NO_DATA                  PLS_INTEGER := 100;

  /* Exceptions */
  invalid_parameters EXCEPTION;
  PRAGMA EXCEPTION_INIT(invalid_parameters, -22369);

  incorrect_usage EXCEPTION;
  PRAGMA EXCEPTION_INIT(incorrect_usage, -22370);

  type_mismatch EXCEPTION;
  PRAGMA EXCEPTION_INIT(type_mismatch, -22626);

END dbms_types;


51 rows selected.


Happy describing . 

Comments Welcome !


Wednesday, June 18, 2014

How to retrieve the underlying SQL for a view.



Some time , you run into performance tuning tasks , where non-performant query uses views . Those views in turn uses views one after the other . 
Though , this is not a recommended practice , it is widely (ab)used . You need to reverse engineer the view to get the actual base tables and its filters and so on. 
Most of the time , the query performs better when directly accessed with the base table ( it depends on the query , and other factors) . 

In any case , Oracle 12c has introduced a new API ( dbms_utility.EXPAND_SQL_TEXT ) to get the underlying base table structures for the views .
This is in one of the most underutilized package , in my opinion ( dbms_utility ). 

Let us get to the example . 



SQL> CREATE OR REPLACE VIEW dept_sales_v
  2  AS
  3    SELECT
  4      deptno AS DepartmentNumber ,
  5      dname  AS DepartmentName ,
  6      loc    AS Location
  7    FROM
  8      scott.dept
  9    WHERE
 10      deptno = 30 ;

View created.


Now , Let us use this new API to get the underlying DDL for the view. 
You may notice that the generated DDL is conceptually the same , syntactically Oracle has created sub query and reference that sub query in the outer query.

SQL> SET serveroutput ON
SQL> SET LONG 10000
SQL> DECLARE
  2    expsql CLOB ;
  3    inpsql CLOB ;
  4  BEGIN
  5    inpsql := 'Select * From dept_sales_v' ;
  6    dbms_utility.EXPAND_SQL_TEXT ( input_sql_text => inpsql, output_sql_text =>
  7    expsql) ;
  8    dbms_output.put_line( expsql) ;
  9  END ;
 10  /
SELECT "A1"."DEPARTMENTNUMBER" "DEPARTMENTNUMBER","A1"."DEPARTMENTNAME"
"DEPARTMENTNAME","A1"."LOCATION" "LOCATION" FROM  (SELECT "A2"."DEPTNO"
"DEPARTMENTNUMBER","A2"."DNAME" "DEPARTMENTNAME","A2"."LOC" "LOCATION" FROM
"SCOTT"."DEPT" "A2" WHERE "A2"."DEPTNO"=30) "A1"

PL/SQL procedure successfully completed.


Now , let us create a view based on this view. 


SQL> CREATE OR REPLACE VIEW EmpSales_v
  2  AS
  3    SELECT
  4      v.DEPARTMENTNAME ,
  5      v.location ,
  6      e.EMPNO AS EmployeeCode,
  7      e.ENAME AS EmployeeName
  8    FROM
  9      DEPT_SALES_V v ,
 10      emp e
 11    WHERE
 12      v.DEPARTMENTNUMBER = e.deptno ;

View created.

Here is the SQL generated by Oracle for this view. 

SQL>     set serveroutput on
SQL> set long 10000
SQL> declare
  2  expsql clob ;
  3  inpsql clob ;
  4  begin
  5  inpsql := 'Select * From EmpSales_v'  ;
  6  dbms_utility.EXPAND_SQL_TEXT  ( input_sql_text => inpsql, output_sql_text => expsql) ;
  7  dbms_output.put_line( expsql) ;
  8  end ;
  9  /
SELECT "A1"."DEPARTMENTNAME" "DEPARTMENTNAME","A1"."LOCATION"
"LOCATION","A1"."EMPLOYEECODE" "EMPLOYEECODE","A1"."EMPLOYEENAME" "EMPLOYEENAME"
FROM  (SELECT "A3"."DEPARTMENTNAME" "DEPARTMENTNAME","A3"."LOCATION"
"LOCATION","A2"."EMPNO" "EMPLOYEECODE","A2"."ENAME" "EMPLOYEENAME" FROM  (SELECT
"A4"."DEPTNO" "DEPARTMENTNUMBER","A4"."DNAME" "DEPARTMENTNAME","A4"."LOC"
"LOCATION" FROM "SCOTT"."DEPT" "A4" WHERE "A4"."DEPTNO"=30) "A3",SCOTT."EMP"
"A2" WHERE "A3"."DEPARTMENTNUMBER"="A2"."DEPTNO") "A1"

PL/SQL procedure successfully completed.

Here is the SQL that has been formatted for Clarity.




Comments Welcome.