WEB开发网      濠电姷鏁告慨鐑藉极閸涘﹥鍙忛柣鎴f閺嬩線鏌涘☉姗堝姛缂佺娀绠栭弻宥堫檨闁告挻姘ㄩ幑銏犫槈濞嗘劕顎撻梺鍛婂姇瀵爼骞栭幇顔炬/闁告挆鍕畬闂佸疇顫夐崹鍧楀箖閳哄啠鍋撻崷顓炐㈡い銉︾箞濮婂搫效閸パ€鍋撳Δ鍛;闁规崘顕ф闂佸憡娲﹂崹鎵不濞戙垺鐓曟い鎰剁稻缁€鍐┿亜鎼达紕效婵﹨娅g划娆忊枎閹冨闂備礁鎽滄慨鐢稿礉濞嗘劒绻嗛柣銏⑶圭粈瀣亜閺嶃劏澹橀柛鐐姂濮婃椽妫冨ù銈嗙⊕閹峰懘骞撻幒宥咁棜闂備礁婀遍崕銈夈€冮崱娑樼厱闁圭儤顨嗛悡鏇㈡煛閸ャ儱濡煎ù婊勭矋閵囧嫯绠涢敐鍛睄闂佸搫澶囬埀顒€纾弳鍡涙倵閿濆骸澧伴柡鍡欏█閺屟勫濞嗘垵鍩岄梺闈涙鐢帡锝炲┑瀣亗閹艰揪绲奸悽鑽ょ磽娴h娈曢柛銊ョ仢椤繒绱掑Ο璇差€撶紓浣圭☉椤戝懎鈻撻鐐╂斀妞ゆ梹鏋婚崗顒傜磼閻樿櫕宕岄柕鍡曠椤繈骞囨担鍏夋瀸濠电姷鏁告慨顓㈠磻閹捐秮褰掓晲閸モ斂鈧﹪鏌¢埀顒佺鐎n偆鍘藉┑鈽嗗灡椤戞瑩宕电€n兘鍋撶憴鍕仩闁稿氦绮鹃悘鍐⒑缂佹◤顏勵嚕閸洖鐤柣鎰暩绾惧ジ鏌涚仦鐐殤閺佸牓鎮楃憴鍕缂侇喖绻樿棟閻庨潧鎽滃Λ顖炴煙椤栧棔绀佹禒顕€鎮楀▓鍨灈闁绘牜鍘ч悾鐑芥偂鎼存ɑ顫嶅┑鈽嗗灟鐠€锕傛倵瀹曞洨纾介柛灞剧懅閸斿秵銇勯妸銉︻棞闁伙絾绻堥獮鏍ㄦ媴濮濆本鎲伴梻浣虹帛濡啴藟閹捐姹查悗锝庡枟閻撶喐淇婇妶鍌氫壕闂佺粯顨呭Λ妤呭煝閹炬緞鏃堝川椤旇瀚奸梺鑽ゅТ濞茬娀鍩€椤掑啯鐝柣蹇婂亾闂傚倷绀侀幖顐﹀箠閹邦厽鍙忛柟缁㈠枟閸嬧晠鏌i妶搴$仜濞存粌缍婇弻鐔兼倻濡偐鐣洪梺鍝勬噺缁诲牆顫忓ú顏咁棃婵炴垶鑹鹃。鍝勨攽閳藉棗浜濋柣鐔叉櫊閵嗕礁鈻庨幒鏃傛澑闂佸搫鍟崐濠氭儊閸儲鈷戞慨鐟版搐閻忓弶绻涙担鍐插椤╃兘鏌ㄩ弴鐐测偓褰掓偂閺囥垺鐓忓┑鐐茬仢閸斻倝鏌涢埡瀣ɑ妞ゃ劊鍎甸幃娆撳级閹存繍娼氭俊銈囧Х閸嬬偤鏁冮姀銈冣偓浣糕枎閹炬潙娈愰梺鍐叉惈椤戝洭鐛姀銈嗏拻闁稿本鐟︾粊鐗堛亜椤愩埄妲搁柣锝呭槻铻i柤娴嬫櫇閻撳顪冮妶鍡橆梿闁跨喆鍎茬粋宥堛亹閹烘挾鍘甸梺缁樺灦钃遍悘蹇e幖闇夋繝濠傚暟缁夌儤鎱ㄦ繝鍛仩缂佽鲸甯掕灒闁惧繘鈧稒顢橀梻鍌欑劍鐎笛兠哄澶婄柧婵炴垶绮庢禍閬嶆⒒娴e憡鍟炴繛璇х畵瀹曞綊鏌嗗鍛幈闂佺鎻梽鍕偂濞嗘挻鐓犳繛鏉戭儐濞呭懎霉閻樺磭鐭婇柍瑙勫灴閸ㄩ箖鎮欓挊澶夊垝闂備浇顕栭崰妤呫€冮崨鏉戠叀濠㈣埖鍔曠粻鎶芥煙閹屽殶鐟滄澘娲ㄧ槐鎾诲磼濞嗘垼绐楅梺鍝ュУ閻楃娀銆侀弽顓炲窛闁圭⒈鍘介弲锝夋⒑缁嬭法绠抽柛妯犲懏顐介柣鎰節缁诲棙銇勯弽銊х煂閻㈩垱绋掔换娑㈠川椤撶喎鏋犲┑顔硷功缁垶骞忛崨瀛樺仭闂侇叏绠戝▓婵堢磽閸屾瑦绁版い鏇嗗洤纾归柛顭戝櫘閸ゆ洜绱撴担璐細缂佲檧鍋撻梻浣规偠閸庮垶宕濆鍛瀺闁搞儺鍓氶埛鎴犵磼鐎n偄顕滄繝鈧幍顔剧<閻庯綆鍋呭畷宀€鈧娲忛崹浠嬪箖娴犲宸濆┑鐘插楠炴姊洪悷鏉挎倯闁伙綆浜畷瑙勭節濮橆剛鍘愰梺鍝勬储閸ㄦ椽鎮¢妷锔藉弿婵☆垰鐏濋悡鎰版煟閹捐泛鏋涢柣鎿冨亰瀹曞爼濡烽妷銉バ戠紓鍌欑椤戝牆鐣烽悽鍨潟闁圭儤姊荤壕鍏间繆椤栨繂浜归柣锝堟缁辨挻鎷呴搹鐟扮缂備浇顕ч悧鍡涙偩瀹勯偊娼ㄩ柍褜鍓氭穱濠傤潰瀹€濠冃ㄧ紓鍌欐祰妞村摜鎹㈤崼婵愭綎缂備焦蓱婵绱掑☉姗嗗剰婵炲牊鍔欏娲箹閻愭彃顬嗛梺鍛婎殔閸熷潡鎮鹃悜绛嬫晬闁绘劘灏欐鍥⒑閻熼偊鍤熷┑顕€娼ч埢鎾淬偅閸愨斁鎷虹紓鍌欑劍钃遍柍閿嬪浮閺屽秴鐣¢幍顔尖叺閻庢鍣崑濠傜暦閹烘鍊烽悗鐢登归獮鍫ユ⒒娴g懓鈻曢柡渚囧櫍瀹曟垿骞樼紒妯煎幐闂佸憡渚楅崰姘跺箠閸涱喕绻嗛柛娆忣槸婵洭鎽堕敐澶嬪仩婵炴垶甯掓晶鏌ユ煛閸屾浜鹃梻鍌氬€烽懗鍓佸垝椤栫偛绀夐柡鍥╁€i悢鍝ョ瘈闁搞儜鍐╁劒闂備胶绮弻銊╂儍濠靛缁╅柤鎭掑劘娴滄粓鏌¢崘銊﹀妞ゃ儱顦甸弻娑㈠棘鐠囨祴鍋撳┑瀣闁割偅娲橀崐鐑芥煟閹寸偍缂氶柛姗€浜跺娲传閸曨剙鍋嶉梺鍛婃煥閺堫剟寮查崼鏇ㄦ晬闁绘劕顕崢鍗炩攽閻愬弶顥滅紒缁樺笧缁粯绻濆顓犲幐闁诲繒鍋熼弲顐f櫏闁诲氦顫夊ú锕傚磻婵犲倻鏆﹂柣鏃傗拡閺佸棝鏌嶈閸撴瑩鍩㈠澶嬫櫜闁搞儮鏅濋敍婵囩箾鏉堝墽绋荤憸鏉垮暞缁傚秹鎮欓鍌滅槇闂侀潧楠忕徊鍓ф兜閻愵兙浜滈柟瀛樼箖瀹告繄绱掗鍓у笡闁靛牞缍佸畷姗€鍩¢崘銊ョ闂備浇顕х€涒晝绮欓幒鎴犲箵閻犳亽鍔庢稉宥嗘叏濡炶浜鹃梺鍝勮閸斿矂鍩ユ径濞㈢喐寰勯惂鍝ョɑ闁靛洤瀚版俊鎼佹晲閸涱厼袝闂備浇顕栭崰妤呮偡閳哄懌鈧線寮崼婵堫槹濡炪倖鎸荤换鍕矆閸曨垱鈷掗柛灞剧懄缁佺増銇勯弴鐔哄⒌鐎规洑鍗冲浠嬵敃閵堝浂妲稿┑鐘垫暩婵挳宕愭繝姘辈闁挎洖鍊归悡娆愩亜閺嶎偄浠滃ù婊呭娣囧﹪鎳犳0婵嗘闂佸疇顫夐崹鍧楀春閸曨垰绀冮柕濞у懌鍋″┑锛勫亼閸娿倝宕㈡ィ鍐ㄧ婵☆垯璀﹂崵鏇㈡偣閸ャ劎銈存俊鎻掔墦閺屾洝绠涢弴鐑嗘綌闂佸啿鎼幊蹇涙偂韫囨搩鐔嗛悹楦挎婢ф洟鏌涢弮鈧幐鎶藉蓟濞戙垹妫樻繛鍡欏亾妤旂紓鍌欐祰妞存悂骞愭繝姘闁告侗鍨抽惌娆撳箹鐎涙ɑ灏ù婊堢畺閺屾稑鈹戦崟顐㈠Б闂佹椿鍘介幐楣冨箟閹间焦鍋嬮柛顐g箘閻熴劑姊洪崫鍕靛剮缂佽埖宀稿濠氭偄閻撳海顦悷婊冪箳閺侇喖鈽夐姀锛勫幐闂佸憡渚楅崰妤呭磹閹扮増鐓涢悘鐐额嚙婵倿鏌熼鍝勨偓婵嗙暦閹烘垟妲堟慨妤€妫旂槐锟� ---闂傚倸鍊搁崐鎼佸磹閹间礁纾归柟闂寸绾惧綊鏌i幋锝呅撻柛濠傛健閺屻劑寮崼鐔告闂佺ǹ顑嗛幐鍓у垝椤撶偐妲堟俊顖氭惈缁犺鈹戦悙鍙夆枙濞存粍绮撻幃鈥斥槈閵忥紕鍘卞┑鐐村灥瀹曨剟鐛Ο姹囦簻闁哄倹瀵чˉ銏℃叏婵犲懏顏犻柟鐟板婵℃悂濡烽敂鎯х稈闂傚倷鑳堕幊鎾诲吹閺嶎厼绠柨鐕傛嫹
开发学院数据库DB2 INSTEAD OF 触发器 - 所有视图都是可更新的! 阅读

