您的位置:首页 > 数据库 > Oracle

Oracle PL/SQL 优化与调整 -- Bulk 说明

2011-06-30 23:10 543 查看
一.Bulk概述

本来只想测试一下BulkCollect和update性能的,但发现Bulk的东西还是很多的,在OTN上搜了一些,整理如下。

1.1BulkBinding和BulkSQL

From:http://download.oracle.com/docs/cd/E11882_01/appdev.112/e17125/adfns_packages.htm#ADFNS343

OracleDatabaseusestwoenginestorunPL/SQLblocksandsubprograms.ThePL/SQLenginerunsproceduralstatements,whiletheSQLenginerunsSQLstatements.Duringexecution,everySQLstatementcausesacontextswitchbetweenthetwoengines,resultinginperformanceoverhead.

--Oracle使用2个引擎来执行SQL和代码块:SQL引擎和PL/SQL引擎,SQL语句会导致在两个引擎之间进行contextswitch,从而影响性能。

Performancecanbeimprovedsubstantiallybyminimizingthenumberofcontextswitchesrequiredtorunaparticularblockorsubprogram.WhenaSQLstatementrunsinsidealoopthatusescollectionelementsasbindvariables,thelargenumberofcontextswitchesrequiredbytheblockcancausepoorperformance.Collectionsinclude:

(1)Varrays

(2)Nestedtables

(3)Index-bytables

(4)Hostarrays

--从本质上讲,使用特殊的block或者subprogram来降低contextswitches可以提高性能。当SQL语句在loop内使用collectionelements作为bindvariables来运行时,就会产生大量的contextswitches。

BulkSQLminimizestheperformanceoverheadofthecommunicationbetweenPL/SQLandSQL.

PL/SQLandSQLcommunicateasfollows:

TorunaSELECTINTOorDMLstatement,thePL/SQLenginesendsthequeryorDMLstatementtotheSQLengine.TheSQLenginerunsthequeryorDMLstatementandreturnstheresulttothePL/SQLengine.

--PL/SQL和SQL引擎的交流方式

ThePL/SQLfeaturesthatcomprisebulkSQLaretheFORALLstatementandtheBULKCOLLECTclause.

TheFORALLstatementsendsDMLstatementsfromPL/SQLtoSQLinbatchesratherthanoneatatime.

TheBULKCOLLECTclausereturnsresultsfromSQLtoPL/SQLinbatchesratherthanoneatatime.IfaqueryorDMLstatementaffectsfourormoredatabaserows,thenbulkSQLcansignificantlyimproveperformance.

AssigningvaluestoPL/SQLvariablesthatappearinSQLstatementsiscalledbinding.

PL/SQLbindingoperationsfallintothesecategories:

BindingCategory

WhenThisBindingOccurs

In-bind

WhenanINSERTorUPDATEstatementstoresaPL/SQLorhostvariableinthedatabase

Out-bind

WhentheRETURNINGINTOclauseofanINSERT,UPDATE,orDELETEstatementassignsadatabasevaluetoaPL/SQLorhostvariable

DEFINE

WhenaSELECTorFETCHstatementassignsadatabasevaluetoaPL/SQLorhostvariable

Forin-bindsandout-binds,bulkSQLusesbulkbinding;thatis,itbindsanentirecollectionofvaluesatonce.

Foracollectionofnelements,bulkSQLusesasingleoperationtoperformtheequivalentofnSELECTINTOorDMLstatements.AquerythatusesbulkSQLcanreturnanynumberofrows,withoutusingaFETCHstatementforeachone.

BindingistheassignmentofvaluestoPL/SQLvariablesinSQLstatements.Bulkbindingisbindinganentirecollectionatonce.Bulkbindspasstheentirecollectionbackandforthbetweenthetwoenginesinasingleoperation.

--Binding是在SQL语句里分配一个value给PL/SQL变量

--BulkBinding是一次分配所有的数据,然后通过这个entirecollection,在一个操作就可以完成两个引擎处理。

Typically,usingbulkbindsimprovesperformanceforSQLstatementsthataffectfourormoredatabaserows.ThemorerowsaffectedbyaSQLstatement,thegreatertheperformancegainfrombulkbinds.

注意:

ParallelDMLstatementsaredisabledwithbulkbindsandbulkSQL.

并行的DML操作会禁用bulkbinds和bulkSQL.

Note:

ThissectionprovidesanoverviewofbulkbindstohelpyoudecidewhethertousetheminyourPL/SQLapplications.Fordetailedinformationaboutusingbulkbinds,includingwaystohandleexceptionsthatoccurinthemiddleofabulkbindoperation,seeOracleDatabasePL/SQLLanguageReference.

1.2WhentoUseBulkBinds

Considerusingbulkbindstoimprovetheperformanceof:

DMLStatementsthatReferenceCollections

SELECTStatementsthatReferenceCollections

FORLoopsthatReferenceCollectionsandReturnDML

1.2.1DMLStatementsthatReferenceCollections

Abulkbind,whichusestheFORALLkeyword,canimprovetheperformanceofINSERT,UPDATE,orDELETEstatementsthatreferencecollectionelements.

ThePL/SQLblockinExample6-9increasesthesalaryforemployeeswhosemanager'sIDnumberis7902,7698,or7839,withandwithoutbulkbinds.Withoutbulkbind,PL/SQLsendsaSQLstatementtotheSQLengineforeachupdatedemployee,leadingtocontextswitchesthatslowperformance.

Example6-9DMLStatementsthatReferenceCollections

declare

typenumlistisvarray(100)ofnumber;

idnumlist:=numlist(7902,7698,7839);

begin

--Efficientmethod,usingbulkbind:

foralliinid.first..id.last

updateemployees

setsalary=1.1*salary

wheremanager_id=id(i);

--Slowermethod:

foriinid.first..id.lastloop

updateemployees

setsalary=1.1*salary

wheremanager_id=id(i);

endloop;

end;

/

1.2.2SELECTStatementsthatReferenceCollections

TheBULKCOLLECTINTOclausecanimprovetheperformanceofqueriesthatreferencecollections.YoucanuseBULKCOLLECTINTOwithtablesofscalarvalues,ortablesof%TYPEvalues.

ThePL/SQLblockinExample6-10queriesmultiplevaluesintoPL/SQLtables,withandwithoutbulkbinds.Withoutbulkbind,PL/SQLsendsaSQLstatementtotheSQLengineforeachselectedemployee,leadingtocontextswitchesthatslowperformance.

Example6-10SELECTStatementsthatReferenceCollections

declare

typevar_tabistableofvarchar2(20)

indexbypls_integer;

empnovar_tab;

enamevar_tab;

counternumber;

cursorcis

selectemployee_id,last_name

fromemployees

wheremanager_id=7698;

begin

--Efficientmethod,usingbulkbind:

selectemployee_id,last_namebulkcollect

intoempno,ename

fromemployees

wheremanager_id=7698;

--Slowermethod:

counter:=1;

forrecincloop

empno(counter):=rec.employee_id;

ename(counter):=rec.last_name;

counter:=counter+1;

endloop;

end;

/

1.2.3FORLoopsthatReferenceCollectionsandReturnDML

YoucanusetheFORALLkeywordwiththeBULKCOLLECTINTOkeywordstoimprovetheperformanceofFORloopsthatreferencecollectionsandreturnDML.

