Search This Blog

Saturday, October 16, 2010

Execute immediate - Dynamic SQL



NATIVE DYNAMIC SQL (NDS) - Execute immediate


DDL's must be executed via dynamic sql within the pl/sql.


Syntax:

EXECUTE IMMEDIATE <SQL_string>
   [INTO {define_variable[, define_variable]... | record}]
   [USING [IN | OUT | IN OUT] bind_argument
       [, [IN | OUT | IN OUT] bind_argument]...];



Note: <SQL_string> can be an SQL statement or PL/SQL block.



If <SQL_string> is a pl/sql block, then it ends with a semi colon(;).

If <SQL_string> is a sql statement(DML or DDL), then don't end with a semi colon(;).

Note: DDL statement via execute immediate will issue an implicit commit.


Method 1:

No queries; just DDL statements and UPDATEs, INSERTs, or DELETEs, which have no bind variables.

Example:
Begin
  Execute immediate ('create table test_table(rno number,name varchar2(100))');
End;
/




Method 2: (USING clause)

No queries; just UPDATEs, INSERTs, or DELETEs, with a fixed number of bind variables.

Example:


Begin

  Execute immediate 'update scott.emp set sal=sal*:percent where deptno=:deptno' using 0.2,20;
  Dbms_output.put_line(sql%rowcount);

End;
/



Method 3: 

A.  Single row data (INTO & USING clauses)

  Queries (SELECT statements) with a fixed numbers of columns and bind variables, retrieving a single row of data


Example:

Declare
  p_deptno number;
  p_sal number;
  p_hike number;
Begin
  Execute immediate 'select deptno,max(sal),:hike_pct * 100 from scott.emp
                               where deptno=:deptno
                               group by deptno'
                         into p_deptno,p_sal,p_hike
                         using 0.2,20;
   Dbms_output.put_line('Deptno:'|| p_deptno || ' Max salary:'|| p_sal || ' Hike perct:' || p_hike);

End;
/


B.  Multiple row data  ( BULK COLLECT INTO & USING clauses) / (OPEN FOR)

  Queries (SELECT statements) with a fixed numbers of columns and bind variables, retrieving or more rows of data.

Example:

Declare
  Type emp_tab_col is table of scott.emp%rowtype;
  my_emp_tab emp_tab_col:=emp_tab_col();
Begin
 Execute immediate 'select * from scott.emp
                   where deptno=:deptno'
 Bulk collect into my_emp_tab
 using 20;

 For i in my_emp_tab.first..my_emp_tab.last
 loop
  dbms_output.put_line(my_emp_tab(i).empno || ':' || my_emp_tab(i).sal || ':' || my_emp_tab(i).deptno);
 end loop;
End;
/


Method 4:


A statement in which the numbers of columns selected (for a query) or the number of bind variables set are not known until runtime.

(will be executing a dynamic PL/SQL block, rather than a SQL string).

Example:


CREATE OR REPLACE PROCEDURE
get_rows (
   table_in   IN   VARCHAR2,
   where_in   IN   VARCHAR2
                )
IS
BEGIN
   EXECUTE IMMEDIATE
      'DECLARE
          l_row ' || table_in || '%ROWTYPE;
       BEGIN
          SELECT * INTO l_row
            FROM ' || table_in || ' WHERE ' || where_in || ';
       END;';
END get_rows;



BIND VARIABLES:

We can bind into our SQL statement only those expressions (literals, variables, complex expressions) that replace placeholders for data values inside the dynamic string. We cannot bind in the names of schema elements (tables, columns, etc.) or entire chunks of the SQL statement (such as the WHERE clause).

For those parts of our string, we must use concatenation.


IN / OUT / IN OUT : (Argument types)

When we are executing a dynamic query, all bind arguments must be of mode IN,except when we are taking advantage of the RETURNING clause & dynamic PL/SQL.

Example:

PROCEDURE analyze_new_technology (
   tech_name IN VARCHAR2,
   analysis_year IN INTEGER,
   number_of_adherents IN OUT NUMBER,
   projected_revenue OUT NUMBER,
   ) ... ;


DECLARE
   devoted_followers NUMBER;
   est_revenue NUMBER;
BEGIN
   EXECUTE IMMEDIATE
      'BEGIN
          analyze_new_technology (:p1, :p2, :p3, :p4);       END;'
   USING 'oracle', 2010, devoted_followers, est_revenue;