INSTEAD OF 触发器 - 所有视图都是可更新的!

 2009-11-23 00:00:00 来源:WEB开发网 闂傚倸鍊搁崐鎼佸磹閹间礁纾归柟闂寸绾剧懓顪冪€n亜顒㈡い鎰Г閹便劌顫滈崱妤€骞婄紓鍌氬€瑰銊╁箟缁嬫鍚嬮柛顐線缂冩洟姊婚崒娆戭槮婵犫偓闁秵鎯為幖娣妼缁愭鏌″搴′簽濞戞挸绉甸妵鍕冀椤愵澀娌梺缁樻尪閸庣敻寮婚敐澶婂嵆闁绘劖绁撮崑鎾诲捶椤撴稑浜炬慨妯煎亾鐎氾拷闂傚倸鍊搁崐鎼佸磹閹间礁纾归柟闂寸绾剧懓顪冪€n亝鎹i柣顓炴閵嗘帒顫濋敐鍛婵°倗濮烽崑娑⑺囬悽绋挎瀬闁瑰墽绮崑鎰版煙缂佹ê绗ч柍褜鍓﹂崣鍐潖閸濆嫅褔宕惰娴犲ジ姊虹拠鑼闁煎綊绠栭幃楣冩倻閽樺鎽曢梺闈涱檧婵″洭宕㈤悽鍛娾拺閻熸瑥瀚烽崯蹇涙煕閻樺磭澧甸柕鍡楀€圭缓浠嬪川婵犲嫬骞堥梺纭呭閹活亞妲愰弴鐔哄ⅰ闂傚倷绶氬ḿ褍煤閵堝洠鍋撳顐㈠祮闁绘侗鍣i獮鎺懳旈埀顒傜不閿濆棛绡€闂傚牊绋戦弳娆徝瑰⿰鍫㈢暫闁哄矉缍佹慨鈧柍鎯版硾濠€杈ㄧ珶閺囩喓绡€婵﹩鍘鹃崢鐢告⒑缂佹ê濮﹂柛鎾村哺閹ɑ娼忛妸銈囩畾闂佸湱绮敮鐐存櫠濞戞氨纾肩紓浣贯缚濞插鈧娲栧畷顒冪亙闂佸憡鍔曢崯鐘诲礈濠靛牊宕叉繛鎴炨缚閺嗗棗鈹戦悩杈厡闁轰焦鐗滅槐鎾存媴娴犲鎽甸梺鍦嚀濞层倝鎮鹃悜钘夌闁规惌鍘介崓鐢告⒑閻熸澘鎮侀柣鎺炵畵閹骞栨担鍏夋嫽婵炶揪绲块崕銈夊吹閳ь剟姊洪幖鐐测偓鏍偋閻樿崵宓侀煫鍥ㄧ⊕閺呮悂鏌ㄩ悤鍌涘濠电姷鏁告慨鐑藉极閸涘﹥鍙忛柣鎴f閺嬩線鏌涘☉姗堟敾闁告瑥绻戦妵鍕箻閸楃偟浠肩紓浣哄閸ㄥ爼寮诲☉銏犵疀闂傚牊绋掗悘鍫ユ倵閻熺増鍟炵紒璇插暣婵$敻宕熼姘鳖啋闁诲酣娼ч幗婊堟偩婵傚憡鈷戠痪顓炴媼濞兼劖绻涢懠顒€鏋庢い顐㈢箳缁辨帒螣閼测晜鍤岄梻渚€鈧偛鑻晶顔肩暆閿濆牆鍔垫い锔界叀閹繝濡舵径瀣帾闂佸壊鍋呯换鍐磻椤忓懐绠剧€瑰壊鍠曠花濠氬箚閻斿吋鈷戦悗鍦У閵嗗啴鏌ら崘鑼煟鐎规洘绻堥弫鍐焵椤掑嫧鈧棃宕橀鍢壯囨煕閳╁喚娈橀柣鐔稿姍濮婃椽鎮℃惔鈩冩瘣闂佺粯鐗曢妶绋跨暦閻戞ḿ绡€闁搞儜鍐ㄧギ闂備線娼ф蹇曟閺囥垹鍌ㄦい蹇撶墛閳锋垿鏌熼懖鈺佷粶闁告梹顨婇弻锟犲川椤旈敮濮囩紓浣稿€圭敮鐔妓囩€靛摜纾奸弶鍫涘妼缁楁碍绻涢悡搴g闁糕斁鍓濋幏鍛存煥鐎e灚缍楅梻鍌氬€峰ù鍥ь浖閵娾晜鍊块柨鏇炲€哥粻鏌ユ煕閵夘喖澧柡瀣╃窔閺岀喖宕滆鐢盯鏌¢崨顔藉€愰柡灞诲姂閹倝宕掑☉姗嗕紦闂傚倸鍊搁崐鎼佸磹閹间礁纾归柟闂寸绾剧懓顪冪€n亜顒㈡い鎰Г閹便劌顫滈崱妤€骞婄紓鍌氬€瑰銊╁箟缁嬫鍚嬮柛顐線缂冩洟姊婚崒娆戭槮婵犫偓闁秵鎯為幖娣妼缁愭鏌″搴′簽濞戞挸绉甸妵鍕冀椤愵澀娌梺缁樻尪閸庣敻寮婚敐澶婂嵆闁绘劖绁撮崑鎾诲捶椤撴稑浜炬慨妯煎亾鐎氾拷  闂傚倸鍊搁崐鎼佸磹閹间礁纾归柟闂寸绾惧綊鏌i幋锝呅撻柛銈呭閺屻倝宕妷锔芥瘎婵炲濮靛銊ф閹捐纾兼繛鍡樺笒閸橈紕绱撴笟鍥ф珮闁搞劌鐖兼俊鎾礃椤旂厧绐涢梺鍝勵槹閸ㄥ綊宕㈠ú顏呭€垫鐐茬仢閸旀碍銇勯敂璇茬仸鐎规洩绻濋獮搴ㄦ嚍閵壯冨妇闂傚⿴鍋勫ú锕€煤閺嶃劎澧¢梻鍌欐祰椤曆呪偓鍨浮瀹曟粓鎮㈡總澶嬬稁闂佹儳绻愬﹢杈╁閸忛棿绻嗘い鏍ㄧ閹牊銇勯銏㈢劯婵﹨娅i幏鐘绘嚑椤掑偆鍞规繝娈垮枟鑿ч柛鏃€鍨垮畷娲焵椤掍降浜滈柟鍝勭Ф椤︼箓鏌涢妶搴″⒋闁哄本鐩獮妯兼崉閻戞ḿ鈧顪冮妶搴′簻缂佺粯鍔楅崣鍛渻閵堝懐绠伴悗姘煎墴閹顢橀悜鍡樺瘜闂侀潧鐗嗗Λ娆戠矆閳ь剟姊洪悷鏉挎毐闂佸府绲介悾宄扳堪閸曨偒鍤ら柣搴㈢⊕鑿ら柟閿嬫そ濮婄粯绗熼崶褌绨介梺绋款儐閻╊垶骞婇悢纰辨晬婵炴垶鐟﹂悵宄邦渻閵堝棙鐓ュ褏鏅竟鏇㈡偂鎼搭喚鍞甸柣鐘烘鐏忋劑宕濋悢铏圭<濠㈣泛瀛╅鐘绘煃瑜滈崜姘额敊閺嶎厼绐楅柡宥庡幐閳ь剨绠撻弻銊р偓锝傛櫇缁犳艾鈹戦鐣岀畵闁活厼鐗嗗嵄闁绘垼濮ら埛鎴犵磼鐎n偒鍎ラ柛搴㈠姍閺岀喖鎮烽悧鍫熸倷闁捐崵鍋ら弻娑㈠箛閳轰礁唯濠碘剝褰冮悧濠勬崲濞戙垹骞㈡俊銈呭暟椤斿鈹戦悙鑼闁挎洏鍨归~蹇曠磼濡顎撴俊鐐差儏缁ㄨ偐鎲伴崱娆戠=闁稿本姘ㄨⅵ闂佺ǹ顑嗛幐鑽ゆ崲濞戞埃鍋撳☉娆嬬細闁活厹鍊濋弻娑㈠箻鐠虹儤鐏堥悗瑙勬礃濡炰粙宕洪埀顒併亜閹哄秹妾峰ù婊勭矒閺岀喖鎮滃Ο铏逛淮闂侀€炲苯澧紓宥咃工椤曪綁骞庣粵瀣櫌闂佸憡娲﹂崜娑㈠储闁秵鐓熼幖鎼灣缁夐潧霉濠婂懎鍘撮柣鎿冨墴椤㈡宕掑Δ鈧禍楣冩偡濞嗗繐顏痪鎯ь煼閺屾稑螖閳ь剟宕崸妤婃晪闁挎繂顦壕褰掓煟閺囨氨鍔嶉棄瀣⒒閸屾瑧顦﹂柟纰卞亜铻為悗闈涙憸娑撳秹鏌熼幑鎰靛殭闁藉啰鍠栭弻鏇熺箾閻愵剚鐝曢梺绋款儏濡繈寮诲☉姘勃闁告挆鈧Σ鍫濐渻閵堝懘鐛滈柟鍑ゆ嫹