ThePL/SQLblockinExample6-11updatestheEMPLOYEEStablebycomputingbonusesforacollectionofemployees.Thenitreturnsthebonusesinacolumncalledbonus_list_inst.Theactionsareperformedwithandwithoutbulkbinds.Withoutbulkbind,PL/SQLsendsaSQLstatementtotheSQLengineforeachupdatedemployee,leadingtocontextswitchesthatslowperformance.

Example6-11FORLoopsthatReferenceCollectionsandReturnDML

declare

typeemp_listisvarray(100)ofemployees.employee_id%type;

empidsemp_list:=emp_list(182,187,193,200,204,206);

typebonus_lististableofemployees.salary%type;

bonus_list_instbonus_list;

begin

--Efficientmethod,usingbulkbind:

foralliinempids.first..empids.last

updateemployees

setsalary=0.1*salary

whereemployee_id=empids(i)

returningsalarybulkcollectintobonus_list_inst;

--Slowermethod:

foriinempids.first..empids.lastloop

updateemployees

setsalary=0.1*salary

whereemployee_id=empids(i)

returningsalaryintobonus_list_inst(i);

endloop;

end;

/

1.3Triggers

AtriggerisaspecialkindofPL/SQLanonymousblock.YoucandefinetriggerstofirebeforeorafterSQLstatements,eitheronastatementlevelorforeachrowthatisaffected.YoucanalsodefineINSTEADOFtriggersorsystemtriggers(triggersonDATABASEandSCHEMA).

二.有关BulkSQL和BulkBinding的更多示例

From:

BulkSQLandBulkBinding

http://download.oracle.com/docs/cd/E11882_01/appdev.112/e17126/tuning.htm

2.1FORALLStatement

TheFORALLstatement,afeatureofbulkSQL,sendsDMLstatementsfromPL/SQLtoSQLinbatchesratherthanoneatatime.

TounderstandtheFORALLstatement,firstconsidertheFORLOOPstatementinExample12-7.ItsendstheseDMLstatementsfromPL/SQLtoSQLoneatatime:

deletefromemployees_tempwheredepartment_id=depts(10);

deletefromemployees_tempwheredepartment_id=depts(30);

deletefromemployees_tempwheredepartment_id=depts(70);

Example12-7DELETEStatementinFORLOOPStatement

DROPTABLEemployees_temp;

CREATETABLEemployees_tempASSELECT*FROMemployees;

DECLARE

TYPENumListISVARRAY(20)OFNUMBER;

deptsNumList:=NumList(10,30,70);--departmentnumbers

BEGIN

FORiINdepts.FIRST..depts.LASTLOOP

DELETEFROMemployees_temp

WHEREdepartment_id=depts(i);

ENDLOOP;

END;

/

NowconsidertheFORALLstatementinExample12-8.ItsendsthesamethreeDMLstatementsfromPL/SQLtoSQLasabatch.

Example12-8DELETEStatementinFORALLStatement

DROPTABLEemployees_temp;

CREATETABLEemployees_tempASSELECT*FROMemployees;

DECLARE

TYPENumListISVARRAY(20)OFNUMBER;

deptsNumList:=NumList(10,30,70);--departmentnumbers

BEGIN

FORALLiINdepts.FIRST..depts.LAST

DELETEFROMemployees_temp

WHEREdepartment_id=depts(i);

END;

/

AFORALLstatementisusuallymuchfasterthananequivalentFORLOOPstatement.However,aFORLOOPstatementcancontainmultipleDMLstatements,whileaFORALLstatementcancontainonlyone.

--FORALL只能包含一条DML语句,而FORLOOP可以包含多条

ThebatchofDMLstatementsthataFORALLstatementsendstoSQLdifferonlyintheirVALUESandWHEREclauses.Thevaluesinthoseclausesmustcomefromexisting,populatedcollections.

Note:

TheDMLstatementinaFORALLstatementcanreferencemultiplecollections,butperformancebenefitsapplyonlytocollectionreferencesthatusetheFORALLindexvariableasanindex.

Example12-9insertsthesamecollectionelementsintotwodatabasetables,usingaFORLOOPstatementforthefirsttableandaFORALLstatementforthesecondtableandshowinghowlongeachstatementtakes.(Timesvaryfromruntorun.)

Example12-9TimeDifferenceforINSERTStatementinFORLOOPandFORALLStatements

DROPTABLEparts1;

CREATETABLEparts1(

pnumINTEGER,

pnameVARCHAR2(15)

);

DROPTABLEparts2;

CREATETABLEparts2(

pnumINTEGER,

pnameVARCHAR2(15)

);

DECLARE

TYPENumTabISTABLEOFparts1.pnum%TYPEINDEXBYPLS_INTEGER;

TYPENameTabISTABLEOFparts1.pname%TYPEINDEXBYPLS_INTEGER;

pnumsNumTab;

pnamesNameTab;

iterationsCONSTANTPLS_INTEGER:=50000;

t1INTEGER;

t2INTEGER;

t3INTEGER;

BEGIN

FORjIN1..iterationsLOOP--populatecollections

pnums(j):=j;

pnames(j):='PartNo.'||TO_CHAR(j);

ENDLOOP;

t1:=DBMS_UTILITY.get_time;

FORiIN1..iterationsLOOP

INSERTINTOparts1(pnum,pname)

VALUES(pnums(i),pnames(i));

ENDLOOP;

t2:=DBMS_UTILITY.get_time;

FORALLiIN1..iterations

INSERTINTOparts2(pnum,pname)

VALUES(pnums(i),pnames(i));

t3:=DBMS_UTILITY.get_time;

DBMS_OUTPUT.PUT_LINE('ExecutionTime(secs)');

DBMS_OUTPUT.PUT_LINE('---------------------');

DBMS_OUTPUT.PUT_LINE('FORLOOP:'||TO_CHAR((t2-t1)/100));

DBMS_OUTPUT.PUT_LINE('FORALL:'||TO_CHAR((t3-t2)/100));

COMMIT;

END;

/

Resultissimilarto:

ExecutionTime(secs)

---------------------

FORLOOP:2.16

FORALL:.11

PL/SQLproceduresuccessfullycompleted.

InExample12-10,theFORALLstatementappliestoasubsetofacollection.

Example12-10FORALLStatementforSubsetofCollection

DROPTABLEemployees_temp;

CREATETABLEemployees_tempASSELECT*FROMemployees;

DECLARE

TYPENumListISVARRAY(10)OFNUMBER;

deptsNumList:=NumList(5,10,20,30,50,55,57,60,70,75);

BEGIN

FORALLjIN4..7

DELETEFROMemployees_tempWHEREdepartment_id=depts(j);

END;

/

2.2FORALLStatementsforSparseCollections

IftheFORALLstatementboundsclausereferencesasparsecollection,thenspecifyonlyexistingindexvalues,usingeithertheINDICESOForVALUESOFclause.YoucanuseINDICESOFforanycollectionexceptanassociativearrayindexedbystring.YoucanuseVALUESOFonlyforacollectionofPLS_INTEGERelementsindexedbyPLS_INTEGER.

AcollectionofPLS_INTEGERelementsindexedbyPLS_INTEGERcanbeanindexcollection;thatis,acollectionofpointerstoelementsofanothercollection(theindexedcollection).