END;


NOTE: OUT & IN OUT variable type and procedure parameters must be of the same data type.


Duplicate Placeholders:

When we are executing a dynamic SQL string (DML or DDLin other words, the string does not end in a semicolon), we must supply an argument for each placeholder, even if there are duplicates.



Example:

CREATE OR REPLACE PROCEDURE updnumval (
   col_in     IN   VARCHAR2,
   start_in   IN   DATE, end_in  IN   DATE,
   val_in     IN   NUMBER)
IS
   dml_str VARCHAR2(32767) :=
      'UPDATE emp SET ' || col_in || ' = :val
        WHERE hiredate BETWEEN :lodate AND :hidate
        AND :val IS NOT NULL';
BEGIN
   EXECUTE IMMEDIATE dml_str
  USING val_in, start_in, end_in, val_in;
END;




When we are executing a dynamic PL/SQL block (the string ends in a semicolon), we must supply an argument for each unique placeholder.

Example:

CREATE OR REPLACE PROCEDURE updnumval (
   col_in     IN   VARCHAR2,
   start_in   IN   DATE, end_in IN   DATE,
   val_in     IN   NUMBER)
IS
   dml_str VARCHAR2(32767) :=
      'BEGIN
          UPDATE emp SET ' || col_in || ' = :val
           WHERE hiredate BETWEEN :lodate AND :hidate
           AND :val IS NOT NULL;
       END;';
BEGIN
   EXECUTE IMMEDIATE dml_str
  USING val_in, start_in, end_in;
END;



Passing NULL Values:

We can't directly pass NULL via USING.

We will encounter an error, if we try to pass NULL directly. Because NULL doesn't have any datatype.

EXECUTE IMMEDIATE
   'UPDATE employee SET salary = :newsal
     WHERE hire_date IS NULL'
   USING NULL;


So we need to pass an uninitialized variable (default null) or to_number(NULL).


BEGIN
    EXECUTE IMMEDIATE
      'UPDATE employee SET salary = :newsal
        WHERE hire_date IS NULL'
      USING TO_NUMBER (NULL);
END;


NOTE:


It turns out that a dynamically constructed and executed PL/SQL block is not treated as a nested block;
instead, it is handled as if it were a procedure or function called from within the current block.
So any variables local to the current or enclosing blocks are not recognized in the dynamic PL/SQL block;
we can make references only to globally defined programs and data structures. These PL/SQL elements include standalone functions and procedures and any elements defined in the specification of a package.

Wednesday, October 6, 2010

Forward Declarations

"Declare elements before using them".


PL/SQL supports the forward declaration of local modules, which means that modules are declared in advance of the actual definition of that program. This declaration makes that program available to be called by other programs even before the program definition.

A forward declaration consists simply of the program header followed by a semicolon (;).This construction is called the module header.


Example:

PROCEDURE MY_TEST_PROCEDURE
IS

   FUNCTION X (..)  RETURN NUMBER;

   FUNCTION Y (..)  RETURN NUMBER;

   FUNCTION X (..) RETURN NUMBER  
   IS
   BEGIN
      -- Uses Function Y
   END X;

   FUNCTION Y(..)  RETURN NUMBER  
   IS
   BEGIN
      -- Uses Function X  
   END Y;
BEGIN
      -- Main logic
END MY_TEST_PROCEDURE;
/

Note:

We cannot make forward declarations of a variable or cursor.
This technique works only with modules (procedures and functions).


The definition for a forwardly declared program must be contained in the declaration section of the same PL/SQL block.

LOCAL MODULES

A local module is a procedure or function that is defined in the declaration section of a PL/SQL block.

Example:

DECLARE
   PROCEDURE show_date (date_in IN DATE) IS
   BEGIN
      DBMS_OUTPUT.PUT_LINE (TO_CHAR (date_in, 'Month DD, YYYY');
   END;
BEGIN
   ...
END;


Note:
Local modules must be located after all of the other declaration statements in the declaration section.
we must declare our variables, cursors, exceptions, types, records, tables, and so on before we type in the
first PROCEDURE or FUNCTION keyword.

Benefits:

1.To reduce the size of the module by stripping it of repetitive code.
2.To improve the readability of your code.


Local modules can be called only from within the block in which they are defined.

Example:

Declare
  my_avg_1 number;
  my_avg_2 number;
  my_avg_3 number;

  Function Calculate_averages (n1 number,n2 number,n3 number,n4 number,n5 number)
    return number
  is
  begin

  return ((n1+n2+n3+n4+n5)/5);

  end Calculate_averages;

Begin

  my_avg_1 := calculate_averages(10,20,30,40,50);

  my_avg_2 := calculate_averages(100,200,300,400,500);

  my_avg_3 := calculate_averages(1000,2000,3000,4000,5000);

  dbms_output.put_line ('my_avg_1:'|| my_avg_1);
  dbms_output.put_line ('my_avg_2:'|| my_avg_2);
  dbms_output.put_line ('my_avg_3:'|| my_avg_3);

END;
/

NOCOPY Parameter Mode (Hint)

There are two ways to pass parameter values:

1.By reference : Pointer of the actual parameter is passed to the formal parameter.

2.By value : value of the actual parameter is passed to the formal parameter & if program
                     terminates without an exception,the formal value is copied back to actual parameter.


                    IN : BY reference

OUT / IN OUT : By value ( If the called block fails, the values are not copied back to actual parameter)


If a large data structure is passed as an OUT / IN OUT parameter, that structure will be passed by value, and our application could experience performance degradation (copying).

To avoid this, we use NOCOPY.


parameter_name [ IN | IN OUT | OUT | IN OUT NOCOPY | OUT NOCOPY ] parameter_datatype


Depending on your application, NOCOPY can improve the performance of programs with IN OUT or OUT parameters. As we might expect, these potential gains come with a tradeoff: if a program terminates with an unhandled exception, we cannot trust the values in a NOCOPY actual parameter.


NOCOPY is a hint, not a command. This means that the compiler might silently decide that it can't fulfill your request for a NOCOPY parameter treatment.

Example:

Create or Replace Procedure test_out_par
(my_out_var out NOCOPY varchar2,
 my_in_var in varchar2)
Is
  my_exception exception;
Begin
  my_out_var:=my_in_var;
  raise my_exception;
End;
/


Declare
  my_out_var_kk varchar2(200);
  my_in_var_kk  varchar2(200);
Begin
  my_in_var_kk :='kiran';
  my_out_var_kk :='kodavati';
  dbms_output.put_line('my_in_var_kk:'|| my_in_var_kk);
  dbms_output.put_line('my_out_var_kk:'|| my_out_var_kk);
  test_out_par (my_out_var_kk,my_in_var_kk);
Exception
  when others then
    dbms_output.put_line('my_in_var_kk:'|| my_in_var_kk);
    dbms_output.put_line('my_out_var_kk:'|| my_out_var_kk);
End;
/

Note: Even we got the exception the variable value got changed (MY_OUT_VAR_KK) because of NOCOPY.

Procedure / Function / Parameters



Procedure:

Syntax:

PROCEDURE [schema.]name [( parameter [, parameter ...] ) ]
   [AUTHID DEFINER | CURRENT_USER]
IS
   [declarations]


BEGIN
   executable statements


[ EXCEPTION
     exception handlers]


END [name];


Function:

Syntax:

FUNCTION [schema.]name [( parameter [, parameter ...] ) ]
   RETURN return_datatype
   [AUTHID DEFINER | CURRENT_USER]
   [DETERMINISTIC]
   [PARALLEL ENABLE ...]
   [PIPELINED]
IS
   [declaration statements]


BEGIN
   executable statements


[EXCEPTION
   exception handler statements]


END [ name ];


Note:

A function's execution section should have a RETURN statement in it,although it is not necessary for the function to compile. If, however, your function finishes executing without processing a RETURN statement, Oracle will raise the following error:

ORA-06503: PL/SQL: Function returned without value



PARAMETERS

1. A parameter has a passing mode.
2. A parameter declaration must be unconstrained.


Unconstrained means, the size or the upper limit of the datatype must not be set

Unconstrained Example: VARCHAR2
                                      NUMBER

Constrainted   Example : VARCHAR2(60)


The formal parameter and the actual parameter that corresponds to it (when called) must be of the same or compatible datatypes.

The actual parameter values are copied to the formal parameters in the function header and those will be used within the function.


Parameter Modes:

1. IN ( Read only ) : Default Mode

2. OUT ( Write only ):

Any assignments made to OUT parameters are rolled back when an exception is raised in the program. Because the value for an OUT parameter is not actually assigned until a program completes successfully, any intermediate assignments are therefore ignored. Unless an exception handler traps the exception and then assigns a value to the OUT parameter,no assignment is made to that parameter. The variable will retain the same value it had before the program was called.

Example:

Create or Replace Procedure test_out_par
(my_out_var out varchar2,
 my_in_var in varchar2)
Is
  my_exception exception;
Begin
  my_out_var:=my_in_var;
  raise my_exception;
End;
/


Declare
  my_out_var_kk varchar2(200);
  my_in_var_kk  varchar2(200);
Begin
  my_in_var_kk :='kiran';
  my_out_var_kk :='kodavati';
  dbms_output.put_line('my_in_var_kk:'|| my_in_var_kk);
  dbms_output.put_line('my_out_var_kk:'|| my_out_var_kk);
  test_out_par (my_out_var_kk,my_in_var_kk);
Exception
  when others then
    dbms_output.put_line('my_in_var_kk:'|| my_in_var_kk);
    dbms_output.put_line('my_out_var_kk:'|| my_out_var_kk);
End;
/

3. IN OUT ( Read / Write )

An IN OUT parameter cannot have a default value.


Matching Actual and Formal Parameters:


1. Positional notation
2. Named notation : (formal parameter => actual parameter)

Note: If we do mix notation, however, we must list all of our positional parameters before any named notation parameters. Positional notation has to have a starting point from which to keep track of positions, and the only starting point is the first parameter.


Advantages of Named notation:

--  We can list the parameters in any order we want.

--  We can also include only the parameters we want or need in the parameter list.

--  Self documenting.


As a general rule, named notation is the best way to write code that is readable and more easily maintained.


Default values:

There are two ways to specify a default value:

1.with the keyword DEFAULT
2.with the assignment operator (:=)

Monday, October 4, 2010

DATE / TIMESTAMP DATATYPES.

We have two options to store the date in oracle table.
1. DATE DATATYPE
2. TIMESTAMP DATATYPE

1. DATE DATATYPE:
It has the ability to store the month, day, year, century, hours, minutes, and seconds.
The problem with the DATE datatype is it's granularity when trying to determine a time interval
between two events when the events happen within a second of each other.

2. TIMESTAMP DATATYPE:
Oracle has expanded on the DATE datatype and has given us the TIMESTAMP datatype which stores all the information that the DATE datatype stores, but also includes fractional seconds.

Trunc can't be used on TIMESTAMP DATATYPE columns.

Example:

select TO_CHAR(time1,'MM/DD/YYYY HH24:MI:SS:FF3') from table;

Where FF, represents the Fractional seconds part.

Calculating the time difference between two TIMESTAMP datatypes is much easier than the DATE datatype.

When we just do straight subtraction of the columns,we see, the results are much easier to recognize,  X days, Y hours, Z minutes, and F seconds.


In DATE we use : SYSDATE
IN TimeStamp we use: SYSTIMESTAMP

Example:

SELECT SYSTIMESTAMP FROM DUAL;


Oracle's TIME ZONE feature of the TIMESTAMP datatype:

1. WITH TIME ZONE
2. WITH LOCAL TIME ZONE

We have two options when setting the timezone of a database.

1. By specifying the displacement (hh:mi) from GMT/UTC.
2. By specifying the name in V$TIMEZONE.

The timezone of the database is set at the time of creation & it can be altered.

select DBTIMEZONE from dual;

ALTER database SET TIME_ZONE = '-04:00';

We need to bounce back the database to make the alter affective.

We can set at session level

Alter session set TIME_ZONE='-03:00';

Select SESSIONTIMEZONE from dual;


SYSTEMTIMESTAMP : This will return the sysdate with time zone information.

SELECT SYSTIMESTAMP FROM DUAL;

10/4/2010 4:13:56.200918 PM -04:00

If we have set the time zone info at the session level, we need to use CURRENT_TIMESTAMP

select CURRENT_TIMESTAMP from dual;

Example:

Create table date_table (
                                      time_stamp_tz   TIMESTAMP WITH TIME ZONE,
                                      time_stamp_ltz  TIMESTAMP WITH LOCAL TIME ZONE
                                     );


Select dbtimezone,sessiontimezone from dual;

Insert into date_table values (SYSTIMESTAMP,SYSTIMESTAMP);

Alter session set time_zone='-03:00';

Select dbtimezone,sessiontimezone from dual;

Insert into date_table values (LOCALTIMESTAMP,LOCALTIMESTAMP );

Insert into date_table values (CURRENT_TIMESTAMP ,SYSTIMESTAMP);

COMMIT;

Saturday, October 2, 2010

CURSOR VARIABLES & REF CURSORS

SYNTAX: 


-- Define a Cursor type
TYPE cursor_type_name IS REF CURSOR [ RETURN return_type ];


-- Declare a Cursor variable
cursor_name cursor_type_name;


-- Open a Cursor object and attach to a Cursor variable
OPEN cursor_name FOR select_statement;


-- Fetch data from Cursor 
FETCH cursor_variable_name INTO [record_name|variable_name, variable_name,..];


A cursor variable is a reference to the work area, but the explicit cursor names the work area.

A cursor varaiable can be opened of any query.


Similar to collections, we need to define the type and then declare a variable of that type.

Example:

Declare
  Type my_ref is ref cursor return source_daily%rowtype;
  my_ref_var my_ref;
Begin
  null;
End;
/


We can classify into two catergories.


1. Strong Ref Cursor :  The return type is defined at the type declaration level

Example:


Declare
  Type my_ref is ref cursor return source_daily%rowtype;
  my_ref_var my_ref;
Begin
  Open my_ref_var for select * from source_daily;
End ;
/



2. Weak Ref Cursor : This can be opened for any query.

Example:

Declare
  Type my_ref is ref cursor;
  my_ref_var my_ref;
Begin
  Open my_ref_var for select * from source_daily;
  Open my_ref_var for select * from target_monthly;
End ;
/



SYS_REFCURSOR:

Starting with Oracle9i Database, Oracle provides a predefined weak REF CURSOR type named SYS_REFCURSOR.

we no longer need to define our own weak type; just use Oracle's

DECLARE
       my_cursor SYS_REFCURSOR;


Note : No need Type statement (Defining Ref Cursor Type).


When ever a cursor variable is opened for the first time, it create a cursor object, which points to the query(result set) & when we re-open the same variable for the second time, it will use the same object, but it changes the query(result set).


If we open a cursor variable and then do assignment to new cursor variable,the newly assigned variable points to the same object.
What ever is done on one variable will reflect to the second.

Example:

Declare
  my_ref_var     sys_refcursor;
  my_ref_var_dup sys_refcursor;
  source_daily_rec source_daily%rowtype;
Begin
  open my_ref_var for select * from source_daily;
  my_ref_var_dup := my_ref_var;
  fetch my_ref_var into source_daily_rec;
  dbms_output.put_line(my_ref_var%rowcount);
  dbms_output.put_line(my_ref_var_dup%rowcount);
  fetch my_ref_var_dup into source_daily_rec;
  dbms_output.put_line(my_ref_var%rowcount);
  dbms_output.put_line(my_ref_var_dup%rowcount);
  close my_ref_var_dup;
  if my_ref_var%isopen then
    dbms_output.put_line('my_ref_var is still open');
  else
    dbms_output.put_line('my_ref_var was closed');
  end if;
End ;
/

Note: The assigned cursor variable acts as alias to the first variable.


ROWTYPE_MISMATCH Exception:

If the query and the INTO clause do not structurally match, then the PL/SQL runtime engine will raise the predefined ROWTYPE_MISMATCH exception.



SCOPE OF CURSOR OBJECT:

Once a OPEN FOR creates a cursor object, the cursor object remains accessible as long as atleast one
cursor variable refers to that cursor object.

This means that we can create a cursor object in one scope (PL/SQL block) and assign it to a cursor variable. Then, by assigning that cursor variable to another cursor variable with a different scope,
the cursor object remains accessible even if the original cursor variable has gone out of scope.

Example:

Create or Replace Function return_cursorobject (my_query varchar2)
 Return SYS_REFCURSOR
IS
  my_local_cur sys_refcursor;
Begin
  Open my_local_cur for my_query;
  return my_local_cur;
End;
/
-- MAIN BLOCK

Declare
  my_local_cursor sys_refcursor;
  my_fectch_rec target_monthly%rowtype;
Begin
  my_local_cursor := return_cursorobject ('select * from target_monthly');
  fetch my_local_cursor into my_fectch_rec;
  dbms_output.put_line(my_local_cursor%rowcount);
End;
/