核心提示:简介视图通常用来区分逻辑数据库模式和物理模式,遗憾的是,INSTEAD OF 触发器 - 所有视图都是可更新的!,在 UPDATE、DELETE 或 INSERT 操作中通常都达不到预期的透明度,因为除了最简单的视图之外所有的视图都不可更新,或者直接的、间接的、特殊的、由此产生的、附带的损失或惩罚性的损失赔偿负责,即使

简介

视图通常用来区分逻辑数据库模式和物理模式。遗憾的是,在 UPDATE、DELETE 或 INSERT 操作中通常都达不到预期的透明度,因为除了最简单的视图之外所有的视图都不可更新。本文评估哪些视图自己是可更新的,然后引入 INSTEAD OF 触发器 - 这是 DB2® Universal Database™ V8.1 for Linux、UNIX® 和 Windows® 的新功能,它使所有视图都可以更新。

先决条件

本文中的许多示例都使用 SQL 过程化语言(SQL Procedural Language,SQL PL)。由于 SQL PL 在其主体中使用分号( ;)来分隔各个语句,所以对 CLP 或命令中心(Command Center)必须使用另一个定界符。在本篇文章中我将使用美元符号( $)来给语句定界。

要启动 CLP 会话以使用本文中的示例,请从 shell 输入: db2 -td$ 。对于命令中心,通过单击 Tools -> ToolsSettings -> Use statement termination character来设置并激活定界符。

纵览可更新视图

在我们向您完整地介绍可更新视图之前,我们需要基本了解一下“可更新”是什么意思以及 DB2(或就这一点而言的任何数据库管理系统)必须解决哪些基本问题才能通过视图更新数据库中的行。首先,缺省情况下 DB2 所拥有的全部视图信息就是视图的定义,即指定由该视图派生出的表的查询。

为了精确起见,我们需要区别三个级别的可更新能力:

可删除:为了从视图中删除一行,DB2 必须能够将视图中指定的那一行映射到基本表中有且仅有的一行。
可更新:为了更新视图中的列,DB2 必须不仅能够将视图中指定的一行映射到基本表中的一行,它还必须能够将要更新的一列映射到基本表中的一列。因此,根据定义所有可更新的视图必须是可删除的。
可插入:为了将行插入到视图中,DB2 必须能将新的行映射到一张表中并能够将指定的所有列映射到该表中的列。因此所有可插入的视图要定义成是可更新的,从而也是可删除的。

现在让我们定义一些表,然后研究这些表的各种视图的可更新能力:

CREATE TABLE T1(c1 INT, c2 FLOAT)$ 
INSERT INTO T1 VALUES (5, 6.0), 
           (6, 7.0), 
           (5, 6.0)$ 
CREATE TABLE T2(c1 INT, c2 FLOAT)$ 
INSERT INTO T2 VALUES (5, 9.0), 
           (5, 4.0), 
           (7, 5.0)$

对于本文中的所有示例,让我们假设提供了下列数据。示例不应该相互影响。可以通过在 CLP 中使用 +c option或从命令中心取消激活 Options -> Execution -> Automatically Commit 来关闭(OFF)自动提交,从而使它们不相互影响。在每个示例结束时使用 ROLLBACK 命令。

示例 1:

CREATE VIEW V1(c1) 
AS SELECT c1 FROM T1 WHERE c2 > 0$

这是一个非常简单的视图。派生的表包含了 T1 的行子集和列子集。这个视图是可删除的,因为 DB2 可以跟踪每一行的起源并在基本表中删除各自的行。

DELETE FROM V1 WHERE c1 = 6$

但是请注意,如果有人执行了定位删除并删除 V1.c1 等于 5 的那一行,那么就看不出将删除 T1.c1 等于 5 的那两行中的哪一行。

视图也是可更新的:

UPDATE V1 SET c1 = c1 + 5 WHERE c1 = 5$

可以直接将 V1.c1 映射到 T1.c1。

INSERT 会怎么样呢?显然,应当将插入到 V1 中的任意行插入到 T1 中。为 V1.c1 指定的值应当用于 T1.c1。但是 T1.c2 呢?将使用哪些值?毕竟,视图并不知道这一列,因此用户不能为它指定值。

INSERT INTO V1 VALUES (8)$

这里需要一个定义,而 SQL 标准已经提供了该定义:将每个未提供的列值初始化成该列的隐式缺省值或显式缺省值。由于没有为 T1.c2 指定显式缺省值,所以选择值 NULL 并将(8, NULL)插入到 T1 中。

示例 2:

当有表达式时会是怎样?

CREATE VIEW V2(c1, c2) 
AS SELECT c1, c2 * c2 FROM T1$

与第一个示例相比较,在可删除性方面没什么改变。DB2 仍然知道视图中的某一行是由基本表中的哪一行生成的。V2.c1 列是可更新的,因此该视图也是可更新的。

但是,V2.c2 是不可更新的。原因是从任何给定的 V2.c2 无法确定 T1.c2 的值。DB2 可以设法使用除法操作来查找匹配值;但是,这个简单的示例恰恰阐明这样的数学方法只能在用在最为简单的情形中。这个小小的尝试用来说明这样的算法的功能和限制,显示出这个算法缺乏可用性。

那么对于可插入性这到底又意味着什么呢?在 DB2 V8 之前,为使视图可插入,就要求视图的所有列都是可更新的。在 DB2 V8 中,只要有一个可更新的列就足够了。只要没有指定其它列,那么就可以忽略它们。

INSERT INTO V2(c1) VALUES (7)$

系统将把(7, NULL)插入到 T1 中。请注意,即使没有一列是可更新的,视图也是可删除的。同理,反对将不可更新的列插入到视图中的唯一理由就是在不是单一列的情况下既没有定义 VALUES 也没有定义 SELECT。

示例 3:

CREATE VIEW V3(c1, c2, c3) 
AS SELECT T1.c1, T1.c2, T2.c2 
   FROM T1, T2 WHERE T1.c1 = T2.c1$

这个视图是从连接(join)派生出的。在本例中,其结果是:

SELECT * FROM V3 ORDER BY c1, c2, c3$ 
    C1 C2  C3 
-- --- --- 
 5 6.0 4.0 
 5 6.0 4.0 
 5 6.0 9.0 
 5 6.0 9.0 

这个视图是不可删除的。该视图中的每一行都可以追溯到表 T1 和 T2 中的某一行,通过删除 T1 和 T2 中的各自行来删除第一行(5, 6.0, 4.0),那么也会间接删除第二行(5, 6.0, 4.0)和两个(5, 6.0, 9.0)行中的一行。这个行为不是很直观,对于不知道视图查询的用户尤为如此。

有的时候在视图中删除一行导致基本表中也删除一行,同时并没有对视图造成不希望出现的影响。例如,如果 T1.c1 和 T2.c1 都是唯一的,就属于上述情况。DB2 现在不考虑这种特殊情况。

由于 V3 是不可删除的,因此它也是不可更新的。再次声明,如果您想更新视图中的某一行,会发生令人奇怪的事情;如果想更新 V1.c1,以使视图中不同行拥有不同的值,那么情况会更糟糕。

同样,INSERT 的语义不清楚。根据为基本表插入操作选择的语义,添加另一行(5, 6.0, 9.0)会产生不同的结果。这是否意味着可更新视图不能引用另一张表?并非如此。如 示例 2所讨论的那样,带有表达式的视图具有很好的可更新性。因此标量子查询就非常适合。

示例 4:

CREATE VIEW V4(c1, c2) 
AS SELECT c1, c2 FROM T1 
  UNION ALL 
  SELECT c1, c2 FROM T2$

V4 中的每一行明显源自特定表中的某一行。因此,基于 UNION ALL 的视图是可删除的。如果列没有基于表达式,那么该列也是可更新的。但是,该视图是不可插入的,原因很明显:它不能确定任何给定的行应当插入到哪一张基本表中。允许将某一行同时插入到两张基本表中并不合适,因为随后从视图中进行选择将两次显示该行。为允许通过 UNION ALL 进行 INSERT,需要对基本表进行约束:只能将任一给定行分派给一张表。

请参阅 http://www.ibm.com/developerworks/cn/dmdd/library/techarticles/0209rielau/0209rielau.shtml以获取有关通过 UNION ALL 视图进行 INSERT 的更多详细情况。

对通过 UNION ALL 得到的视图有一些限制,应当加以注意。UNION ALL 视图的列是不可更新的,即使 DB2 自身已插入了表达式也是如此。

CREATE VIEW V5(c1, c2) 
AS SELECT c2, c1 FROM T1 
  UNION ALL 
  SELECT c1, c2 FROM T2$

DB2 必须同时将 T2.c1 和 T1.c1 的数据类型转换为 FLOAT。V5.c1 和 V5.c2 现在都基于表达式,因此它们是不可更新的。但是,V5 仍然是可删除的。

CREATE VIEW V6(c1, c2) 
AS SELECT c1, c2 FROM T1 
  UNION ALL 
  SELECT c1, c2 FROM T1$

象 V6 这样的视图也可称为“菱形”,因为处理从一个源分散为两个操作(SELECT),然后再返回到一起(UNION ALL)。菱形是只读的。不能删除行。原因在于 T1 中的每一行在 V6 中被表示了两次。因此在 V6 中不可能只删除一行。另外也不可能只更新一行。

当使用 UNION(或 DISTINCT)时会出现相反的问题。现在可以将视图中的每一行映射到基本表中可能的许多行。应当只删除基本表中的一行还是删除基本表中的所有匹配行?

我们已经讨论了一些不同类型视图的可更新性背后的想法。其它视图是不可更新的;但是,让我们将目光转向视图可更新性的重要“武器”:INSTEAD OF 触发器。

INSTEAD OF 触发器

视图是由其主体定义的。对于 SELECT 查询而言这一点非常明显。对于我们在上面所讨论的 UPDATE、DELETE 和 INSERT 而言,尽管对于某些类有可能假设视图如何定义明显的语义,而在大多数情况下无法做到这一点。这就是 INSTEAD OF 触发器的突破口。INSTEAD OF 触发器阻止 DB2 去尝试解释更新操作的视图定义。与此“相反”,它将执行触发器主体,依靠定义者来得出有意义的语义。

例如:

CREATE VIEW V7(c1, c2) 
AS SELECT DISTINCT c1, c2 FROM T1$

可以将 INSTEAD OF 触发器定义成删除 T1 中与 V7 中给定行相匹配的所有行或者根据某条预定规则仅删除一行。

让我们定义一个触发器,它删除基本表中所有的匹配行:

CREATE TRIGGER V7_DELETE INSTEAD OF DELETE ON V7 
 REFERENCING OLD_TABLE AS OLD_TAB 
 OLD AS O FOR EACH ROW MODE DB2SQL 
 DELETE FROM T1 WHERE o.c1 = c1 AND o.c2 = c2$

从表面上看,与普通触发器相比,只有两个子句发生了变化。首先我们定义了关键字 INSTEAD OF。其次我们指定了视图名而不是基本表名。

INSTEAD OF 是一个非常清晰的子句。它并不表示在试图进行删除操作之前执行触发器。也不表示在进行删除操作之后执行触发器。按照字面意思,就是表示忘掉删除操作。转而执行这段代码。

INSTEAD OF 触发器总是为视图而创建的,永远不是为基本表或别名而创建的。DB2 V8 中的 INSTEAD OF 触发器始终是 FOR EACH ROW 触发器。这意味着针对每个可以对视图进行删除、更新或插入操作的行,都要执行一次这个触发器。

该触发器中转换表和转换变量的概念与普通触发器中的概念一致。下面的 表 1显示了根据 INSTEAD OF 触发器的类型,可以在哪里使用转换变量和转换表:

表 1. 转换变量

 OLD OLD_TABLE NEW NEW_TABLE