IndexcollectionsareusefulforprocessingdifferentsubsetsofthesamecollectionwithdifferentFORALLstatements.Insteadofcopyingelementsoftheoriginalcollectionintonewcollectionsthatrepresentthesubsets(whichcanusesignificanttimeandmemory),representeachsubsetwithanindexcollectionandthenuseeachindexcollectionintheVALUESOFclauseofadifferentFORALLstatement.

Example12-11usesaFORALLstatementwiththeINDICESOFclausetopopulateatablewiththeelementsofasparsecollection.ThenitusestwoFORALLstatementswithVALUESOFclausestopopulatetwotableswithsubsetsofacollection.

Example12-11FORALLStatementsforSparseCollectionandItsSubsets

DROPTABLEvalid_orders;

CREATETABLEvalid_orders(

cust_nameVARCHAR2(32),

amountNUMBER(10,2)

);

DROPTABLEbig_orders;

CREATETABLEbig_ordersAS

SELECT*FROMvalid_orders

WHERE1=0;

DROPTABLErejected_orders;

CREATETABLErejected_ordersAS

SELECT*FROMvalid_orders

WHERE1=0;

DECLARE

SUBTYPEcust_nameISvalid_orders.cust_name%TYPE;

TYPEcust_typISTABLEOFcust_name;

cust_tabcust_typ;--Collectionofcustomernames

SUBTYPEorder_amountISvalid_orders.amount%TYPE;

TYPEamount_typISTABLEOFNUMBER;

amount_tabamount_typ;--Collectionoforderamounts

TYPEindex_pointer_tISTABLEOFPLS_INTEGER;

/*Collectionsforpointerstoelementsofcust_tabcollection

(torepresenttwosubsetsofcust_tab):*/

big_order_tabindex_pointer_t:=index_pointer_t();

rejected_order_tabindex_pointer_t:=index_pointer_t();

PROCEDUREpopulate_data_collectionsIS

BEGIN

cust_tab:=cust_typ(

'Company1','Company2','Company3','Company4','Company5'

);

amount_tab:=amount_typ(5000.01,0,150.25,4000.00,NULL);

END;

BEGIN

populate_data_collections;

DBMS_OUTPUT.PUT_LINE('---Originalorderdata---');

FORiIN1..cust_tab.LASTLOOP

DBMS_OUTPUT.PUT_LINE(

'Customer#'||i||','||cust_tab(i)||':$'||amount_tab(i)

);

ENDLOOP;

--Deleteinvalidorders:

FORiIN1..cust_tab.LASTLOOP

IFamount_tab(i)ISNULLORamount_tab(i)=0THEN

cust_tab.delete(i);

amount_tab.delete(i);

ENDIF;

ENDLOOP;

--cust_tabisnowasparsecollection.

DBMS_OUTPUT.PUT_LINE('---Orderdatawithinvalidordersdeleted---');

FORiIN1..cust_tab.LASTLOOP

IFcust_tab.EXISTS(i)THEN

DBMS_OUTPUT.PUT_LINE(

'Customer#'||i||','||cust_tab(i)||':$'||amount_tab(i)

);

ENDIF;

ENDLOOP;

--Usingsparsecollection,populatevalid_orderstable:

FORALLiININDICESOFcust_tab
INSERTINTOvalid_orders(cust_name,amount)

VALUES(cust_tab(i),amount_tab(i));

populate_data_collections;--Restoreoriginalorderdata

--cust_tabisadensecollectionagain.

/*Populatecollectionsofpointerstoelementsofcust_tabcollection

(whichrepresenttwosubsetsofcust_tab):*/

FORiINcust_tab.FIRST..cust_tab.LASTLOOP

IFamount_tab(i)ISNULLORamount_tab(i)=0THEN

rejected_order_tab.EXTEND;

rejected_order_tab(rejected_order_tab.LAST):=i;

ENDIF;

IFamount_tab(i)>2000THEN

big_order_tab.EXTEND;

big_order_tab(big_order_tab.LAST):=i;

ENDIF;

ENDLOOP;

/*UsingeachsubsetinadifferentFORALLstatement,

populaterejected_ordersandbig_orderstables:*/

FORALLiINVALUESOFrejected_order_tab

INSERTINTOrejected_orders(cust_name,amount)

VALUES(cust_tab(i),amount_tab(i));

FORALLiINVALUESOFbig_order_tab

INSERTINTObig_orders(cust_name,amount)

VALUES(cust_tab(i),amount_tab(i));

END;

/

2.3UnhandledExceptionsinFORALLStatements

InaFORALLstatementwithouttheSAVEEXCEPTIONSclause,ifoneDMLstatementraisesanunhandledexception,thenPL/SQLstopstheFORALLstatementandrollsbackallchangesmadebypreviousDMLstatements.

Forexample,theFORALLstatementinExample12-8executestheseDMLstatementsinthisorder,unlessoneofthemraisesanunhandledexception:

DELETEFROMemployees_tempWHEREdepartment_id=depts(10);

DELETEFROMemployees_tempWHEREdepartment_id=depts(30);

DELETEFROMemployees_tempWHEREdepartment_id=depts(70);

Ifthethirdstatementraisesanunhandledexception,thenPL/SQLrollsbackthechangesthatthefirstandsecondstatementsmade.Ifthesecondstatementraisesanunhandledexception,thenPL/SQLrollsbackthechangesthatthefirststatementmadeandneverrunsthethirdstatement.

YoucanhandleexceptionsraisedinaFORALLstatementineitheroftheseways:

(1)Aseachexceptionisraised(see"HandlingFORALLExceptionsImmediately")

(2)AftertheFORALLstatementcompletesexecution,byincludingtheSAVEEXCEPTIONSclause(see"HandlingFORALLExceptionsAfterFORALLStatementCompletes")

2.4HandlingFORALLExceptionsImmediately

TohandleexceptionsraisedinaFORALLstatementimmediately,omittheSAVEEXCEPTIONSclauseandwritetheappropriateexceptionhandlers.(Forinformationaboutexceptionhandlers,seeChapter11,"PL/SQLErrorHandling.")IfoneDMLstatementraisesahandledexception,thenPL/SQLrollsbackthechangesmadebythatstatement,butdoesnotrollbackchangesmadebypreviousDMLstatements.

InExample12-12,theFORALLstatementisdesignedtorunthreeUPDATEstatements.However,thesecondoneraisesanexception.Anexceptionhandlerhandlestheexception,displayingtheerrormessageandcommittingthechangemadebythefirstUPDATEstatement.ThethirdUPDATEstatementneverruns.

Example12-12HandlingFORALLExceptionsImmediately

DROPTABLEemp_temp;

CREATETABLEemp_temp(

deptnoNUMBER(2),

jobVARCHAR2(18)

);

CREATEORREPLACEPROCEDUREpAUTHIDDEFINERAS

TYPENumListISTABLEOFNUMBER;

deptsNumList:=NumList(10,20,30);

error_messageVARCHAR2(100);

BEGIN

--Populatetable:

INSERTINTOemp_temp(deptno,job)VALUES(10,'Clerk');

INSERTINTOemp_temp(deptno,job)VALUES(20,'Bookkeeper');

INSERTINTOemp_temp(deptno,job)VALUES(30,'Analyst');

COMMIT;

--Append9-characterstringtoeachjob:

FORALLjINdepts.FIRST..depts.LAST

UPDATEemp_tempSETjob=job||'(Senior)'

WHEREdeptno=depts(j);

EXCEPTION

WHENOTHERSTHEN

