Post on 17-Mar-2020
transcript
Mark TownsendDirector, Product Managementfor
Bryn LlewellynPL/SQL Product Manager
Oracle Corporation
Less Pain, More GainUse the new PL/SQL featuresin Oracle9i Database
to get better programsby writing fewer code lines
paper #30720, OracleWorld, Copenhagen, Tue 25-June-2002
OTN homepageOTN homepage
TechnologiesTechnologies
PL/SQLPL/SQL
otn.oracle.com/tech/pl_sqlotn.oracle.com/tech/pl_sql
9.2.0 Enhancements
This presentation focuses on enhancements introduced in Oracle9i Database Release 2We’ll refer to this as Version 9.2.0 for brevityMajor enhancements were also introduced in Oracle9i Database Release 1We’ll refer to that as Version 9.0.1This succeded the last release of Oracle8iWe’ll refer to that as Version 8.1.7
Recap of 9.0.1 Enhancements
Some of you will be upgrading directlyfrom Version 8.1.7 to Version 9.2.0You’ll get PL/SQL benefits that go way beyond what’s discussed todayThe new Version 9.0.1 featurestogether with the new Version 9.2.0 featuresallow you to writesignificantly faster PL/SQL applicationswith dramatically fewer lines of code
Recap of 9.0.1 Enhancements
Native compilationCursor expressions;Table functions;Multilevel collections;Exception handling in bulk binding DML operations;Bulk binding in native dynamic SQLCASE statements and CASE expressions
Recap of 9.0.1 Enhancements
VARCHAR2 <-> NVARCHAR2 (etc) assignment;VARCHAR2 <-> CLOB assignment;SUBSTR and INSTR w/ CLOB;Seamless access to new SQL features (eg MERGE, multitable insert, new time datatypes)OO:Schema evolution, inheritance supportUtl_Http, Utl_Raw, Utl_File enhancedNineteen new packages
Recap of 9.0.1 EnhancementsManipulating records faster by up to 5x;Inter-package calls faster by up to 1.5x;Utl_Tcp native implementation(underlies Utl_Http, Utl_Smtp)
Common SQL parser
Summary of 9.2.0 Enhancements
Index-by-varchar2 tables,aka associative arrays
RECORD binds in DML andin BULK SELECTs
Utl_File enhancements
GUI debugging via JDeveloper Version 9.0.3
Associative arrays
declaretype word_list is table of varchar2(20)index by varchar2(20);
the_list word_list;beginthe_list ( 'book' ) := livre;the_list ( 'tree' ) := 'arbre';
end;
Associative arrays
idx varchar2(20);.../* loop in lexical sort order */idx := the_list.First();while idx is not nullloopShow ( idx || ' : ' || the_list(idx) );idx := the_list.Next(idx);
end loop;
Associative arraysThese three flavors of index-by tables are now supported …
– index by binary_integer– index by pls_integer– index by varchar2
the term associative array is now usedfor all these variants in line withcommon usage in other 3GLs
Lookup caching scenario
Req't to look up a value via a uniquenon-numeric key is generic computational problemOracle9i Database provides a solution with SQL and a B*-tree index (!)But performance improvement by using an explicit PL/SQL implementationTrue even before the new index-by-varchar2 table
Lookup caching scenario
Scenarios characterized by very frequent lookup in a relatively small set of values, usually in connection with flattening a relational representation for reportingor for UI presentationWe'll use a simple neutral scenario
(not to complicate the following examples with distracting detail)
Lookup caching scenarioselect * from translations;ENGLISH FRENCH-------------------- ----------computer ordinateurtree arbre...furniture meubles
Allow lookup from French to EnglishAllow efficient addition of newvocabulary pairs
The Vocab packageWe’ll abstract the solution as a package...
package Vocab isfunction Lookup (p_english in varchar2)return varchar2;
procedure New_Pair (p_english in varchar2,p_french in varchar2);
end Vocab;
The Vocab packageMust support...
beginShow ( Vocab.Lookup ( 'tree' ) );...Vocab.New_Pair ( 'garden', 'jardin' );Show ( Vocab.Lookup ( 'garden' ) );
end;
package body Vocab is
/* Naïve pure SQL approach */
function Lookup ( p_english in varchar2 ) return varchar2isv_french translations.french%type;
beginselect french into v_french from translations
where english = p_english;return v_french;
end;...
end Vocab;
package body Vocab is
/* Naïve pure SQL approach */
...procedure New_Pair ( p_english in varchar2, p_french in varchar2 ) isbegininsert into translations ( english, french )values ( p_english, p_french );
end New_Pair;end Vocab;
Naïve pure SQLConcerned only about correctness of behavior and ease of algorithm designAccept the performance we getEach time Lookup is invoked, we make a round trip between PL/SQL and SQLThis frequently repeated context switch can become significantly expensive
package body Vocab is/* Linear search in index-by-binary_integer table */type word_list is table of translations%rowtype
index by binary_integer;g_english_french word_list;...
begin /* package initialization */declare idx binary_integer := 0;begin
for j in ( select english, french from translations )loop
idx := idx+1;g_english_french(idx).english := j.english;g_english_french(idx).french := j.french;
end loop;end;
end Vocab;
package body Vocab is/* Linear search in index-by-binary_integer table */...function Lookup ( p_english in varchar2 )
return varchar2isbegin
for j in 1..g_english_french.Last()loop
if g_english_french(j).english = p_englishthen
return g_english_french(j).french;end if;
end loop;end Lookup;...
end Vocab;
package body Vocab is/* Linear search in index-by-binary_integer table */...procedure New_Pair (
p_english in varchar2, p_french in varchar2 )is
idx binary_integer;begin
idx := g_english_french.Last() + 1;g_english_french(idx).english := p_english;g_english_french(idx).french := p_french;insert into translations ( english, french )
values ( p_english, p_french );end New_Pair;...
end Vocab;
Linear search in index-by-binary_integer table
Algorithm is still trivial, but does require some explicit codingThe entire table contents are loaded into an index-by-binary_integer tableWell-known disadvantage that on average half the elements are examined before we find a match
Binary chop search in index-by-binary_integer table
Possible improvement: maintain the elements in lexical sort orderCompare the search target to the half-way element to determine which half it's inRepeat this test recursively on the relevant half
Binary chop search in index-by-binary_integer table
Requires more elaborate coding - and testingPoses a design problem for the New_PairprocedureEither the array must be opened at the insertion point copying all later elements to the next slotOr it must be created sparse
Binary chop search in index-by-binary_integer table
Neither of these approaches is very comfortableThe sparse alternative is not complete until we cater for the corner case where a gapfills up
package body Vocab is/* Hash-based lookup in index-by-pls_integer table */hash binary_integer;g_hash_base constant number := 100;g_hash_size constant number := 100;type word_list is table of translations.french%type
index by binary_integer;g_english_french word_list;...
begin /* package initialization */begin
for j in ( select english, french from translations )loop
hash := Dbms_Utility.Get_Hash_Value (j.english, g_hash_base, g_hash_size );
g_english_french(hash) := j.french;end loop;
end;end Vocab;
package body Vocab is/* Hash-based lookup in index-by-pls_integer table */...function Lookup ( p_english in varchar2 )
return varchar2 isbegin
hash := Dbms_Utility.Get_Hash_Value (p_english, g_hash_base, g_hash_size );
return g_english_french(hash);end Lookup;...
end Vocab;
package body Vocab is/* Hash-based lookup in index-by-pls_integer table */...procedure New_Pair (
p_english in varchar2, p_french in varchar2 )isbegin
hash := Dbms_Utility.Get_Hash_Value (p_english, g_hash_base, g_hash_size );
g_english_french(hash) := p_french;insert into translations ( english, french )
values ( p_english, p_french );end New_Pair;...
end Vocab;
Hash-based lookup in index-by-binary_integer table
The algorithm as shown is too naïve for real-world useNo guarantee that two distinct values for the nameIN parameter to Get_Hash_Value will always produce distinct hash valuesTo be robust, a collision avoidance scheme must be implemented
Hash-based lookup in index-by-binary_integer table
Oracle provides no specific support for collision avoidance in Get_Hash_ValueSolving this is a non-trivial design, implementation and testing taskProbable that the resulting index-by-binary_integertable will be quite sparseWill discuss this later
Purpose-built B*-tree structurein PL/SQL
Study the relevant computer science textbooksImplement a B*-tree structure in PL/SQL, horror of wheel re-invention notwithstanding!Very far from trivial - certainly too long and complex for inclusion hereBut at 9.20, we don’t need to entertain that uncomfortable thought: Oracle does this for us behind the scenes !
package body Vocab is/* Direct lookup in index-by-varchar2 table */type word_list is table of translations.french%type
index by translations.english%type;g_english_french word_list;...
begin /* package initialization */for j in ( select english, french from translations )loop
g_english_french( j.english ) := j.french;end loop;
end Vocab;
package body Vocab is/* Direct lookup in index-by-varchar2 table */...function Lookup ( p_english in varchar2 )
return varchar2isbegin
return g_english_french( p_english );end Lookup;...
end Vocab;
package body Vocab is/* Direct lookup in index-by-varchar2 table */...procedure New_Pair (
p_english in varchar2, p_french in varchar2 )isbegin
g_english_french( p_english ) := p_french;insert into translations ( english, french )
values ( p_english, p_french );end New_Pair;
...end Vocab;
Direct lookup in index-by-varchar2 table
Use precisely the B*-tree organization of the values but to do so implicitly via the new language featureCan think of the index-by-varchar2 table as the in-memory PL/SQL version of the schema-level index organized table
index-by-varchar2 table Optimized for efficiency of lookup on a non-numeric keyNotion of sparseness is not really applicableindex-by-*_integer table (now *_integer can be either pls_integer or binary_integer) is optimized for compactness of storage on the assumption that the data is densesometimes better to represent numeric key as a index-by-varchar2 table via a To_Char conversion
Associative arrays: summaryindex-by-varchar2 table is a major enhancementAllows a class of very common programming tasks to be implemented …
– much more performantly than was possible pre Version 9.2.0
– with very much less design and testing– in dramatically fewer lines of code
Associative arrays: summaryindex-by-pls_integer table is a minor enhancement that allows a clear coding guideline for all new projects
– never use BINARY_INTEGER except where it's required to match the type in an existing API
Summary of 9.2.0 EnhancementsIndex-by-varchar2 tables,aka associative arrays
RECORD binds in DML andin BULK SELECTs
Utl_File enhancements
GUI debugging via JDeveloper Version 9.0.3
RECORD bindsPL/SQL RECORD datatype corresponds to a row in a schema-level tableNatural construct when manipulating table rows programmatically, especially when a row from one table is manipulated programmatically, and then stored (via INSERT or UPDATE) in an another table with the same shape
RECORD bindsDeclaration is compact, using mytable%rowtypeGuaranteed to match the corresponding schema-level templateImmune to schema-level changes in definition of the shape of the tableRECORD can be used as subprogram parameter giving compact and guaranteed correct notation and allowing optimizations in the implementation of parameter passing
RECORD bindsSQL-PL/SQL interface allows a syntax which does not list the columns of the source/target table explicitlyAgain allowing for robust code which has a greater degree of schema-independence
RECORD bindsReduce effort of writing the codeIncrease the chances of its correctness
But... use of RECORDs in the SQL-PL/SQL interface was greatly restricted pre Version 9.2.0Supported only for single row SELECT
Thus... the listed advantages were not yet capable of being realized
RECORD bindsVersion 9.2.0 adds support for BULK SELECT in both Static and Native Dynamic SQLie full support for all flavors of SELECTAdds support (with some minor restrictions) for all Static SQL flavors of INSERT, DELETE and UPDATE
– INSERT– DELETE ... RETURNING– UPDATE ... RETURNING– UPDATE ... SET ROW
procedure P ( p_date in date ) is/* BULK SELECT into a RECORD */type emprec_tab_t istable of employees%rowtypeindex by pls_integer;
v_emprecs emprec_tab_t;cursor cur isselect * from employeeswhere hire_date >= p_date;
beginopen cur;fetch cur bulk collect into v_emprecs limit 10;close cur;...
end P;
procedure P ( p_date in date ) is/* BULK SELECT into a RECORD w/ dynamic SQL */...cur sys_refcorsor;
beginopen cur for'select * from employees
where hire_date >= :the_date'using p_date;
fetch cur bulk collect into v_emprecs limit 10;close cur;...
end P;
declaretype employee_ids_t is table of employees.employee_id%type
index by binary_integer;type first_names_t is table of employees.first_name%type
index by binary_integer;type last_names_t is table of employees.last_name%type
index by binary_integer;type emails_t is table of employees.email%type
index by binary_integer;type phone_numbers_t is table of employees.phone_number%type
index by binary_integer;type hire_dates_t is table of employees.hire_date%type
index by binary_integer;type job_ids_t is table of employees.job_id%type
index by binary_integer;type salarys_t is table of employees.salary%type
index by binary_integer;type commission_pcts_t is table of employees.commission_pct%type
index by binary_integer;type manager_ids_t is table of employees.manager_id%type
index by binary_integer;type department_ids_t is table of employees.department_id%type
index by binary_integer;
v_employee_ids employee_ids_t;v_first_names first_names_t;v_last_names last_names_t;v_emails emails_t;v_phone_numbers phone_numbers_t;v_hire_dates hire_dates_t;v_job_ids job_ids_t;v_salarys salarys_t;v_commission_pcts commission_pcts_t;v_manager_ids manager_ids_t;v_department_ids department_ids_t;
type emprec_tab_t is table of employees%rowtypeindex by pls_integer;
v_emprecs emprec_tab_t;...
begin...
end;
declare...cursor cur isselectemployee_id,first_name,last_name,email,phone_number,hire_date,job_id,salary,commission_pct,manager_id,department_id
from employees where hire_date >= '25-JUN-97';begin...
end;
declare...
beginopen cur;fetch cur bulk collect intov_employee_ids,v_first_names,v_last_names,v_emails,v_phone_numbers,v_hire_dates,v_job_ids,v_salarys,v_commission_pcts,v_manager_ids,v_department_idslimit 10;
close cur;
for j in 1..v_employee_ids.Lastloopv_emprecs(j).employee_id := v_employee_ids(j);v_emprecs(j).first_name := v_first_names(j);v_emprecs(j).last_name := v_last_names(j);v_emprecs(j).email := v_emails(j);v_emprecs(j).phone_number := v_phone_numbers(j);v_emprecs(j).hire_date := v_hire_dates(j);v_emprecs(j).job_id := v_job_ids(j);v_emprecs(j).salary := v_salarys(j);v_emprecs(j).commission_pct := v_commission_pcts(j);v_emprecs(j).manager_id := v_manager_ids(j);v_emprecs(j).department_id := v_department_ids(j);
end loop;...
end;
BULK SELECT with RECORD bindPre Version 9.2.0 you needed a scalar index-bytable for each select list item and had to list all columns explicitly to be robustNeeded explicit loop to assign RECORD values following the SELECTApproaches what is feasible to maintainFeels especially uncomfortable because of the artificial requirement to compromise the natural modeling approach by slicing the desired table of records vertically into N tables of scalars
declare/* INSERT RECORD, single row
Dynamic SQL not yet supported */v_emprec employees%rowtype := Get_One_Row;
begininsert into employees values v_emprec;
end;
declare/* BULK INSERT RECORD
Dynamic SQL not yet supported */v_emprecs Emp_Util.emprec_tab_t :=Emp_Util.Get_Many_Rows;
beginforall j in v_emprecs.first..v_emprecs.lastinsert into employees values v_emprecs(j);
end;
declare/* BULK INSERT RECORD,
showing SAVE EXCEPTIONS syntax */bulk_errors exception;pragma exception_init ( bulk_errors, -24381 );...
beginforall j in v_emprecs.first..v_emprecs.lastsave exceptionsinsert into employees values v_emprecs(j);
exception when bulk_errors thenfor j in 1..sql%bulk_exceptions.countloopShow ( sql%bulk_exceptions(j).error_index,sql%bulk_exceptions(j).error_code );
end loop;end;
declare...type employee_ids_t is table of employees.employee_id%type
index by binary_integer;type first_names_t is table of employees.first_name%type
index by binary_integer;type last_names_t is table of employees.last_name%type
index by binary_integer;type emails_t is table of employees.email%typeindex by binary_integer;
type phone_numbers_t is table of employees.phone_number%typeindex by binary_integer;
type hire_dates_t is table of employees.hire_date%typeindex by binary_integer;
type job_ids_t is table of employees.job_id%typeindex by binary_integer;
type salarys_t is table of employees.salary%typeindex by binary_integer;
type commission_pcts_t is table of employees.commission_pct%typeindex by binary_integer;
type manager_ids_t is table of employees.manager_id%typeindex by binary_integer;
type department_ids_t is table of employees.department_id%typeindex by binary_integer;
v_employee_ids employee_ids_t;v_first_names first_names_t;v_last_names last_names_t;v_emails emails_t;v_phone_numbers phone_numbers_t;v_hire_dates hire_dates_t;v_job_ids job_ids_t;v_salarys salarys_t;v_commission_pcts commission_pcts_t;v_manager_ids manager_ids_t;v_department_ids department_ids_t;
v_emprecs Emp_Util.emprec_tab_t := Emp_Util.Get_Many_Rows;begin...
end;
declare...
beginfor j in 1..v_emprecs.Lastloopv_employee_ids(j) := v_emprecs(j).employee_id;v_first_names(j) := v_emprecs(j).first_name;v_last_names(j) := v_emprecs(j).last_name;v_emails(j) := v_emprecs(j).email;v_phone_numbers(j) := v_emprecs(j).phone_number;v_hire_dates(j) := v_emprecs(j).hire_date;v_job_ids(j) := v_emprecs(j).job_id;v_salarys(j) := v_emprecs(j).salary;v_commission_pcts(j) := v_emprecs(j).commission_pct;v_manager_ids(j) := v_emprecs(j).manager_id;v_department_ids(j) := v_emprecs(j).department_id;
end loop;
forall j in v_emprecs.first..v_emprecs.last save exceptionsinsert into employees_2 (employee_id,first_name,last_name,email,phone_number,hire_date,job_id,salary,commission_pct,manager_id,department_id )
values (v_employee_ids(j),v_first_names(j),v_last_names(j),v_emails(j),v_phone_numbers(j),v_hire_dates(j),v_job_ids(j),v_salarys(j),v_commission_pcts(j),v_manager_ids(j),v_department_ids(j) );
exception when bulk_errors then...
end;
BULK INSERT with RECORD bindPre Version 9.2.0 you needed a scalar index-bytable for each target column and had to list all columns explicitly to be robustNeeded explicit loop to assign the scalr tables before the INSERTAgain, approaches what is feasible to maintainAgain, feels especially uncomfortable because of the artificial requirement to compromise the natural modeling approach by slicing the desired table of records vertically into N tables of scalars
declarev_emprec employees%rowtype;
begindelete from employees where employee_id = 100returningemployee_id,first_name,last_name,email,phone_number,hire_date,job_id,salary,commission_pct,manager_id,department_id
into v_emprec;...
end;
declarev_emprecs Emp_Util.emprec_tab_t;
beginupdate employees set salary = salary * 1.1where hire_date < = '25-JUN-97'returningemployee_id,first_name,last_name,email,phone_number,hire_date,job_id,salary,commission_pct,manager_id,department_id
bulk collect into v_emprecs;...
end;
declaretype employee_ids_t is table of employees.employee_id%type
index by binary_integer;type first_names_t is table of employees.first_name%type
index by binary_integer;type last_names_t is table of employees.last_name%type
index by binary_integer;type emails_t is table of employees.email%type
index by binary_integer;type phone_numbers_t is table of employees.phone_number%type
index by binary_integer;type hire_dates_t is table of employees.hire_date%type
index by binary_integer;type job_ids_t is table of employees.job_id%type
index by binary_integer;type salarys_t is table of employees.salary%type
index by binary_integer;type commission_pcts_t is table of employees.commission_pct%type
index by binary_integer;type manager_ids_t is table of employees.manager_id%type
index by binary_integer;type department_ids_t is table of employees.department_id%type
index by binary_integer;
v_employee_ids employee_ids_t;v_first_names first_names_t;v_last_names last_names_t;v_emails emails_t;v_phone_numbers phone_numbers_t;v_hire_dates hire_dates_t;v_job_ids job_ids_t;v_salarys salarys_t;v_commission_pcts commission_pcts_t;v_manager_ids manager_ids_t;v_department_ids department_ids_t;
v_emprecs Emp_Util.emprec_tab_t;begin...
end;
declare...
beginupdate employees set salary = salary * 1.1
where hire_date < = '25-JUN-97'returningemployee_id,first_name,last_name,email,phone_number,hire_date,job_id,salary,commission_pct,manager_id,department_id
bulk collect intov_employee_ids,v_first_names,v_last_names,v_emails,v_phone_numbers,v_hire_dates,v_job_ids,v_salarys,v_commission_pcts,v_manager_ids,v_department_ids;
for j in 1..v_employee_ids.Lastloopv_emprecs(j).employee_id := v_employee_ids(j);v_emprecs(j).first_name := v_first_names(j);v_emprecs(j).last_name := v_last_names(j);v_emprecs(j).email := v_emails(j);v_emprecs(j).phone_number := v_phone_numbers(j);v_emprecs(j).hire_date := v_hire_dates(j);v_emprecs(j).job_id := v_job_ids(j);v_emprecs(j).salary := v_salarys(j);v_emprecs(j).commission_pct := v_commission_pcts(j);v_emprecs(j).manager_id := v_manager_ids(j);v_emprecs(j).department_id := v_department_ids(j);
end loop;...end;
BULK UPDATE and DELETEwith RECORD bind
Even at Version 9.2.0, we don’t yet have a RETURNING * syntaxBut pre Version 9.2.0 you needed a scalar index-bytable for each target columnAgain, approaches what is feasible to maintainAgain, feels especially uncomfortable because of the artificial requirement to compromise the natural modeling approach by slicing the desired table of records vertically into N tables of scalars
UPDATE ... SET ROW =with RECORD bind
This syntax is useful when a row from one table is manipulated programmatically, and then stored in for example an auditing table with the same shape where an earlier version of the row already existsFirst, the single row syntax…
declarev_emprec employees%rowtype :=Emp_Util.Get_One_Row;
beginv_emprec.salary := v_emprec.salary * 1.2;update employees_2set row = v_emprecwhere employee_id = v_emprec.employee_id;
end;
declarev_emprec employees%rowtype := Emp_Util.Get_One_Row;
beginv_emprec.salary := v_emprec.salary * 1.2;update employeessetfirst_name = v_emprec.first_name,last_name = v_emprec.last_name,email = v_emprec.email,phone_number = v_emprec.phone_number,hire_date = v_emprec.hire_date,job_id = v_emprec.job_id,salary = v_emprec.salary,commission_pct = v_emprec.commission_pct,manager_id = v_emprec.manager_id,department_id = v_emprec.department_id
where employee_id = v_emprec.employee_id;end;
UPDATE ... SET ROW =with RECORD bind
Next, the BULK syntaxNote: you can’t reference fields of the BULK In-bind table of RECORDs in the WHERE clause…
declarev_emprecs Emp_Util.emprec_tab_t :=Emp_Util.Get_Many_Rows;
type employee_id_tab_t is table ofemployees.employee_id%typeindex by pls_integer;
v_employee_ids Employee_Id_Tab_t;Begin/* workaround for PLS-00436 */for j in v_emprecs.first..v_emprecs.last loopv_employee_ids(j) :=
v_emprecs(j).employee_id; end loop;
forall j in v_emprecs.first..v_emprecs.lastupdate employees set row = v_emprecs(j)
where employee_id = v_employee_ids(j);end;
declarev_emprecs Emp_Util.emprec_tab_t :=Emp_Util.Get_Many_Rows;
type employee_ids_t is table of employees.employee_id%typeindex by binary_integer;
type first_names_t is table of employees.first_name%typeindex by binary_integer;
type last_names_t is table of employees.last_name%typeindex by binary_integer;
type emails_t is table of employees.email%typeindex by binary_integer;
type phone_numbers_t is table of employees.phone_number%typeindex by binary_integer;
type hire_dates_t is table of employees.hire_date%typeindex by binary_integer;
type job_ids_t is table of employees.job_id%typeindex by binary_integer;
type salarys_t is table of employees.salary%typeindex by binary_integer;
type commission_pcts_t is table of employees.commission_pct%typeindex by binary_integer;
type manager_ids_t is table of employees.manager_id%typeindex by binary_integer;
type department_ids_t is table of employees.department_id%typeindex by binary_integer;
v_employee_ids employee_ids_t;v_first_names first_names_t;v_last_names last_names_t;v_emails emails_t;v_phone_numbers phone_numbers_t;v_hire_dates hire_dates_t;v_job_ids job_ids_t;v_salarys salarys_t;v_commission_pcts commission_pcts_t;v_manager_ids manager_ids_t;v_department_ids department_ids_t;
begin...
end;
declare...
beginfor j in 1..v_emprecs.Lastloopv_employee_ids(j) := v_emprecs(j).employee_id;v_first_names(j) := v_emprecs(j).first_name;v_last_names(j) := v_emprecs(j).last_name;v_emails(j) := v_emprecs(j).email;v_phone_numbers(j) := v_emprecs(j).phone_number;v_hire_dates(j) := v_emprecs(j).hire_date;v_job_ids(j) := v_emprecs(j).job_id;v_salarys(j) := v_emprecs(j).salary * 1.2;v_commission_pcts(j) := v_emprecs(j).commission_pct;v_manager_ids(j) := v_emprecs(j).manager_id;v_department_ids(j) := v_emprecs(j).department_id;
end loop;
forall j in v_emprecs.first..v_emprecs.lastupdate employeessetfirst_name = v_first_names(j),last_name = v_last_names(j),email = v_emails(j),phone_number = v_phone_numbers(j),hire_date = v_hire_dates(j),job_id = v_job_ids(j),salary = v_salarys(j),commission_pct = v_commission_pcts(j),manager_id = v_manager_ids(j),department_id = v_department_ids(j)
where employee_id = v_employee_ids(j);end;
UPDATE ... SET ROW =with RECORD bind
Pre Version 9.2.0 you needed a scalar index-bytable for each target columnAgain, approaches what is feasible to maintainAgain, feels especially uncomfortable because of the artificial requirement to compromise the natural modeling approach by slicing the desired table of records vertically into N tables of scalars
RECORD binds: summaryYou can now take advantage of the power of the PL/SQL RECORD datatype in all SELECT constructs and in all DML constructs in Static SQLThe volume of code you had to write pre Version 9.2.0 was enormously greater – verging on unmaintainable - and forced you to compromise the natural modelling approachPre Version 9.2.0 you had to copy from one representation to another. Now that you no longer need to do this, your program runs faster
Summary of 9.2.0 EnhancementsIndex-by-varchar2 tables,aka associative arrays
RECORD binds in DML andin BULK SELECTs
Utl_File enhancements
GUI debugging via JDeveloper Version 9.0.3
Utl_File enhancementsYou can now use the DIRECTORY schema object(as for BFILEs)Line length limit for Utl_File.Get_Line and Utl_File.Put_Line has been increased to 32KNew APIs to manipulate files at the operating system levelNew APIs for handling RAW dataPerformance is improved via transparent internal reimplementation
Utl_File using DIRECTORYPre Version 9.2.0, the way to denote the director(ies) for files was via the UTL_FILE_DIRinitialization parameterDisadvantages
– instance had to be bounced to make changes to the list of directories
– all users could access files on all directoriesVersion 9.2.0 allows the same mechanism to be used with Utl_File as is used for BFILEsThe UTL_FILE_DIR initialization parameter is slated for deprecation
Utl_File handling RAW dataprocedure Fseek -- go to offsetfunction Fgetpos -- report offsetprocedure Get_Rawprocedure Put_Raw
Utl_File - summaryFunctionality now provided natively for a number of common file i/o tasks
– dramatically reduces the amount of code you need to write
– delivers better performancePerformance improvement further enhanced by some transparent internal changesRobustness improved by removing some uncomfortable limitsSecurity improved by adopting the BFILE model
Summary of 9.2.0 EnhancementsIndex-by-varchar2 tables,aka associative arrays
RECORD binds in DML andin BULK SELECTs
Utl_File enhancements
GUI debuggingvia JDeveloper Version 9.0.3
GUI PL/SQL debuggingvia JDeveloper Version 9.0.3
JDeveloper Version 9.0.3 will very soon be available for download from OTNIts PL/SQL IDE subcomponent is extended to provide support for graphical debugging of PL/SQL stored subprogramsThis includes support for all the new features discussed in this presentation
Connection modelJDeveloper (when executing a PL/SQL subprogram) connects as a classical Oracle client via Oracle NetOr, any Oracle client connectsThe client (implicitly or explicitly) requests debuggingThe shadow process connects back to JDeveloper(or any equivalent 3rd party tool)via the Java Debugging Wire Protocol aka JDWP
Connection modelDbms_Debug_Jdwp.Connect_Tcp( :the_node, :the_port, ... )
Usually called transparently by the debugging tool or by some client or middle-tier infrastructure
Connection modelJava Debugging Wire Protocol aka JDWP
– industry standard invented to support Java debugging
– completely suitable for PL/SQL too– allows 3rd party tools vendors to implement
PL/SQL debuggingAt the same protocol level as say HTTP orOracle NetTypically implemented on top of TCP/IP
Connection modelJDWP also allows JDeveloper and other 3rd party tools to debug database stored JavaWhen debugging an application implemented in a mix of Java and PL/SQL
– execution point moves seamlessly from one environment to the other
– integrity of the call stack is maintained across the language boundary
Connection modelJDeveloper incorporates a JDWP listenerCan be started on any portCan spawn any number of debugging sessions running concurrently - cf tnslsnrThe Oracle shadow processes which are the debugging clients could be running on different machines, communicating with each other via say Oracle AQ
Connection model
tnstnslistenerlistener
Oracle NetOracle Net
JDeveloperJDeveloper
JWDPJWDPListenerListener
ShadowShadowProcessProcess
OracleOracleClientClient
DebuggingDebuggingServerServerJDWPJDWP
Pre-conditions for debuggingUser must have debug connect session and debug any procedure system privilegesmust have execute privilege on the subprograms of interestsource code must not be wrappedmust have been compiled with debug information, eg…
alter [ package | ... ] P compile debug;
GUI-initiated debugging modeA debugging exercise always involves opening the source code read from the database in the source windowThis is how the current execution point is displayed and how you set breakpoints
GUI-initiated debugging modeIn many scenarios you’re happy to kick off debugging directly from JDeveloperYou choose your top level subprogram and set its actual parameter values via the same GUI used just to execute subprograms for testingThe magic to control the JDWP listener is implicitIn the default configuration JDeveloper chooses the first available port automaticallyTo debug code which is invoked when a trigger fires, write a small procedure to invoke the SQL
Remote debugging modeWhat if the server-side PL/SQL which you want to debug is invoked from a client?
– Pro*C or Java application in a classical two-tier model
– mod_plsql component of Oracle9iAS as the middle tier in a three-tier model
The parameters that define the debugging case of interest are set by application logicCalling your subprogram with these values by hand is too tortuous
Remote debugging modeYou could change your application code to call the Dbms_Debug_Jdwp APIsBut this invasive approach is uncomfortableIn the two-tier case, use the environment variable ora_debug_jdwp, eg…
set ora_debug_jdwp=host=lap99.acme.com;port=2125
Read by the OCI layer which then calls Dbms_Debug_Jdwp.Connect_Tcpimmediately on connection
Remote debugging modeJDeveloper (or an equivalent third party tool) must be running on the indicated nodeIts JDWP listener must have been started manually to listen on the indicated portDo this by choosing remote debugging rather than the default GUI-initiated debugging mode via a UI that allows selection of the port
Remote debugging modeWhat if the client is mod_plsql ?Want to turn on debugging for the server-side PL/SQL that supports a particular browser (pseudo) sessionAnd not to turn it on for other concurrent browser sessionsThe debugging user sets a cookie via thebrowser UI – specifying JDWP host and portCauses mod_plsql to call Connect_Tcp before its normal calls and Disconnect after these
Remote debugging modeIf you use…
– a jdbc:thin Java client– Or some middle tier infrastructure other than
Oracle9iAS's mod_plsql…then you'll need to make calls to the Dbms_Debug_Jdwp API in yourself in your production code
Debugger featuresGUI-initiated and remote debugging modesCan support one or many concurrent debugging clients and thus debug interacting processesCurrent execution point displayed as highlighted line in the source extracted dynamically from the database where the code is executingBreakpoints can be set and unset before and during the debugging session. Displayed as highlighted line in source windowStart debugging session with Step Into, Step Overor Run to First Breakpoint
Debugger featuresWhen paused, can continue with Resume (aka Run to Next Breakpoint), Step Into or Step OverOr Abort the current debugging sessionCan attach condition to a breakpoint to determine if execution stops there or notWhen paused, can view the values of variables visible in the current subprogram, and the values for all variables that are currently “alive”Intuitive display for collection objects and objects of user-defined abstract datatypes
Debugger featuresCall stack displayCan click anywhere in the stack to view the values of variables that belong to the selected subprogramIntuitive display for recursive callsWhen paused, can modify the values of variables
normal caveats regarding what you can then deduce about your program's subsequent behavior!
Debugger scenarioAn index-by- pls_integer table of RECORDs of employees%rowtype is populated byBULK SELECT using Native Dynamic SQLThe index-by table is inserted into a second database table with the same shape with BULK INSERT using Static SQL with thesave exceptions constructThe table has a trigger which raises an exception on conditions of these dataAn exception handler traverses the index-by tableof exception codes
Debugger scenarioThe contents of the target table are selected using the same subprogram as at the startBut now the Native Dynamic SQL uses a different table nameThis confirms the successful insert of those rows for which an exception was not raised
Debugger scenarioThe Data Window shows the state of the variables of the function that does the BULK SELECT when paused as in the earlier screenshotThe second element of the emprecs index-by tableis expanded to show the values of each field in the RECORD…
PL/SQL Debugging at 9.0.1and earlier
JDeveloper Version 9.0.3 also implements PL/SQL debugging via the earlier Dbms_Debug API to allow it to be used against database versions earlier than Version 9.2.0
PL/SQL debugging: summaryValue of a GUI debugger is self-evident!Consider just index-by tables of RECORDsNo PL/SQL syntax to display all fields in each row of such a structureEffort required to program a loop to do this via Dbms_Output is time-consuming and potentially error-proneJDeveloper provides ready-made intuitive mechanisms for displaying the values of arbitrarily complex structures
Oracle9i in Action170 Systems, Inc have been an Oracle Partner for eleven years and participated in the Beta Program for the Oracle9i Database with particular interest in PL/SQL Native CompilationThey have now certified their 170 MarkView Document Management and Imaging System™against Oracle9i Vesrion 9.2.0
170 MarkViewDocument Managementand Imaging System
Provides Content Management, Document Management, Imaging and Workflow solutionsTightly integrated with the Oracle9i Database, Oracle9i Application Server andthe Oracle E-Business SuiteEnables businesses to capture and manage all of their information online in a single, unified system
170 MarkView™Large-scale multi-user, multi-access systemCustomers include…
– British Telecommunications– E*TRADE Group– the Apollo Group– the University of Pennsylvania
Very large numbers of documents, images, concurrent users, and high transaction ratesPerformance and scalability especially important
170 Systems, IncPlanning to take advantage of many of the new Version 9.2.0 PL/SQL featureseg Associative Arrays to improve performance in an application that performs complex stack manipulationsValues are taken from the stack using random access based on a identifying character namePreviously, this was simulated using hashing routines written using PLSQLTherefore a performance improvement is expected
170 Systems, IncTested the JDeveloper PL/SQL debugging environment extensivelyThey like itPlan to adopt it as their PL/SQL debugging environment of choice for their developers
Summary of 9.2.0 EnhancementsIndex-by-varchar2 tables,aka associative arrays
RECORD binds in DML andin BULK SELECTs
Utl_File enhancements
GUI debugging via JDeveloper Version 9.0.3
Summary of 9.2.0 EnhancementsAll the enhancements discussed in this presentation dramatically reduce the effort of program design, implementation and testing to build applications with matching requirementsAnd Index-by-varchar2 tables, RECORD binds and the Utl_File enhancements also deliver more performant programs
you should upgrade !you should upgrade !