INSERT NONOYESYES
UPDATE YESYESYESYES
DELETE YESYESNONO

OLD_TABLE 和 NEW_TABLE 转换表指定了由视图处理的行集。OLD 和 NEW 转换变量是由当前对触发器的调用所处理的各自转换表中的特定行。

在 DELETE 或 UPDATE 触发器中,OLD_TABLE 是符合删除条件的行集。这表示它们是视图上的游标能够看到的行。下面是我们示例中的 OLD_TABLE 值:

OLD_TAB: 
 C1  C2 
--- ---- 
 5 6.0 
 6 7.0 

触发器处理行的顺序是任意的。这意味着没有规定上面的触发器先将(5, 6.0)还是先将(6, 7.0)看作 OLD 转换行。还有一点需要注意,对于所有触发器(包括 INSTEAD OF 触发器),转换表是在触发器被触发之前完全确定好的。这意味着我们可以在 T1 中进行删除,而不会看到 OLD_TABLE 中的更改。

请注意,上面的触发器在触发操作中不能有 WHEN 子句。INSTEAD OF 触发器是无条件触发的。对于 BEFORE 和 AFTER UPDATE 触发器的典型列列表而言也是如此。这些也是一种触发器条件,而 INSTEAD OF 触发器不会考虑它们。

CREATE TRIGGER V7_UPDATE INSTEAD OF UPDATE ON V7 
 REFERENCING NEW AS n OLD AS o 
 FOR EACH ROW MODE DB2SQL 
 UPDATE T1 SET (c1, c2) = (n.c1, n.c2) 
  WHERE c1 = o.c1 AND c2 = o.c2$

无论更新哪一列都会触发该触发器。

图 1显示了 INSTEAD OF 触发器的完整语法图:

图 1. INSTEAD OF 触发器的语法图

>>-CREATE TRIGGER-- 
     trigger-name--INSTEAD OF--+-INSERT-+--ON-- 
     view-name-------> 
                       +-DELETE-+ 
                       '-UPDATE-' 
 
>-----+---------------------------------------------------------------------+> 
   |       .----------------------------------.          | 
   |       V    .-AS-.           |          | 
   '-REFERENCING---+-OLD--+----+-- 
     correlation-name-+-+-+---------------+-' 
           |   .-AS-.          |  
           +-NEW-+----+-- 
     correlation-name--+ 
           |      .-AS-.       | 
           +-OLD_TABLE-+----+-- 
     identifier--+ 
           |      .-AS-.       | 
           '-NEW_TABLE-+----+-- 
     identifier--' 
 
>----FOR EACH ROW---MODE DB2SQL---- 
     SQL-procedure-statement------------------>< 

注:DB2 V8.1 文档显示需要 DEFAULTS NULL子句。这个子句在 V8.1 中已经 除去了。请参阅下面的 异常表示例以获取 INSTEAD OF 触发器上下文中缺省值的行为。

SQL-procedure-statement可以是 SQL 语句(比如 INSERT、UPDATE、DELETE、SELECT 或 VALUES),也可以是使用 BEGIN ATOMIC ... END 的动态复合语句。

通过示例进行学习

理论到此结束。让我们研究一下您在实际生活中可能会碰到的某些示例。

垃圾箱

INSTEAD OF 触发器编写得好坏都取决于您自己。视图定义和 INSTEAD OF 触发器之间的唯一联系在于转换表的行定义(特征符),这些表的列类型和名称与从视图查询中继承的列类型和名称相匹配。

这是什么意思?如果您定义了 INSTEAD OF 触发器,您就可以做您喜欢做的任何事。

CREATE VIEW GARBAGE(text) 
AS SELECT CAST(NULL AS CLOB(20M)) 
   FROM SYSIBM.SYSDUMMY1 
   WHERE 1 = 0$ 
   
CREATE TRIGGER INSERT_GARBAGE 
INSTEAD OF INSERT ON GARBAGE FOR EACH ROW MODE DB2SQL 
BEGIN ATOMIC 
END$ 
INSERT INTO GARBAGE VALUES 'unrecoverable document'$

哦,我们在这里为自己做了一个漂亮的垃圾箱。鉴于此,只允许对视图拥有 CONTROL 特权的用户、视图定义者、SYSADM 或 DBADM 才能在视图上创建 INSTEAD OF 触发器。

透明加密

在介绍了 INSTEAD OF 触发器这种最简单的应用之后,让我们讨论更常见的类型,也就是使用 INSTEAD OF 触发器在视图中为加密表达式提供“逆”操作。

CREATE TABLE USERS 
  (user VARCHAR(20), 
  system VARCHAR(30), 
  login VARCHAR(20), 
  password VARCHAR(40) FOR BIT DATA)$

上面的表包含了不同系统上用户的标识和加密密码。下面的视图假定在用户提供了正确凭证的前提下,对与当前用户相关的行进行解密。

CREATE VIEW MY_LOGINS(system, login, password) 
AS SELECT system, login, decrypt_char(password) 
   FROM USERS AS u WHERE u.user = USER$ 

为了通过这个视图进行更新或插入,我们需要定义 INSTEAD OF 触发器,该触发器对用户提供的密码进行加密以将其存储在基本表中。

CREATE TRIGGER INSERT_MY_LOGINS INSTEAD OF INSERT 
 ON MY_LOGINS REFERENCING NEW AS n 
 FOR EACH ROW MODE DB2SQL 
 INSERT INTO USERS 
  VALUES(USER, n.system, n.login, 
      encrypt(password))$ 
CREATE TRIGGER UPDATE_MY_LOGINS INSTEAD OF UPDATE 
 ON MY_LOGINS REFERENCING OLD AS o NEW AS n 
 FOR EACH ROW MODE DB2SQL 
 UPDATE USERS U 
  SET system = n.system, 
    login = n.login, 
    password = encrypt(n.password) 
  WHERE system = o.system 
   AND login = o.login 
   AND U.user = USER$

加密数据通过 ENCRYPTION PASSWORD 特殊寄存器受到保护。

SET ENCRYPTION PASSWORD = 'unbreakable'$

现在把我们的机密信息填入表中:

INSERT INTO MY_LOGINS 
 VALUES('AFS',  'srielau', 'mydogsname'), 
    ('Linux', 'root',  'oopsIforgot'), 
    ('IIUG', 'Rielau', '123456789')$ 
SELECT * FROM MY_LOGINS WHERE system = 'Linux'$ 
SYSTEM LOGIN   PASSWORD 
------ -------  ------------ 
Linux  root   oopsIforgot

在基本表上密码是加过密的。没有上面设置的密码,任何人(即使是 DB2 支持人员)都不能取回信息。

SELECT * FROM USERS U ORDER BY U.user, system, login$ 
    USER   SYSTEM  LOGIN   PASSWORD 
-------- ------- -------  ------------- 
SRIELAU AFS   srielau  0x........... 
SRIELAU IIUG   Rielau  0x........... 
SRIELAU Linux  root   0x........... 

可以更新视图中的 password 或任何其它字段。

UPDATE MY_LOGINS 
 SET password = 'mycatsname' 
 WHERE system = 'AFS' AND login = 'srielau'$

但是,首先有一点,视图是可删除的。因此我们不用 INSTEAD OF DELETE 触发器就可以删除各项。

DELETE FROM my_logins WHERE SYSTEM = 'AFS'$

有关加密的更多详细情况,请参阅 http://www7b.boulder.ibm.com/dmdd/library/techarticle/benfield/0108benfield.html。

让我们用一个较复杂的安全性示例来增加点复杂性。

组级和行级安全性

经理可以查看其雇员的工资,但是不能查看其同级经理或那些经理所管辖的那些雇员的工资。二线经理可以看到其直接以及间接报告的工资。

让我们对这样的模式进行建模,然后定义 INSTEAD OF 触发器,以允许经理可以更新其管辖雇员的信息。

CREATE TABLE PROFILES 
(empid INT, name VARCHAR(20), sqlid VARCHAR(18), 
 mgrid INT, salary DECIMAL(9,2), ismgr CHAR(1))$

我们想填入一些数据,在本例中是一个四层的层次结构。我们是名叫“MySelf”的二线经理。