error_message:=SQLERRM;

DBMS_OUTPUT.PUT_LINE(error_message);

COMMIT;--Commitresultsofsuccessfulupdates

RAISE;

END;

/

Result:

Procedurecreated.

Invokeprocedure:

BEGIN

p;

END;

/

Result:

ORA-12899:valuetoolargeforcolumn"HR"."EMP_TEMP"."JOB"(actual:19,

maximum:18)

ORA-06512:at"HR.P",line27

ORA-06512:atline2

PL/SQLproceduresuccessfullycompleted.

Query:

SELECT*FROMemp_temp;

Result:

DEPTNOJOB

----------------------------

10Clerk(Senior)

20Bookkeeper

30Analyst

3rowsselected.

2.5HandlingFORALLExceptionsAfterFORALLStatementCompletes

ToallowaFORALLstatementtocontinueevenifsomeofitsDMLstatementsfail,includetheSAVEEXCEPTIONSclause.WhenaDMLstatementfails,PL/SQLdoesnotraiseanexception;instead,itsavesinformationaboutthefailure.AftertheFORALLstatementcompletes,PL/SQLraisesasingleexceptionfortheFORALLstatement(ORA-24381).IntheexceptionhandlerforORA-24381,youcangetinformationabouteachindividualDMLstatementfailurefromtheimplicitcursorattributeSQL%BULK_EXCEPTIONS.

SQL%BULK_EXCEPTIONSislikeanassociativearrayofinformationabouttheDMLstatementsthatfailedduringthemostrecentlyrunFORALLstatement.

SQL%BULK_EXCEPTIONS.COUNTisthenumberofDMLstatementsthatfailed.IfSQL%BULK_EXCEPTIONS.COUNTisnotzero,thenforeachindexvalueifrom1throughSQL%BULK_EXCEPTIONS.COUNT:

(1)SQL%BULK_EXCEPTIONS(i).ERROR_INDEXisthenumberoftheDMLstatementthatfailed.

(2)SQL%BULK_EXCEPTIONS(i).ERROR_CODEistheOracleDatabaseerrorcodeforthefailure.

Forexample,ifaFORALLSAVEEXCEPTIONSstatementruns100DMLstatements,andthetenthandsixty-fourthonesfailwitherrorcodesORA-12899andORA-19278,respectively,then:

SQL%BULK_EXCEPTIONS.COUNT=2

SQL%BULK_EXCEPTIONS(1).ERROR_INDEX=10

SQL%BULK_EXCEPTIONS(1).ERROR_CODE=12899

SQL%BULK_EXCEPTIONS(2).ERROR_INDEX=64

SQL%BULK_EXCEPTIONS(2).ERROR_CODE=19278

Note:

AfteraFORALLstatementwithouttheSAVEEXCEPTIONSclauseraisesanexception,SQL%BULK_EXCEPTIONS.COUNT=1.

Withtheerrorcode,youcangettheassociatederrormessagewiththeSQLERRMfunction(describedin"SQLERRMFunction"):

SQLERRM(-(SQL%BULK_EXCEPTIONS(i).ERROR_CODE))

However,theerrormessagethatSQLERRMreturnsexcludesanysubstitutionarguments(comparetheerrormessagesinExample12-12andExample12-13).

Example12-13islikeExample12-12except:

(1)TheFORALLstatementincludestheSAVEEXCEPTIONSclause.

(2)Theexception-handlingparthasanexceptionhandlerforORA-24381,theinternallydefinedexceptionthatPL/SQLraisesimplicitlywhenabulkoperationraisesandsavesexceptions.TheexamplegivesORA-24381theuser-definednamedml_errors.

(3)Theexceptionhandlerfordml_errorsusesSQL%BULK_EXCEPTIONSandSQLERRM(andsomelocalvariables)toshowtheerrormessageandwhichstatement,collectionitem,andstringcausedtheerror.

Example12-13HandlingFORALLExceptionsAfterFORALLStatementCompletes

CREATEORREPLACEPROCEDUREpAUTHIDDEFINERAS

TYPENumListISTABLEOFNUMBER;

deptsNumList:=NumList(10,20,30);

error_messageVARCHAR2(100);

bad_stmt_noPLS_INTEGER;

bad_deptnoemp_temp.deptno%TYPE;

bad_jobemp_temp.job%TYPE;

dml_errorsEXCEPTION;

PRAGMAEXCEPTION_INIT(dml_errors,-24381);

BEGIN

--Populatetable:

INSERTINTOemp_temp(deptno,job)VALUES(10,'Clerk');

INSERTINTOemp_temp(deptno,job)VALUES(20,'Bookkeeper');

INSERTINTOemp_temp(deptno,job)VALUES(30,'Analyst');

COMMIT;

--Append9-characterstringtoeachjob:

FORALLjINdepts.FIRST..depts.LASTSAVEEXCEPTIONS

UPDATEemp_tempSETjob=job||'(Senior)'

WHEREdeptno=depts(j);

EXCEPTION

WHENdml_errorsTHEN

FORiIN1..SQL%BULK_EXCEPTIONS.COUNTLOOP

error_message:=SQLERRM(-(SQL%BULK_EXCEPTIONS(i).ERROR_CODE));

DBMS_OUTPUT.PUT_LINE(error_message);

bad_stmt_no:=SQL%BULK_EXCEPTIONS(i).ERROR_INDEX;

DBMS_OUTPUT.PUT_LINE('Badstatement#:'||bad_stmt_no);

bad_deptno:=depts(bad_stmt_no);

DBMS_OUTPUT.PUT_LINE('Baddepartment#:'||bad_deptno);

SELECTjobINTObad_jobFROMemp_tempWHEREdeptno=bad_deptno;

DBMS_OUTPUT.PUT_LINE('Badjob:'||bad_job);

ENDLOOP;

COMMIT;--Commitresultsofsuccessfulupdates

WHENOTHERSTHEN

DBMS_OUTPUT.PUT_LINE('Unrecognizederror.');

RAISE;

END;

/

Result:

Procedurecreated.

Invokeprocedure:

BEGIN

p;

END;

/

Result:

ORA-12899:valuetoolargeforcolumn(actual:,maximum:)

Badstatement#:2

Baddepartment#:20

Badjob:Bookkeeper

PL/SQLproceduresuccessfullycompleted.

Query:

SELECT*FROMemp_temp;

Result:

DEPTNOJOB

----------------------------

10Clerk(Senior)

20Bookkeeper

30Analyst(Senior)

3rowsselected.

2.6SparseCollectionsandSQL%BULK_EXCEPTIONS

IftheFORALLstatementboundsclausereferencesasparsecollection,thentofindthecollectionelementthatcausedaDMLstatementtofail,youmuststepthroughtheelementsonebyoneuntilyoufindtheelementwhoseindexisSQL%BULK_EXCEPTIONS(i).ERROR_INDEX.Then,iftheFORALLstatementusestheVALUESOFclausetoreferenceacollectionofpointersintoanothercollection,youmustfindtheelementoftheothercollectionwhoseindexisSQL%BULK_EXCEPTIONS(i).ERROR_INDEX.

2.7GettingNumberofRowsAffectedbyFORALLStatement

AfteraFORALLstatementcompletes,youcangetthenumberofrowsthateachDMLstatementaffectedfromtheimplicitcursorattributeSQL%BULK_ROWCOUNT.(TogetthetotalnumberofrowsaffectedbytheFORALLstatement,usetheimplicitcursorattributeSQL%ROWCOUNT,describedin"SQL%ROWCOUNTAttribute:HowManyRowsWereAffected?".)

