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:
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:
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表空间和数据文件的关系,否则拒绝申请
本来只想测试一下BulkCollect和update性能的,但发现Bulk的东西还是很多的,在OTN上搜了一些,整理如下。
1.1BulkBinding和BulkSQL
From:
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 |
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,see
1.2WhentoUseBulkBinds
Considerusingbulkbindstoimprovetheperformanceof:
1.2.1DMLStatementsthatReferenceCollections
Abulkbind,whichusestheFORALLkeyword,canimprovetheperformanceofINSERT,UPDATE,orDELETEstatementsthatreferencecollectionelements.
ThePL/SQLblockin
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/SQLblockin
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/SQLblockin
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
2.1FORALLStatement
TheFORALLstatement,afeatureofbulkSQL,sendsDMLstatementsfromPL/SQLtoSQLinbatchesratherthanoneatatime.
TounderstandtheFORALLstatement,firstconsidertheFORLOOPstatementin
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;
/
NowconsidertheFORALLstatementin
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-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.
In
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-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_tabINSERTINTOvalid_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,theFORALLstatementin
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
(2)AftertheFORALLstatementcompletesexecution,byincludingtheSAVEEXCEPTIONSclause(see
2.4HandlingFORALLExceptionsImmediately
TohandleexceptionsraisedinaFORALLstatementimmediately,omittheSAVEEXCEPTIONSclauseandwritetheappropriateexceptionhandlers.(Forinformationaboutexceptionhandlers,see
In
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:
maximum:18)
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(
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-fourthonesfailwitherrorcodes
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
SQLERRM(-(SQL%BULK_EXCEPTIONS(i).ERROR_CODE))
However,theerrormessagethatSQLERRMreturnsexcludesanysubstitutionarguments(comparetheerrormessagesin
(1)TheFORALLstatementincludestheSAVEEXCEPTIONSclause.
(2)Theexception-handlingparthasanexceptionhandlerfor
(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:
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%BULK_ROWCOUNTislikeanassociativearraywhoseithelementisthenumberofrowsaffectedbytheithDMLstatementinthemostrecentlycompletedFORALLstatement.
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-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),asin
2.9SELECTINTOStatementwithBULKCOLLECTClause
TheSELECTINTOstatementwiththeBULKCOLLECTclause(alsocalledtheSELECTBULKCOLLECTINTOstatement)selectsanentireresultsetintooneormorecollectionvariables.Formoreinformation,see
Caution:
TheSELECTBULKCOLLECTINTOstatementisvulnerabletoaliasing,whichcancauseunexpectedresults.Fordetails,see
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-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:
In
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-19CursorWorkaroundfor
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-20SecondCollectionWorkaroundfor
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(describedin
In
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
(2)Insteadofloopingthroughtheresultsettosearchforcertainvaluesorfiltertheresultsintoasmallerset,dothesearchingorfilteringinthequeryoftheSELECTINTOstatement.
Forexample,insimplequeries,useWHEREclauses;inqueriesthatcomparemultipleresultsets,usesetoperatorssuchasINTERSECTandMINUS.Forinformationaboutsetoperators,see
(3)Insteadofloopingthroughtheresultsetandrunninganotherqueryforeachresultrow,useasubqueryinthequeryoftheSELECTINTOstatement(see
(4)InsteadofloopingthroughtheresultsetandrunninganotherDMLstatementforeachresultrow,usetheFORALLstatement(see
2.13FETCHStatementwithBULKCOLLECTClause
TheFETCHstatementwiththeBULKCOLLECTclause(alsocalledtheFETCHBULKCOLLECTstatement)fetchesanentireresultsetintooneormorecollectionvariables.Formoreinformation,see
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-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.
In
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
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.
In
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-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.
In
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:
Email:dvd.dba@gmail.com
DBA1群:62697716(满);DBA2群:62697977(满)DBA3群:62697850(满)
DBA超级群:63306533(满);DBA4群:83829929DBA5群:142216823
DBA6群:158654907聊天群:40132017聊天2群:69087192
--加群需要在备注说明Oracle表空间和数据文件的关系,否则拒绝申请
相关文章推荐
- 转:// Oracle PL/SQL 优化与调整 -- Bulk 说明
- 转://Oracle PL/SQL 优化与调整 -- Bulk 说明
- Oracle PL/SQL 优化与调整 -- Bulk 说明
- Oracle PL/SQL 优化与调整 -- Bulk 说明
- Oracle PL/SQL 优化与调整 -- Bulk 说明
- Oracle PL/SQL 优化与调整 -- Bulk 说明
- Oracle PL/SQL 优化与调整 -- Bulk 说明
- Oracle PL/SQL 优化与调整 -- Bulk 说明
- Oracle PL/SQL 优化与调整 -- Bulk 说明
- Oracle PL/SQL 优化与调整 – PL/SQL Native Compilation 说明
- Oracle PL/SQL 优化与调整 – PL/SQL Native Compilation 说明
- Oracle PL/SQL 优化与调整 – PL/SQL Native Compilation 说明
- Oracle PL/SQL 优化与调整 – PL/SQL Native Compilation 说明
- Oracle PL/SQL 优化与调整 – PL/SQL Native Compilation 说明
- Oracle PL/SQL 优化与调整 – PL/SQL Native Compilation 说明
- Oracle PL/SQL 优化与调整 – PL/SQL Native Compilation 说明
- Oracle PL/SQL 优化与调整 – PL/SQL Native Compilation 说明
- Oracle sql 性能优化调整
- oracle sql优化案例2(RBO下调整表连接的顺序)
- ORACLE PL/SQL开发--bulk collect的用法