INSERT INTO PROFILES 
VALUES(0001, 'SuperBoss', 'sboss', NULL, 500000, 'Y'), 
   (1001, 'BigBoss',  'bboss', 0001, 200000, 'Y'), 
   (1002, 'MySelf',   USER,  0001, 250000, 'Y'), 
   (2001, 'FirstLine', 'fline', 1001, 100000, 'Y'), 
   (2002, 'MiddleMen', 'mmen', 1001, 110000, 'Y'), 
   (2003, 'Yeti',    'yeti', 1002, 90000, 'Y'), 
   (2004, 'BigFoot',  'bfoot', 1002, 80000, 'N'), 
   (3001, 'TinyToon',  'ttoon', 2001, 50000, 'N'), 
   (3002, 'Mouse',   'Mouse', 2001, 40000, 'N'), 
   (3003, 'Whatsisname','wname', 2002, 45000, 'N'), 
   (3004, 'Hasnoclue', 'hclue', 2002, 38000, 'N'), 
   (3005, 'Doesallwork','dwork', 2003, 15000, 'N')$

递归视图允许我们确定所有雇员的工资,包括我们自己的工资。

CREATE VIEW my_emps(empid, level, salary) 
AS WITH rec(empid, level, salary) 
     AS (SELECT empid, 0, salary FROM PROFILES 
        WHERE sqlid = USER 
       UNION ALL 
       SELECT P.empid, level-1, P.salary 
        FROM PROFILES P, REC R 
        WHERE level > -100 
         AND R.EMPID = P.MGRID) 
  SELECT empid, level, salary FROM rec$

下面的视图将是个接口,被任何人用来读取雇员信息。我们考虑了雇员标识(empid)、雇员姓名(name)、他们经理的姓名(name)、他们的 SQLID(sqlid)以及他们是否是经理(ismgr)和公共信息。雇员只能看到他们自己的工资及其下属的工资。

CREATE VIEW PROFILES_V(empid, name, mgrname, 
            salary, sqlid, ismgr) 
 AS SELECT P.empid, P.name, 
      (SELECT name FROM PROFILES M 
       WHERE M.empid = P.mgrid), 
      ME.salary, P.sqlid, P.ismgr 
   FROM PROFILES P LEFT OUTER JOIN MY_EMPS ME 
    ON ME.empid = P.empid$

根据我们早先的分析,我们知道这个视图是不可删除的,因此也是不可更新或插入的。为了保持封装性,我们需要定义一组完整的 INSTEAD OF 触发器:

CREATE TRIGGER INSERT_PROFILES_V 
 INSTEAD OF INSERT ON PROFILES_V REFERENCING NEW AS n 
 FOR EACH ROW MODE DB2SQL 
 BEGIN ATOMIC 
  DECLARE mgrid INT; 
  DECLARE ismgr CHAR(1); 
  SET (mgrid, ismgr) 
   = (SELECT empid, ismgr FROM PROFILES 
     WHERE name = n.mgrname); 
  IF mgrid NOT IN (SELECT empid FROM my_emps) 
    OR ismgr = 'N' 
  THEN 
   SIGNAL SQLSTATE '70000' 
     SET MESSAGE_TEXT = 'Not Authorized!'; 
  END IF; 
  INSERT INTO PROFILES 
   VALUES(n.empid, n.name, n.sqlid, 
       mgrid, n.salary, n.ismgr); 
 END$

这个触发器强制执行这条业务规则:假如用户自己是经理,那么他们只能插入作为其下属的雇员。

下面的 INSTEAD OF 更新触发器强制执行这样一条业务规则:只能更新下属的雇员,并且如果一个雇员从一个经理的手下调到了另一个经理的手下,那么这两个经理必须都是执行更新操作的那个用户的下属。

DB2 没法知道用户实际上更新了哪些列,指出这一点很重要。DB2 只能比较 OLD 和 NEW 转换变量以检测更改。在本例中,触发器旨在始终更新整个行。这样做对性能的影响并不象一开始看上去的那么糟,因为 DB2 的记录器可以检测到实际上更改的是哪个部分然后避免额外的日志记录。

CREATE TRIGGER UPDATE_PROFILES_V 
 INSTEAD OF UPDATE ON PROFILES_V REFERENCING NEW AS n 
 OLD AS o FOR EACH ROW MODE DB2SQL 
 BEGIN ATOMIC 
  DECLARE oldmgrid, newmgrid INT; 
  SET oldmgrid = (SELECT empid FROM PROFILES 
          WHERE name = o.mgrname), 
    newmgrid = (SELECT empid FROM PROFILES 
          WHERE name = n.mgrname); 
  IF oldmgrid NOT IN (SELECT empid FROM my_emps) 
    OR newmgrid NOT IN (SELECT empid FROM my_emps) 
    OR o.empid = (SELECT empid FROM PROFILES P 
           WHERE USER = P.sqlid) 
  THEN 
   SIGNAL SQLSTATE '70000' 
     SET MESSAGE_TEXT = 'Not Authorized!'; 
  END IF; 
  UPDATE PROFILES SET empid = n.empid, 
            mgrid = newmgrid, 
            salary = n.salary, 
            sqlid = n.sqlid, 
            name = n.name, 
            ismgr = n.ismgr 
   WHERE empid = o.empid; 
 END$

最后,下面的 INSTEAD OF 删除触发器保证了用户只能删除其下属,而永远不能删除他们自己。

CREATE TRIGGER DELETE_PROFILES_V 
 INSTEAD OF DELETE ON PROFILES_V 
 REFERENCING OLD AS o FOR EACH ROW MODE DB2SQL 
 BEGIN ATOMIC 
  DECLARE mgrid INT; 
  SET mgrid = (SELECT empid FROM PROFILES 
         WHERE name = o.mgrname); 
  IF mgrid NOT IN (SELECT empid FROM my_emps) 
    OR o.empid = (SELECT empid FROM PROFILES P 
           WHERE USER = P.sqlid) 
  THEN 
   SIGNAL SQLSTATE '70000' 
     SET MESSAGE_TEXT = 'Not Authorized!'; 
  END IF; 
  DELETE FROM PROFILES WHERE empid = o.empid; 
 END$

完成我们的 DB 模式并将一些初始数据填入表中之后,我们可以查看一切是否正常工作:

SELECT * FROM PROFILES_V ORDER BY empid$ 
    EMPID NAME     MGRNAME  SALARY  SQLID  ISMGR 
----- ------------ ---------- --------- -------- ----- 
  1 SuperBoss  -         - sboss  Y 
 1001 BigBoss   SuperBoss     - bboss  Y 
 1002 MySelf    SuperBoss 250000.00 SRIELAU Y 
 2001 FirstLine  BigBoss      - fline  Y 
 2002 MiddleMen  BigBoss      - mmen   Y 
 2003 Yeti     MySelf   90000.00 yeti   Y 
 2004 BigFoot   MySelf   80000.00 bfoot  N 
 3001 TinyToon   FirstLine     - ttoon  N 
 3002 Mouse    FirstLine     - Mouse  N 
 3003 Whatsisname MiddleMen     - wname  N 
 3004 Hasnoclue  MiddleMen     - hclue  N 
 3005 Doesallwork Yeti    15000.00 dwork  N 

到目前为止,非常顺利。让我们看看 INSTEAD OF 触发器是否象所设计的那样工作。首先我们雇佣 NewGuy 并让他在 Yeti 手下工作:

INSERT INTO PROFILES_V 
VALUES (3006, 'NewGuy', 'Yeti', 35000, 'nguy', 'N')$ 
SELECT * FROM PROFILES_V WHERE empid = 3006$ 
    EMPID NAME     MGRNAME  SALARY  SQLID  ISMGR 
----- ------------ ---------- --------- -------- ----- 
 3006 NewGuy    Yeti    35000.00 nguy   N 

接下来我们把 Doesallwork 提升为 Myself 手下的管理人员并将他的工资提高 30%:

UPDATE PROFILES_V 
 SET ismgr = 'Y', 
   salary = salary * 1.30, 
   mgrname = 'MySelf' 
 WHERE name = 'Doesallwork'$ 
  