SQL%BULK_ROWCOUNTislikeanassociativearraywhoseithelementisthenumberofrowsaffectedbytheithDMLstatementinthemostrecentlycompletedFORALLstatement.

Example12-14usesSQL%BULK_ROWCOUNTtoshowhowmanyrowseachDELETEstatementintheFORALLstatementdeletedandSQL%ROWCOUNTtoshowthetotalnumberofrowsdeleted.

Example12-14ShowingNumberofRowsAffectedbyEachDELETEinFORALL

DROPTABLEemp_temp;

CREATETABLEemp_tempASSELECT*FROMemployees;

DECLARE

TYPENumListISTABLEOFNUMBER;

deptsNumList:=NumList(30,50,60);

BEGIN

FORALLjINdepts.FIRST..depts.LAST

DELETEFROMemp_tempWHEREdepartment_id=depts(j);

FORiINdepts.FIRST..depts.LASTLOOP

DBMS_OUTPUT.PUT_LINE(

'Statement#'||i||'deleted'||

SQL%BULK_ROWCOUNT(i)||'rows.'

);

ENDLOOP;

DBMS_OUTPUT.PUT_LINE('Totalrowsdeleted:'||SQL%ROWCOUNT);

END;

/

Result:

Statement#1deleted6rows.

Statement#2deleted45rows.

Statement#3deleted5rows.

Totalrowsdeleted:56

Example12-15usesSQL%BULK_ROWCOUNTtoshowhowmanyrowseachINSERTSELECTconstructintheFORALLstatementinsertedandSQL%ROWCOUNTtoshowthetotalnumberofrowsinserted.

Example12-15ShowingNumberofRowsAffectedbyEachINSERTSELECTinFORALL

DROPTABLEemp_by_dept;

CREATETABLEemp_by_deptAS

SELECTemployee_id,department_id

FROMemployees

WHERE1=0;

DECLARE

TYPEdept_tabISTABLEOFdepartments.department_id%TYPE;

deptnumsdept_tab;

BEGIN

SELECTdepartment_idBULKCOLLECTINTOdeptnumsFROMdepartments;

FORALLiIN1..deptnums.COUNT

INSERTINTOemp_by_dept(employee_id,department_id)

SELECTemployee_id,department_id

FROMemployees

WHEREdepartment_id=deptnums(i)

ORDERBYdepartment_id,employee_id;

FORiIN1..deptnums.COUNTLOOP

--Counthowmanyrowswereinsertedforeachdepartment;thatis,

--howmanyemployeesareineachdepartment.

DBMS_OUTPUT.PUT_LINE(

'Dept'||deptnums(i)||':inserted'||

SQL%BULK_ROWCOUNT(i)||'records'

);

ENDLOOP;

DBMS_OUTPUT.PUT_LINE('Totalrecordsinserted:'||SQL%ROWCOUNT);

END;

/

Result:

Dept10:inserted1records

...

Dept280:inserted0records

Totalrecordsinserted:106

2.8BULKCOLLECTClause

TheBULKCOLLECTclause,afeatureofbulkSQL,returnsresultsfromSQLtoPL/SQLinbatchesratherthanoneatatime.TheBULKCOLLECTclausecanappearin:

(1)SELECTINTOstatement

(2)FETCHstatement

(3)RETURNINGINTOclauseof:

(A)DELETEstatement

(B)INSERTstatement

(C)UPDATEstatement

(D)EXECUTEIMMEDIATEstatement

WiththeBULKCOLLECTclause,eachoftheprecedingstatementsretrievesanentireresultsetandstoresitinoneormorecollectionvariablesinasingleoperation(whichismoreefficientthanusingaloopstatementtoretrieveoneresultrowatatime).

Note:

PL/SQLprocessestheBULKCOLLECTclausesimilartothewayitprocessesaFETCHstatementinsideaLOOPstatement.PL/SQLdoesnotraiseanexceptionwhenastatementwithaBULKCOLLECTclausereturnsnorows.Youmustcheckthetargetcollectionsforemptiness(iftheyareassociativearrays)ornullness(iftheyarevarraysornestedtables),asinExample12-22.

2.9SELECTINTOStatementwithBULKCOLLECTClause

TheSELECTINTOstatementwiththeBULKCOLLECTclause(alsocalledtheSELECTBULKCOLLECTINTOstatement)selectsanentireresultsetintooneormorecollectionvariables.Formoreinformation,see"SELECTINTOStatement".

Caution:

TheSELECTBULKCOLLECTINTOstatementisvulnerabletoaliasing,whichcancauseunexpectedresults.Fordetails,see"SELECTBULKCOLLECTINTOStatementsandAliasing".

Example12-16usesaSELECTBULKCOLLECTINTOstatementtoselecttwodatabasecolumnsintotwocollections(nestedtables).

Example12-16Bulk-SelectingTwoDatabaseColumnsintoTwoNestedTables

DECLARE

TYPENumTabISTABLEOFemployees.employee_id%TYPE;

TYPENameTabISTABLEOFemployees.last_name%TYPE;

enumsNumTab;

namesNameTab;

PROCEDUREprint_first_n(nPOSITIVE)IS

BEGIN

IFenums.COUNT=0THEN

DBMS_OUTPUT.PUT_LINE('Collectionsareempty.');

ELSE

DBMS_OUTPUT.PUT_LINE('First'||n||'employees:');

FORiIN1..nLOOP

DBMS_OUTPUT.PUT_LINE(

'Employee#'||enums(i)||':'||names(i));

ENDLOOP;

ENDIF;

END;

BEGIN

SELECTemployee_id,last_name

BULKCOLLECTINTOenums,names

FROMemployees

ORDERBYemployee_id;

print_first_n(3);

print_first_n(6);

END;

/

Example12-17usesaSELECTBULKCOLLECTINTOstatementtoselectaresultsetintoanestedtableofrecords.

Example12-17Bulk-SelectingintoNestedTableofRecords

DECLARE

CURSORc1IS

SELECTfirst_name,last_name,hire_date

FROMemployees;

TYPENameSetISTABLEOFc1%ROWTYPE;

stock_managersNameSet;--nestedtableofrecords

BEGIN

--Assignvaluestonestedtableofrecords:

SELECTfirst_name,last_name,hire_date

BULKCOLLECTINTOstock_managers

FROMemployees

WHEREjob_id='ST_MAN'

ORDERBYhire_date;

--Printnestedtableofrecords:

FORiINstock_managers.FIRST..stock_managers.LASTLOOP

DBMS_OUTPUT.PUT_LINE(

stock_managers(i).hire_date||''||

stock_managers(i).last_name||','||

stock_managers(i).first_name

);

ENDLOOP;END;

/

2.10SELECTBULKCOLLECTINTOStatementsandAliasing

Inastatementoftheform

SELECTcolumnBULKCOLLECTINTOcollectionFROMtable...

columnandcollectionareanalogoustoINNOCOPYandOUTNOCOPYsubprogramparameters,respectively,andPL/SQLpassesthembyreference.Aswithsubprogramparametersthatarepassedbyreference,aliasingcancauseunexpectedresults.

SeeAlso:

"SubprogramParameterAliasingwithParametersPassedbyReference"