SELECT * FROM PROFILES_V WHERE name = 'Doesallwork'$ 
    EMPID NAME     MGRNAME  SALARY  SQLID  ISMGR 
----- ------------ ---------- --------- -------- ----- 
 3005 Doesallwork MySelf   19500.00 dwork  Y 

为使这个示例保持逼真,Doesallwork 想通了并离开公司:

DELETE FROM PROFILES_V WHERE name = 'Doesallwork'$

到目前为止我们已经有多个视图示例,包括没有表的视图、一张表的视图和外连接中所涉及的一张表的视图。让我们现在研究一下所有表都需要更新的多表连接。

垂直分区数据(即星型连接)

要求对所有的表(同时)进行连接和更新的典型方案是数据的垂直分区。为了简单起见,我们使用 PERSONS 模式。一些 PERSONS 被雇佣,另一些则作为学生招收。有一些 PERSONS 既是雇员又是学生。

CREATE TABLE PERSONS(ssn INT NOT NULL, 
           name VARCHAR(20) NOT NULL)$ 
           
CREATE TABLE EMPLOYEES(ssn INT NOT NULL, 
           company VARCHAR(20) NOT NULL, 
           salary DECIMAL(9,2))$ 
            
CREATE TABLE STUDENTS(ssn INT NOT NULL, 
           university VARCHAR(20) NOT NULL, 
           major VARCHAR(10))$

要在应用程序中把所有这些表连接在一起,可能会令人头疼。因此我们创建了一个视图:

CREATE VIEW PERSONS_V(ssn, name, company, 
           salary, university, major) 
AS SELECT P.ssn, name, company, 
     salary, university, major 
   FROM PERSONS P LEFT OUTER JOIN EMPLOYEES E 
             ON P.ssn = E.ssn 
          LEFT OUTER JOIN STUDENTS S 
             ON P.ssn = S.ssn$

再次声明,这个视图既是不可插入的,也是不可更新或删除的。因此我们需要生成一组完整的 INSTEAD OF 触发器:

CREATE TRIGGER INSERT_PERSONS_V 
 INSTEAD OF INSERT ON PERSONS_V 
 REFERENCING NEW AS n FOR EACH ROW MODE DB2SQL 
 BEGIN ATOMIC 
  INSERT INTO PERSONS VALUES (n.ssn, n.name); 
  IF n.university IS NOT NULL THEN 
   INSERT INTO STUDENTS 
    VALUES(n.ssn, n.university, n.major); 
  END IF; 
  IF n.company IS NOT NULL THEN 
   INSERT INTO EMPLOYEES 
    VALUES(n.ssn, n.company, n.salary); 
  END IF; 
 END$

如果某人与大学相关联,上面的触发器则认为他是一个学生;如果某人与公司相关联,上面的触发器则认为他是一个雇员。

实现 INSTEAD OF DELETE 触发器非常简单。从所有这三张表中删除一个人是很安全的,即使这三张表中没有任何项也是如此:

CREATE TRIGGER DELETE_PERSONS_V 
 INSTEAD OF DELETE ON PERSONS_V 
 REFERENCING OLD AS o FOR EACH ROW MODE DB2SQL 
 BEGIN ATOMIC 
  DELETE FROM STUDENTS WHERE ssn = o.ssn; 
  DELETE FROM EMPLOYEES WHERE ssn = o.ssn; 
  DELETE FROM PERSONS WHERE ssn = o.ssn; 
 END$

现在事情变得有些棘手了。正如定义插入触发器时所表明的那样,学生必须和大学相关联,而雇员必须与公司相关联。因此如果某人获得或失去这些关联之一,那么就需要在相应的表中添加或除去行:

CREATE TRIGGER UPDATE_PERSONS_V 
 INSTEAD OF UPDATE ON PERSONS_V 
 REFERENCING OLD AS o NEW AS n 
 FOR EACH ROW MODE DB2SQL 
 BEGIN ATOMIC 
  UPDATE PERSONS 
   SET (ssn, name) = (n.ssn, n.name) 
   WHERE ssn = o.ssn; 
  IF n.university IS NOT NULL 
    AND o.university IS NOT NULL THEN 
   UPDATE STUDENTS 
    SET (ssn, university, major) 
     = (n.ssn, n.university, n.major) 
    WHERE ssn = o.ssn; 
  ELSEIF n.university IS NULL THEN 
   DELETE FROM STUDENTS WHERE ssn = o.ssn; 
  ELSE 
   INSERT INTO STUDENTS 
    VALUES(n.ssn, n.university, n.major); 
  END IF; 
  IF n.company IS NOT NULL 
    AND o.company IS NOT NULL THEN 
   UPDATE EMPLOYEES 
    SET (ssn, company, salary) 
     = (n.ssn, n.company, n.salary) 
    WHERE ssn = o.ssn; 
  ELSEIF n.company IS NULL THEN 
   DELETE FROM EMPLOYEES WHERE ssn = o.ssn; 
  ELSE 
   INSERT INTO EMPLOYEES 
    VALUES(n.ssn, n.company, n.salary); 
  END IF; 
 END$

既然已经完整定义了视图,它包含了用于插入、删除和更新的触发器,那就让我们对其进行测试:

INSERT INTO PERSONS_V VALUES 
 (123456, 'Smith', NULL, NULL, NULL, NULL), 
 (234567, 'Jones', 'Wmart', 20000, NULL, NULL), 
 (345678, 'Miller', NULL, NULL, 'Harvard', 'Math'), 
 (456789, 'McNuts', 'SelfEmp', 60000, 'UCLA', 'CS')$ 
  
SELECT * FROM PERSONS_V ORDER BY SSN$ 
    SSN   NAME  COMPANY SALARY  UNIVERSITY MAJOR 
------ ------ -------- -------- ----------- ------ 
123456 Smith  -        - -      - 
234567 Jones  Wmart  20000.00 -      - 
345678 Miller -        - Harvard   Math 
456789 McNuts SelfEmp 60000.00 UCLA    CS   

现在 Smith 先生结婚了,采用他妻子的姓“Johnson”,并且他以 15000 的工资被 Mickburgs 聘用,而 Miller 女士从 Harvard 毕业并被 IBM 聘用。

UPDATE PERSONS_V SET (name, company, salary) 
          = ('Johnson', 'Mickburgs', 15000) 
 WHERE SSN = 123456$ 
  
UPDATE PERSONS_V SET (company, salary, university) 
          = ('IBM', 70000, NULL) 
 WHERE SSN = 345678$ 
  
SELECT * FROM PERSONS_V WHERE SSN IN (123456, 345678) 
 ORDER BY SSN$ 
  
    SSN   NAME   COMPANY SALARY  UNIVERSITY MAJOR 
------ ------- -------- -------- ----------- ------ 
123456 Johnson Mickburgs  15000 -      - 
345678 Miller  IBM     70000 -      - 

Jones 先生长期出国:

DELETE FROM PERSONS_V WHERE NAME = 'Jones'$

最后说明一点,在插入时我们可以省略值为 NULL 的列:

INSERT INTO PERSONS_V(ssn, name) 
 VALUES (567890, 'vanderpoor')$

既然这么说了,那么谈论 DEFAULTS 就有意义了。我们将在下一个(也就是最后一个)示例中谈谈这一点及其它。

异常表处理

将数据插入表时,就会使用数据类型、BEFORE 触发器、检查约束和 RI 约束来限制用某种方式或其它方式插入的数据。SET INTEGRITY、LOAD 和 IMPORT 都允许用户通过将被拒绝的行存储到异常表中来处理有问题的数据。但是,INSERT 和 UPDATE 自身并没有办法可处理有问题的数据。应用程序负责尝试诸如插入之类的操作,如果失败了,就采取必要的步骤。不便之处在于两个方面:

首先,处理不是封装的。

其次,单行插入很浪费,即使通过存储过程完成时也是如此。

下面的示例使用一个 INSTEAD OF INSERT 触发器将有问题的行分派到另一个位置。

CREATE TABLE ADDRESSES(name varchar(10), 
            number INT, 
            street varchar(20), 
            country VARCHAR(10) 
                WITH DEFAULT 'CANADA')$ 
                