InExample12-18,theintentionistoselectspecificvaluesfromacollection,numbers1,andthenstoretheminthesamecollection.Theunexpectedresultisthatallelementsofnumbers1aredeleted.Forworkarounds,seeExample12-19andExample12-20.

Example12-18SELECTBULKCOLLECTINTOStatementwithUnexpectedResults

CREATEORREPLACETYPEnumbers_typeIS

TABLEOFINTEGER

/

CREATEORREPLACEPROCEDUREp(iININTEGER)IS

numbers1numbers_type:=numbers_type(1,2,3,4,5);

BEGIN

DBMS_OUTPUT.PUT_LINE('BeforeSELECTstatement');

DBMS_OUTPUT.PUT_LINE('numbers1.COUNT()='||numbers1.COUNT());

FORjIN1..numbers1.COUNT()LOOP

DBMS_OUTPUT.PUT_LINE('numbers1('||j||')='||numbers1(j));

ENDLOOP;

--Self-selectingBULKCOLLECTINTOclause:

SELECTa.COLUMN_VALUE

BULKCOLLECTINTOnumbers1

FROMTABLE(numbers1)a

WHEREa.COLUMN_VALUE>p.i

ORDERBYa.COLUMN_VALUE;

DBMS_OUTPUT.PUT_LINE('AfterSELECTstatement');

DBMS_OUTPUT.PUT_LINE('numbers1.COUNT()='||numbers1.COUNT());

ENDp;

/

Invokep:

BEGIN

p(2);

END;

/

Result:

BeforeSELECTstatement

numbers1.COUNT()=5

numbers1(1)=1

numbers1(2)=2

numbers1(3)=3

numbers1(4)=4

numbers1(5)=5

AfterSELECTstatement

numbers1.COUNT()=0

Invokep:

BEGIN

p(10);

END;

/

Result:

BeforeSELECTstatement

numbers1.COUNT()=5

numbers1(1)=1

numbers1(2)=2

numbers1(3)=3

numbers1(4)=4

numbers1(5)=5

AfterSELECTstatement

numbers1.COUNT()=0

Example12-19usesacursortoachievetheresultintendedbyExample12-18.

Example12-19CursorWorkaroundforExample12-18

CREATEORREPLACETYPEnumbers_typeIS

TABLEOFINTEGER

/

CREATEORREPLACEPROCEDUREp(iININTEGER)IS

numbers1numbers_type:=numbers_type(1,2,3,4,5);

CURSORcIS

SELECTa.COLUMN_VALUE

FROMTABLE(numbers1)a

WHEREa.COLUMN_VALUE>p.i

ORDERBYa.COLUMN_VALUE;

BEGIN

DBMS_OUTPUT.PUT_LINE('BeforeFETCHstatement');

DBMS_OUTPUT.PUT_LINE('numbers1.COUNT()='||numbers1.COUNT());

FORjIN1..numbers1.COUNT()LOOP

DBMS_OUTPUT.PUT_LINE('numbers1('||j||')='||numbers1(j));

ENDLOOP;

OPENc;

FETCHcBULKCOLLECTINTOnumbers1;

CLOSEc;

DBMS_OUTPUT.PUT_LINE('AfterFETCHstatement');

DBMS_OUTPUT.PUT_LINE('numbers1.COUNT()='||numbers1.COUNT());

IFnumbers1.COUNT()>0THEN

FORjIN1..numbers1.COUNT()LOOP

DBMS_OUTPUT.PUT_LINE('numbers1('||j||')='||numbers1(j));

ENDLOOP;

ENDIF;

ENDp;

/

Invokep:

BEGIN

p(2);

END;

/

Result:

BeforeFETCHstatement

numbers1.COUNT()=5

numbers1(1)=1

numbers1(2)=2

numbers1(3)=3

numbers1(4)=4

numbers1(5)=5

AfterFETCHstatement

numbers1.COUNT()=3

numbers1(1)=3

numbers1(2)=4

numbers1(3)=5

Example12-20selectsspecificvaluesfromacollection,numbers1,andthenstorestheminadifferentcollection,numbers2.Example12-20runsfasterthanExample12-19.

Example12-20SecondCollectionWorkaroundforExample12-18

CREATEORREPLACETYPEnumbers_typeIS

TABLEOFINTEGER

/

CREATEORREPLACEPROCEDUREp(iININTEGER)IS

numbers1numbers_type:=numbers_type(1,2,3,4,5);

numbers2numbers_type:=numbers_type(0,0,0,0,0);

BEGIN

DBMS_OUTPUT.PUT_LINE('BeforeSELECTstatement');

DBMS_OUTPUT.PUT_LINE('numbers1.COUNT()='||numbers1.COUNT());

FORjIN1..numbers1.COUNT()LOOP

DBMS_OUTPUT.PUT_LINE('numbers1('||j||')='||numbers1(j));

ENDLOOP;

DBMS_OUTPUT.PUT_LINE('numbers2.COUNT()='||numbers2.COUNT());

FORjIN1..numbers2.COUNT()LOOP

DBMS_OUTPUT.PUT_LINE('numbers2('||j||')='||numbers2(j));

ENDLOOP;

SELECTa.COLUMN_VALUE

BULKCOLLECTINTOnumbers2--numbers2appearshere

FROMTABLE(numbers1)a--numbers1appearshere

WHEREa.COLUMN_VALUE>p.i

ORDERBYa.COLUMN_VALUE;

DBMS_OUTPUT.PUT_LINE('AfterSELECTstatement');

DBMS_OUTPUT.PUT_LINE('numbers1.COUNT()='||numbers1.COUNT());

IFnumbers1.COUNT()>0THEN

FORjIN1..numbers1.COUNT()LOOP

DBMS_OUTPUT.PUT_LINE('numbers1('||j||')='||numbers1(j));

ENDLOOP;

ENDIF;

DBMS_OUTPUT.PUT_LINE('numbers2.COUNT()='||numbers2.COUNT());

IFnumbers2.COUNT()>0THEN

FORjIN1..numbers2.COUNT()LOOP

DBMS_OUTPUT.PUT_LINE('numbers2('||j||')='||numbers2(j));

ENDLOOP;

ENDIF;

ENDp;

/

2.11RowLimitsforSELECTBULKCOLLECTINTOStatements

ASELECTBULKCOLLECTINTOstatementthatreturnsalargenumberofrowsproducesalargecollection.Tolimitthenumberofrowsandthecollectionsize,useeithertheROWNUMpseudocolumn(describedinOracleDatabaseSQLLanguageReference)orSAMPLEclause(describedinOracleDatabaseSQLLanguageReference).

InExample12-21,thefirstSELECTBULKCOLLECTINTOstatementusesROWNUMtolimitthenumberofrowsto50,andthesecondSELECTBULKCOLLECTINTOstatementusesSAMPLEtolimitthenumberofrowstoapproximately10%ofthetotal.

Example12-21LimitingBulkSelectionwithROWNUMandSAMPLE

DECLARE

TYPESalListISTABLEOFemployees.salary%TYPE;

salsSalList;

BEGIN

SELECTsalaryBULKCOLLECTINTOsals

FROMemployees

WHEREROWNUM<=50;

SELECTsalaryBULKCOLLECTINTOsalsFROMemployeesSAMPLE(10);

END;

/

2.12GuidelinesforLoopingThroughCollections