CREATE TABLE BAD_ADDRESSES 
AS (SELECT CAST(NULL AS VARCHAR(30)) AS Reason, 
      A.* 
  FROM ADDRESSES A) DEFINITION ONLY$ 
   
CREATE VIEW ADDRESSES_V 
AS SELECT * FROM ADDRESSES$ 
CREATE TRIGGER INSERT_ADDRESSES_V 
 INSTEAD OF INSERT ON ADDRESSES_V 
 REFERENCING NEW AS n FOR EACH ROW MODE DB2SQL 
 BEGIN ATOMIC 
  DECLARE reason VARCHAR(30); 
  SET reason 
   = CASE WHEN n.number IS NULL OR n.number <= 0 
       THEN 'Number' 
       WHEN n.name IS NULL OR LENGTH(n.name) = 0 
       THEN 'Name' 
       WHEN n.street IS NULL 
        OR LENGTH(n.street) = 0 
       THEN 'Street' 
       WHEN country IS NULL 
        OR country NOT IN ('CANADA', 'USA', 
                 'GERMANY', 'FRANCE') 
       THEN 'Country' 
       ELSE NULL END; 
  IF reason IS NOT NULL THEN 
   INSERT INTO BAD_ADDRESSES 
    VALUES(reason, n.name, n.number, 
        n.street, n.country); 
  ELSE 
   INSERT INTO ADDRESSES 
    VALUES(n.name, n.number, n.street, n.country); 
  END IF; 
 END$ 

上面的触发器将把好的数据和有问题的数据分隔开来,并存储相应的地址。但是这个示例还说明了其它更多的东西。

请注意,当前的 DB2 V8 文档规定 INSTEAD OF 触发器应当将列的所有 DEFAULT 值看成 NULL,这一点很重要。这个行为是由于 DB2 V8 Beta 测试版的客户反馈而进行的更改,因此从 INSTEAD OF 触发器规范中删除了 DEFAULTS NULL 子句。

当定义视图时,对于可更新列从底层表中继承其列的缺省值。如果列(在没有 INSTEAD OF 触发器的帮助下)是不可更新的,那么认为 DEFAULT 为 NULL。在本例中,如果没有指定国家或地区(country),或者如果使用了关键字 DEFAULT,那么 INSTEAD OF 触发器将获取“CANADA”。对于其它所有列该触发器将获取 NULL。

INSERT INTO ADDRESSES_V VALUES 
 ('Jones', 510, 'Yonge St.', DEFAULT), 
 ('Smith', -1, 'Nowhere', 'USA'), 
 (NULL, 38, 'Am Feldweg', 'GERMANY'), 
 ('Poubelle', 23, 'Rue de Jardin', 'FRANCE')$ 
 
SELECT * FROM ADDRESSES ORDER BY name$ 
    NAME    NUMBER STREET     COUNTRY  
---------- ------ -------------- ------- 
Jones     510 Yonge St.   CANADA   
Poubelle    23 Rue de Jardin FRANCE   
SELECT * FROM BAD_ADDRESSES ORDER BY name$ 
    REASON NAME  NUMBER STREET   COUNTRY  
------ ------- ------ ----------- ------- 
Number Smith    -1 Nowhere   USA 
Name  -      38 Am Feldweg GERMANY 

可以轻松地调整该触发器以忽略有问题的行而不是将它们存储起来。

更多想法……

INSTEAD OF 触发器的其它应用可能有:

避免递归的 AFTER 触发器。
有时候碰巧 AFTER 触发器必须更新它自己的主题表,在极端的情况下,这可以使可用的语句堆变得紧张并增加语句的编译时间。对主题表使用视图和创建 INSTEAD OF 触发器可以允许对主题表进行多次更新,不会引起视图的递归触发。

实现外部表。
为使表 UDF 也拥有插入、更新和删除功能,您可能想扩展对表 UDF 的现有读访问权。定义特定于插入、更新和删除的 UDF 是切实可行的。然后您可以在表 UDF 上创建一个视图并定义 INSTEAD OF 触发器以驱动其它 UDF。

使 WebSphere® MQ 看起来象一张表。
这可能类似于上面的外部表示例,但是它具有 WebSphere MQ 函数。

注意事项

我们尝试了许多使用 INSTEAD OF 触发器的示例,但是我们还没有谈到它们的限制。现在让我们来谈一谈。

对称视图
由于 INSTEAD OF 触发器上所具有的无法控制的语义,所以您不能在对称视图上创建 INSTEAD OF 触发器。对称视图所接受的行必须可以由该视图返回。此外,不能在拥有 INSTEAD OF 触发器的视图上面创建对称视图。

可更新游标
目前在可更新游标的上下文中不支持 INSTEAD OF 触发器。这意味着如果定位更新或定位删除分别造成 INSTEAD OF UPDATE 或 INSTEAD OF DELETE 触发器触发,那么该操作将失败。

警告

触发器的触发顺序
INSTEAD OF 触发器被认为是和 AFTER 触发器一起触发的。这意味着如果对 UNION ALL 视图进行更新或删除造成 INSTEAD OF 触发器和/或 AFTER 触发器的触发,那么整组触发器将按照触发器创建时间的先后顺序进行触发。

触发器和约束处理
和 AFTER 触发器一样,INSTEAD OF 触发器主体中的 SQL 语句是完整的语义实体。这意味着:举个例子,INSTEAD OF 触发器内的 UPDATE 语句将造成各自的检查约束、RI 约束和触发器在下一个语句之前触发,最终处理转换表中的下一行。

GET_DIAGNOSTICS ROW_COUNT(又名 SQLCA.ERRD(3))
在 DB2 V8 之前,通过视图修改的行数始终等于直接在基本表上更新的行数,此处“直接”意味着不包括通过触发器和删除级联 RI 约束修改的行。有了 INSTEAD OF 触发器,这就不再成立了,而且 ROW_COUNT 的含义需要细化。DB2 V8 中的 ROW_COUNT 指定了符合进行用户所给定的更新、删除或插入操作条件的行数。对于进行这类操作的 INSTEAD OF 触发器,这个行数转换成了执行该触发器的次数和转换表的基数。

可能的改进

从 SQL 语言的角度来看,INSTEAD OF 触发器引起了一些有趣的问题和选择。例如:

视图级缺省值
由于 INSTEAD OF 触发器将视图查询的语义与其插入、删除和更新行为完全分离,因此仅从底层表派生出的缺省值似乎不再是足够的了。考虑到具有显式列缺省值(甚至可能有标识列特性)的视图似乎比较明智。

语句级 INSTEAD OF 触发器
虽然如今 DB2 只支持行级 INSTEAD OF 触发器,但是有各种各样的示例说明语句级 INSTEAD OF 触发器也可能很有用。例如,我们在上面的一个示例中所讨论的异常表触发器如果一次处理一组行而不是每一行,那么肯定会获得更好的性能。

为何视图上只有 INSTEAD OF 触发器?
我们已经介绍了视图中的一种触发器,为何就此罢手了呢?BEFORE、AFTER 触发器甚至 IDENTITY 可能是其它值得介绍的触发器。

结束语

在本文中我们讨论了可更新视图的功能和限制。我们介绍了允许您将任何视图转变成可更新视图的 INSTEAD OF 触发器,并发现了用于分布式平台的 DB2 V8 中的这种功能的各种应用。

免责声明

本文包含了样本代码。IBM 授予您(“被许可方”)使用这个样本代码的非专有的、版权免费的许可证。然而,该样本代码是以“按现状”的基础提供的,没有任何形式的(不论是明示的,还是默示的)保证,包括对适销性、适用于特定用途或非侵权性的默示保证。IBM 及其许可方不对被许可方由于使用该软件所导致的任何损失负责。任何情况下,无论损失是如何发生的,也不管责任条款怎样,IBM 或其许可方都不对由使用该软件或不能使用该软件所引起的收入的减少、利润的损失或数据的丢失,或者直接的、间接的、特殊的、由此产生的、附带的损失或惩罚性的损失赔偿负责,即使 IBM 已经被明确告知此类损害的可能性,也是如此。

Tags:INSTEAD OF 触发器

编辑录入:爽爽 [复制链接] [打 印]
赞助商链接