Whenaresultsetisstoredinacollection,itiseasytoloopthroughtherowsandrefertodifferentcolumns.Thistechniquecanbeveryfast,butalsoverymemory-intensive.Ifyouuseitoften:

(1)Tolooponcethroughtheresultset,useacursorFORLOOP(see"QueryResultSetProcessingWithCursorFORLOOPStatements").Thistechniqueavoidsthememoryoverheadofstoringacopyoftheresultset.

(2)Insteadofloopingthroughtheresultsettosearchforcertainvaluesorfiltertheresultsintoasmallerset,dothesearchingorfilteringinthequeryoftheSELECTINTOstatement.

Forexample,insimplequeries,useWHEREclauses;inqueriesthatcomparemultipleresultsets,usesetoperatorssuchasINTERSECTandMINUS.Forinformationaboutsetoperators,seeOracleDatabaseSQLLanguageReference.

(3)Insteadofloopingthroughtheresultsetandrunninganotherqueryforeachresultrow,useasubqueryinthequeryoftheSELECTINTOstatement(see"QueryResultSetProcessingwithSubqueries").

(4)InsteadofloopingthroughtheresultsetandrunninganotherDMLstatementforeachresultrow,usetheFORALLstatement(see"FORALLStatement").

2.13FETCHStatementwithBULKCOLLECTClause

TheFETCHstatementwiththeBULKCOLLECTclause(alsocalledtheFETCHBULKCOLLECTstatement)fetchesanentireresultsetintooneormorecollectionvariables.Formoreinformation,see"FETCHStatement".

Example12-22usesaFETCHBULKCOLLECTstatementtofetchanentireresultsetintotwocollections(nestedtables).

Example12-22Bulk-FetchingintoTwoNestedTables

DECLARE

TYPENameListISTABLEOFemployees.last_name%TYPE;

TYPESalListISTABLEOFemployees.salary%TYPE;

CURSORc1IS

SELECTlast_name,salaryFROMemployeesWHEREsalary>10000

ORDERBYlast_name;

namesNameList;

salsSalList;

TYPERecListISTABLEOFc1%ROWTYPE;

recsRecList;

v_limitPLS_INTEGER:=10;

PROCEDUREprint_resultsIS

BEGIN

--Checkifcollectionsareempty:

IFnamesISNULLORnames.COUNT=0THEN

DBMS_OUTPUT.PUT_LINE('Noresults!');

ELSE

DBMS_OUTPUT.PUT_LINE('Result:');

FORiINnames.FIRST..names.LAST

LOOP

DBMS_OUTPUT.PUT_LINE('Employee'||names(i)||':$'||sals(i));

ENDLOOP;

ENDIF;

END;

BEGIN

DBMS_OUTPUT.PUT_LINE('---Processingallresultssimultaneously---');

OPENc1;

FETCHc1BULKCOLLECTINTOnames,sals;

CLOSEc1;

print_results();

DBMS_OUTPUT.PUT_LINE('---Processing'||v_limit||'rowsatatime---');

OPENc1;

LOOP

FETCHc1BULKCOLLECTINTOnames,salsLIMITv_limit;

EXITWHENnames.COUNT=0;

print_results();

ENDLOOP;

CLOSEc1;

DBMS_OUTPUT.PUT_LINE('---Fetchingrecordsratherthancolumns---');

OPENc1;

FETCHc1BULKCOLLECTINTOrecs;

FORiINrecs.FIRST..recs.LAST

LOOP

--Nowallcolumnsfromresultsetcomefromonerecord

DBMS_OUTPUT.PUT_LINE(

'Employee'||recs(i).last_name||':$'||recs(i).salary

);

ENDLOOP;

END;

/

Example12-23usesaFETCHBULKCOLLECTstatementtofetcharesultsetintoacollection(nestedtable)ofrecords.

Example12-23Bulk-FetchingintoNestedTableofRecords

DECLARE

CURSORc1IS

SELECTfirst_name,last_name,hire_dateFROMemployees;

TYPENameSetISTABLEOFc1%ROWTYPE;

stock_managersNameSet;--nestedtableofrecords

TYPEcursor_var_typeisREFCURSOR;

cvcursor_var_type;

BEGIN

--Assignvaluestonestedtableofrecords:

OPENcvFOR

SELECTfirst_name,last_name,hire_dateFROMemployees

WHEREjob_id='ST_MAN'ORDERBYhire_date;

FETCHcvBULKCOLLECTINTOstock_managers;

CLOSEcv;

--Printnestedtableofrecords:

FORiINstock_managers.FIRST..stock_managers.LASTLOOP

DBMS_OUTPUT.PUT_LINE(

stock_managers(i).hire_date||''||

stock_managers(i).last_name||','||

stock_managers(i).first_name

);

ENDLOOP;END;

/

2.14RowLimitsforFETCHBULKCOLLECTStatements

AFETCHBULKCOLLECTstatementthatreturnsalargenumberofrowsproducesalargecollection.Tolimitthenumberofrowsandthecollectionsize,usetheLIMITclause.

InExample12-24,witheachiterationoftheLOOPstatement,theFETCHstatementfetchestenrows(orfewer)intoassociativearrayempids(overwritingthepreviousvalues).NotetheexitconditionfortheLOOPstatement.

Example12-24LimitingBulkFETCHwithLIMIT

DECLARE

TYPEnumtabISTABLEOFNUMBERINDEXBYPLS_INTEGER;

CURSORc1ISSELECTemployee_id

FROMemployeesWHEREdepartment_id=80ORDERBYemployee_id;

empidsnumtab;

BEGIN

OPENc1;

LOOP--Fetch10rowsorfewerineachiteration

FETCHc1BULKCOLLECTINTOempidsLIMIT10;

EXITWHENempids.COUNT=0;--Not:EXITWHENc1%NOTFOUND

DBMS_OUTPUT.PUT_LINE('-------ResultsfromOneBulkFetch--------');

FORiIN1..empids.COUNTLOOP

DBMS_OUTPUT.PUT_LINE('EmployeeId:'||empids(i));

ENDLOOP;

ENDLOOP;

CLOSEc1;

END;

/

2.15RETURNINGINTOClausewithBULKCOLLECTClause

TheRETURNINGINTOclausewiththeBULKCOLLECTclause(alsocalledtheRETURNINGBULKCOLLECTINTOclause)canappearinanINSERT,UPDATE,DELETE,orEXECUTEIMMEDIATEstatement.WiththeRETURNINGBULKCOLLECTINTOclause,thestatementstoresitsresultsetinoneormorecollections.Formoreinformation,see"RETURNINGINTOClause".

Example12-25usesaDELETEstatementwiththeRETURNINGBULKCOLLECTINTOclausetodeleterowsfromatableandreturnthemintwocollections(nestedtables).

Example12-25ReturningDeletedRowsinTwoNestedTables

DROPTABLEemp_temp;

CREATETABLEemp_tempAS

SELECT*FROMemployees

ORDERBYemployee_id;

DECLARE

TYPENumListISTABLEOFemployees.employee_id%TYPE;

enumsNumList;

TYPENameListISTABLEOFemployees.last_name%TYPE;

namesNameList;

BEGIN

DELETEFROMemp_tempWHEREdepartment_id=30

RETURNINGemployee_id,last_nameBULKCOLLECTINTOenums,names;

DBMS_OUTPUT.PUT_LINE('Deleted'||SQL%ROWCOUNT||'rows:');

FORiINenums.FIRST..enums.LAST

LOOP

DBMS_OUTPUT.PUT_LINE('Employee#'||enums(i)||':'||names(i));

ENDLOOP;

END;

/

2.16UsingFORALLStatementandBULKCOLLECTClauseTogether

InaFORALLstatement,theDMLstatementcanhaveaRETURNINGBULKCOLLECTINTOclause.ForeachiterationoftheFORALLstatement,theDMLstatementstoresthespecifiedvaluesinthespecifiedcollections—withoutoverwritingthepreviousvalues,asthesameDMLstatementwoulddoinaFORLOOPstatement.

InExample12-26,theFORALLstatementrunsaDELETEstatementthathasaRETURNINGBULKCOLLECTINTOclause.ForeachiterationoftheFORALLstatement,theDELETEstatementstorestheemployee_idanddepartment_idvaluesofthedeletedrowinthecollectionse_idsandd_ids,respectively.

Example12-26DELETEwithRETURNBULKCOLLECTINTOinFORALLStatement

DROPTABLEemp_temp;

CREATETABLEemp_tempAS

SELECT*FROMemployees

ORDERBYemployee_id,department_id;

DECLARE

TYPENumListISTABLEOFNUMBER;

deptsNumList:=NumList(10,20,30);

TYPEenum_tISTABLEOFemployees.employee_id%TYPE;

e_idsenum_t;

TYPEdept_tISTABLEOFemployees.department_id%TYPE;

d_idsdept_t;

BEGIN

FORALLjINdepts.FIRST..depts.LAST

DELETEFROMemp_tempWHEREdepartment_id=depts(j)

RETURNINGemployee_id,department_id

BULKCOLLECTINTOe_ids,d_ids;

DBMS_OUTPUT.PUT_LINE('Deleted'||SQL%ROWCOUNT||'rows:');

FORiINe_ids.FIRST..e_ids.LAST

LOOP

DBMS_OUTPUT.PUT_LINE(

'Employee#'||e_ids(i)||'fromdept#'||d_ids(i)

);

ENDLOOP;

END;

/

Example12-27islikeExample12-26exceptthatitusesaFORLOOPstatementinsteadofaFORALLstatement.

Example12-27DELETEwithRETURNBULKCOLLECTINTOinFORLOOPStatement

DECLARE

TYPENumListISTABLEOFNUMBER;

deptsNumList:=NumList(10,20,30);

TYPEenum_tISTABLEOFemployees.employee_id%TYPE;

e_idsenum_t;

TYPEdept_tISTABLEOFemployees.department_id%TYPE;

d_idsdept_t;

BEGIN

FORjINdepts.FIRST..depts.LASTLOOP

DELETEFROMemp_tempWHEREdepartment_id=depts(j)

RETURNINGemployee_id,department_id

BULKCOLLECTINTOe_ids,d_ids;

ENDLOOP;

DBMS_OUTPUT.PUT_LINE('Deleted'||SQL%ROWCOUNT||'rows:');

FORiINe_ids.FIRST..e_ids.LAST

LOOP

DBMS_OUTPUT.PUT_LINE(

'Employee#'||e_ids(i)||'fromdept#'||d_ids(i)

);

ENDLOOP;

END;

/

2.17ClientBulk-BindingofHostArrays

Clientprograms(suchasOCIandPro*Cprograms)canusePL/SQLanonymousblockstobulk-bindinputandoutputhostarrays.Thisisthemostefficientwaytopasscollectionstoandfromthedatabaseserver.

Intheclientprogram,declareandassignvaluestothehostvariablestobereferencedintheanonymousblock.Intheanonymousblock,prefixeachhostvariablenamewithacolon(:)todistinguishitfromaPL/SQLcollectionvariablename.Whentheclientprogramruns,thedatabaseserverrunsthePL/SQLanonymousblock.

InExample12-28,theanonymousblockusesaFORALLstatementtobulk-bindahostinputarray.IntheFORALLstatement,theDELETEstatementreferstofourhostvariables:scalarslower,upper,andemp_idandarraydepts.

Example12-28AnonymousBlockBulk-BindsInputHostArray

BEGIN

FORALLiIN:lower..:upper

DELETEFROMemployeesWHEREdepartment_id=:depts(i);

END;

/

三.小结

在第一节讲了Bulk的原理,第二节举了Bulk的例子。在这里做一个简单的回顾,到底什么是bulk.

OnemethodoffetchingdataisanOraclebulkcollect.WithOraclebulkcollect,thePL/SQLenginetellstheSQLenginetocollectmanyrowsatonceandplacetheminacollection.DuringanOraclebulkcollect,theSQLengineretrievesalltherowsandloadsthemintothecollectionandswitchesbacktothePL/SQLengine.WhenrowsareretrievedusingOraclebulkcollect,theyareretrievedwithonly2contextswitches.ThelargerthenumberofrowsyouwouldliketocollectwithOraclebulkcollect,themoreperformanceimprovementyouwillseeusinganOraclebulkcollect.

StartinginOracle10g,anOraclebulkcollectmaybeperformedbythethePL/SQLengineforyou.ThePL/SQLenginemayautomaticallyuseOraclebulkcollecttocollect100rowsatatimebecauseofacursorloop.ThisuseofOraclebulkcollectallowsyourcodetoprocessrowswithouthavingtosetupandexecutetheOraclebulkcollectoperation.TheresultofthisuseofOraclebulkcollectisthatbulkcollecting75rowsmaynotprovideyouwithmuchofabenefit,butusingOraclebulkcollecttocollectlargenumbersofrows(manyhundreds)willprovidincreasedperformance.

Oracle有2个引擎来执行PL/SQLblocks和subprograms。那么在执行的时候,PL/SQL引擎把DML语句发送给SQL引擎,然后由SQL引擎执行,执行完毕后,SQL引擎把结果集在发送给PL/SQL引擎。

这个是一条语句的执行过程,如果我们有一个大事务,比如insert100万的数据,那么这个时候,如果按照原始的方法,每次处理一条,这样在2个引擎之间就会发生大量的contextswitches,这样就会影响SQL的效率。

而bulk就是从减少引擎之间contextswitches的方式来提高sql的效率。把对SQL进行打包处理。有2个bulk:

(1)FORALL.将数据打包,一次性从PL/SQL引擎发送给SQL引擎。

如:

BEGIN

FORALLjIN1..10

DELETEFROMemployees_tempWHEREdepartment_id=depts(j);

END;

如果这里用for..loop循环,那么会发送10次,而用Forall,一次行全部发送过去。

(2)bulkcollect:将处理之后的结果集放到bulkcollect里,然后一次性把bulkcollect从SQL引擎发送给PL/SQL引擎。这个bulkcollect需要我们先定义好才能使用。

通过以上说明可以看出,如果使用bulk,那么只有2次contextswitches,当要处理的数据量越大,使用bulk和不使用bulk性能区别就越明显。

-------------------------------------------------------------------------------------------------------

Blog:http://blog.csdn.net/tianlesoftware
Email:dvd.dba@gmail.com

DBA1群:62697716(满);DBA2群:62697977(满)DBA3群:62697850(满)

DBA超级群:63306533(满);DBA4群:83829929DBA5群:142216823

DBA6群:158654907聊天群:40132017聊天2群:69087192

--加群需要在备注说明Oracle表空间和数据文件的关系,否则拒绝申请
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: