【网学网提醒】:文章导读:在新的一年中,各位网友都进入紧张的学习或是工作阶段。网学会员整理了TSQL与PLSQL的语法差异的相关内容供大家参考,祝大家在新的一年里工作和学习顺利!
概述TSQL与PL/SQL是两种数据库的数据库操作和控制语言。关于通用性和易用性之间的争论已经说的太多,这里不再延续这一话题。由于两种语言都有庞大的用户群,用惯了一种语言的程序员在刚开始接触另一种语言时常常会感到不适应。本章内容不简单的介绍语法,而是着重于迁移MSSQL到ORACLE时会感到困惑的一些语法和实际经验。
第一节基本语法区别1.变量定义在TSQL中,不管是变量还是存储过程的参数,都必须以@字符开头,而以@@开头的变量则常为系统提供的全局变量,例如@@SPID返回当前用户进程的服务器进程标识符。在PL/SQL中,变量定义则与大多数常用语言,例如pascal、c等相似,采用字母加数字和下划线。而在实际使用中,建议使用比较好的命名规则。对于命名规则,相信大多数读者都有好的方法。笔者在使用PL/SQL变量时,采取以下的命名规则:一般变量采用v_开头,参数用pi_开头表示输入参数,po_开头表示输出参数,pio_开头表示输入输出参数。在实际迁移中,为了方便性,会有一些其他的规则。在定义变量的数据类型时,TSQL用declare语句指示定义开始,并且可以出现在代码的任何地方,在内部块定义的变量在外部不可访问。在后面定义的变量前面不可以访问。然后PL/SQL则严格的多,必须在代码块开始之前定义变量。TSQL定义多个变量时用逗号分割,而PL/SQL用分号分割。2.赋值语句TSQL中,通常使用select语句对变量进行赋值。例如下面的语句:语句1:Select@money=@amount*@price,@fare=@amount*@ratio上面这个语句计算金额和费用。在同一个语句中使用已有变量对两个变量进行赋值。语句2:Select@name=namefromstudentswheresid=982420上面的语句将为982420的学生赋给变量@v_name在PL/SQL中,情况有所不同。首先,PL/SQL不允许select语句中没有from字句;其次,在从表中取数据到变量时,PL/SQL的行为与TSQL不同。上面的语句一在PL/SQL中,通常使用以下语句来表示:V_moneyV_fare:=v_amount*v_price;:=v_amount*v_ratio;
在PL/SQL中,可使用:=符号对变量赋值;在一个赋值语句中只能对一个变量进行赋值;语句以分号结尾,但可以多个语句在一行书写。其实在select语句中完成这一赋值也是可以的,语句如下:Selectv_amount*v_price,v_amount*v_ratiointov_money,v_farefromdual;这里引用了ORACLE中著名哑元表dual。这是ORACLE默认提供的一个全局表,该表只有一个字段一行记录,并且不可以更改其中数据。可以在任何地方任何时间引用该表。在这个赋值语句中,如同TSQL一样,只使用了一个语句就完成了对
两个变量的赋值。但是在语句结构上有比较大的区别:TSQL将每个变量与值表达式写在一起,而PL/SQL则首先列出所有字段或者表达式,然后在into语句中列出所有变量。
在使用select…into…结构的赋值语句中,在从表中取数据到变量时,PL/SQL的行为与TSQL不同。PL/SQL要求select语句只返回一行记录,否则会触发异常,具体来说是当返回零行时触发NO_DATA_FOUND异常,而返回两行以上记录时触发TOO_MANY_ROWS异常。触发异常的结果是PL/SQL不对当前变量赋值,并且程序执行跳出当前代码快,转到异常处理部分执行。(关于异常处理,本节后面会详细介绍)TSQL则以不同方式处理。首先,不管选择到多少行记录,TSQL继续当前代码的正常执行,只是在@@rowcount全局变量记录返回记录数;其次,如果选择到零行,则当前变量不赋值;如果选择到多行,则按照行的先后次序进行赋值,赋值的结果将是以最后一行记录为准。为了说明这个差异,让我们来看看下面的语句:语句含义:将姓张的学生放到变量name中去TSQLPL/SQLSelect@name=‘张无人’Select@name=namefromstudentswherenamelike‘张%’BeginSelectnameintov_namefromstudentswherenamelike‘张%’ExceptionWhenNO_DATA_FOUNDthenv_name:=‘张无人’;whenTOO_NAMY_FOUNDthenv_name:=‘张多人’;End;
假如studnts表中没有姓张的学生,那么@name和v_name最后的值都是“张无人”;如果students表中只有一个姓张的同学叫“张飞”,那么@name和v_name最后的值都是“张飞”;如果students表中有两个姓张的同学,先后是“张飞”和“张绣”,那么@name最后的值是“张绣”,而v_name最后的值是“张多人”。可以用orderby子句来改变TSQL中的结果集以影响最后的赋值结果。关于TSQL的这种滚动结果集的赋值方法,可以得到一个有趣的方法对变量进行循环赋值。在本质上来讲,返回多行时,TSQL内部循环将每一个记录都赋给变量一次。下面的语句实现求出学生“张飞”所有的课程代号放置在一个字符串内,用逗号分割:Declare@classStrvarchar(255)Select@classStr=‘’Select@classStr=@classStr+’,’+class_idfromstdclasswherestd_name=’张飞’对于PL/SQL程序员来说,这是多么神奇的事情啊!因为在PL/SQL中要完成这件事情得写一大段代码,使用游标和循环才能实现。3.条件语句结构TSQL使用begin…end来包括代码块,但是PL/SQL则使用endif来指示条件语句的结束。在else语句上,TSQL使用elseif,而PL/SQL则还可以使用elsif来简化条件层次。下面是一段比较代码:TSQLIf@stock_amount>1000BeginSelect@DoFlag=1EndPL/SQL(无b
egin…end来包括代码块)Ifv_stock_amount>1000thenv_doflag:=1;elsifv_stock_amount>100thenv_doflag:=1;
ElseBeginIf@stock_amount>100BeginSelect@DoFlag=2EndElseBeginSelect@DoFlag=3Inserttestvalues(@stock_amount)endend
elsev_doflag:=3;Insertintotestvalues(v_stock_amount);endif;
可以看出,PL/SQL的代码更为简洁一点。在TSQL中,经常使用SQL语句作为条件语句的条件,例如下面的语句:Ifexists(selectconfig_nofromsysconfigwhereconfig_no=1127)……但是在PL/SQL是不允许在条件中出现sql语句的(ifexists),上面的语句可以转化为:Selectcount(1)intov_countfromsysconfigwhereconfig_no=1127;Ifv_count>0then……Endif;这里使用的这个技巧在迁移时非常有用!掌握相应的转化方法
4.循环语句4.1WHILE语句在TSQL中,WHILE有三种形式,分别是:基本形式WhileexpressionBeginDo_somethingend中断形式WhileexpressionBeginDo_somethingIfexpression2breakend如果expression2为真,则跳出循环,执行随后的语句重新开始循环形式WhileexpressionBeginIfexpression2ContinueDo_somethingend如果expression2为真,则结束当前循环,回到循环开始处
如果expression为真,则一直做循环体内的语句
可以看出,TSQL中的WHILE语句与C语言的极其类似,其跳转语义也大致相同。在PL/SQL中,WHILE语句被称为WHILELOOP循环。在基本循环形式上与TSQL是一致的,但是在中断时,使用的是EXIT语句,并且EXIT可以带有更多的选项。PL/SQL没有CONTINUE语句,通常使用goto语句来模拟,虽然goto语句在大多数程序语言中被宣称是一种过时语句,但是在PL/SQL中有时还是很有用的。下面是上面三种循环形式的PL/SQL代码示例。
基本形式WhileexpressionloopDo_something;Endloop;
中断形式WhileexpressionloopDo_something;Exitwhenexpression2;Endloop;
重新开始循环形式WhileexpressionloopIfexpression2thenGotocontinue_lable;Endif;Do_something;<
>null;Endloop;
这里读者首先可以看到一种基本迁移continue的基本技巧,即使用goto语句和空语句null的结合。PL/SQL中定义标签需要用双尖括号。Null语句表示什么也不做,但是起到占位的作用。通常,我们将“<>null;”放置在endloop之前,这样就实现了continue语句的功能。关于GOTO语句将在下面介绍。PL/SQL的exit除了完成TSQL的break功能外,还有额外的功能。在exit后面可以跟一个条件,如上面例子中所示。当该条件满足时,程序将跳出当前循环。在介绍完循环后将介绍如何使用exit跳出外循环。4.2Loop循环Loop循环的语法是:LoopDo_something;Exitwhenexpression;Endloop;Loop循环对应与C语
言中的do_while语句,但是不能在while后面写结束条件,必须使用exit语句来结束该中循环。4.3For循环PL/SQL的for语句除了可以完成一些常规的循环功能之外,还提供一些比较特别的功能,大大提高了方便性。其基本语法如下:FORcounterIN[REVERSE]lower_bound..higher_boundLOOPsequence_of_statements
ENDLOOP;
PL/SQL的循环变量是一种特殊变量,你可以在代码段的变量定义区中显式定义该变量,也可以完全不定义该变量而直接在For循环中使用,此时PL/SQL会自动识别其类型,且将该变量的适用范围自动限制在循环内部。循环的起始值可以任意,但是其步长只能是1。注意,当适用反向标志REVERSE时,并不用将lower_bound和higher_bound颠倒位置,PL/SQL自动会从higher_bound循环到lower_bound。适用for语句还可以实现游标循环。此时,循环变量表现为行记录,可以适用访问记录的方法来访问行记录的字段(即列)。采用for循环的游标,不需要显式进行打开,也不需要使用fetch语句和游标结束判断。下面是一段代码例子:DECLAREbonusnumber(9,2);CURSORc1ISSELECTempno,sal,commFROMemp;BEGINFORc1recINc1LOOP
bonus:=(c1rec.sal*0.05)+(c1recm*0.25);INSERTINTObonusesVALUES(c1rec.empno,bonus);ENDLOOP;COMMIT;END;这样的代码体现了某些面向对象的思想,代码显得比较简洁。PL/SQL在这里走的更远,允许在FOR语句中使用隐式游标(直接在循环内使用),例如上述代码块可以写成:DECLAREbonusnumber(9,2);BEGINFORc1recIN(SELECTempno,sal,commFROMemp)LOOPbonus:=(c1rec.sal*0.05)+(c1recm*0.25);INSERTINTObonusesVALUES(c1rec.empno,bonus);ENDLOOP;COMMIT;
END;
4.4循环标签PL/SQL允许在循环前使用标签来表示循环开始处。使用循环标签可以实现以下一些功能:跳出外循环,引用外部同名变量。这些在TSQL中是无法做到的。看下面的代码例子:
<>DECLAREctrLOOP...LOOP...EXITouterWHENouter.ctr>0ENDLOOP;...--跳出外部循环INTEGER;
ENDLOOPouter;上面例子说明,想要使用exit语句跳出外部循环,方法是:首先在外部循环开始前用标签说明,然后在exit后面跟上标签名称。想要引用外部变量的原因是因为内部有同名变量。引用的方法是用外部标签名加点。4.5FORALL循环PL/SQL提供FORALL循环来批量提交语句。这是TSQL中没有的。FORALL语句的基本语法如下:FORALLindex_nameINlower_bound..upper_boundsql_statement;语法显示,这里没有FOR语句的LOOP关键字,也不需要ENDLOOP指示结束。这里的sql_statement也是有要求的,必须直接引用集合类型的insert、update或者delete语句。为何要提供FORALL语句呢?因为PL/SQL
每次提交SQL语句时都需要与SQL引擎
作交互,这是需要一定开销的。FORALL语句将多次交互压缩成一次,在循环较大时,可以大大减少开销,提高效率。下面是一个应用FORALL的例子:
DECLARETYPERatioListISVARRAY(15)OFNUMBER;ratiosRatioList:=NumList();BEGIN--填写比率表...FORALLiIN1..10UPDATEempSETinterest=money*ratios(i)WHERE??;
END;上面说了不少关于循环语句的处理,补充了PL/SQL独有的一些循环语句。在迁移时,最常用的是WHILE循环,因为TSQL中通常使用该结构来表示循环。5.GOTO语句TSQL和PL/SQL都保留了GOTO语句。在结构化程序设计中,GOTO被证明是有害的,但是多年来众多语言并未放弃该元素,因为在实际应用中还时不时需要用到它。使用GOTO能实现的逻辑几乎完全可以使用其它控制流语句实现。在TSQL中,GOTO通常用于跳出深层嵌套的控制流语句。而在PL/SQL中,也常用该语句来模拟continue语句。使用GOTO语句必然要定义标签。TSQL定义标签的语法是:label_name:而PL/SQL定义标签的语法是:<>在调用时语法均为:GOTOlable_name,不同的是PL/SQL需要分号结尾。在编译时,PL/SQL的语法检查要强于TSQL,例如,不能在if分支里跳转到在其else分支里定义的标签去,也不能在循环外面跳转到循环里面去。6.游标在TSQL中,游标是一种开销大的东西,在PL/SQL中,情况会显得好一点。通常,在迁移TSQL的游标到PL/SQL的游标时,需要做的工作不会很多。使用游标时通常分为以下几个步骤:·定义游标·打开游标·使用循环取游标数据进行操作·关闭游标(释放游标)下面是一个简单的TSQL的游标例子:declareffare1_curcursorfor--定义游标selectfare_type,farefromffarewherefare_kind=@fare_kindandexchange_type=@exchange_type
andstock_type=@stock_typeand(entrust_way=@entrust_wayorentrust_way='!')orderbyfare_type,entrust_waydescforreadonlyopenffare1_cur--打开游标fetchffare1_curinto@fare_type,@fare--取游标数据(指定接收变量不需要事先申明,只需与SELECT指定的字段对应即可)while@@fetch_status=0--循环游标beginselect@fare_type=isnull(@fare_type,'')if@fare_type!=@pre_fare_typeselect@ffare=@ffare+round(isnull(@fare,0.0),3),@pre_fare_type=@fare_typefetchffare1_curinto@fare_type,@fare--取下一次循环endcloseffare1_cur--关闭游标deallocateffare1_cur--释放游标上面的代码例子中,在注释中明确列出了使用游标个各个步骤。提取数据使用fetch语句,提取成功@@fetch_status全局变量为0,如果不为0,则表示提取失败。例子中使用的是只读游标,指示字是forreadonly。如果没有该指示字,那
么游标是支持修改的(FORUPDATE),可以使用wherecurrentofcursor_name来对游标的当前行进行(用UPDATE、DELETE操作)修改。在PL/SQL中,不需要指明是否是只读还是可修改,在对游标当前行进行修改时,通常使用rowid的方法来进行。根据rowid来访问行是ORACLE中访问行最快的方法。下面首先将上面的游标例子翻译成PL/SQL代码。declareffare1_cusrISREFCURSOR;--定义游标引用类型beginopenffare1_cusrfor--打开游标selectfare_type,farefromffarewhere(client_group=v_client_grouporclient_group='!')and(room_code=v_room_codeorroom_code='!')andexchange_type=v_exchange_typeandstock_type=v_stock_typeand(entrust_way=v_entrust_wayorentrust_way='!')orderbyfare_type,entrust_waydesc;fetchffare1_cusrintov_fare_type,v_fare;--取游标数据
whileffare1_cusr%FOUND--循环游标
loopp_fare_type:=nvl(p_fare_type,'');ifp_fare_type!=p_pre_fare_typethenp_ffare:=p_ffare+round(nvl(p_fare,0.0),3);p_pre_fare_type:=p_fare_type;endif;fetchffare1_cusrintop_fare_type,p_fare;endloop;closeffare1_cusr;--关闭游标end;可以看出,处理游标的基本步骤是一样的,但是细节上有些差别。首先,PL/SQL允许定义引用类型的游标,该类型的游标变量可以打开任意的SQL语句,此时open语句中即包含了需要打开的语句。虽然这种方法效率不是最好的,但是在迁移时绝对是最方便的。其次,游标循环的条件不一样。PL/SQL使用游标的%FOUND或者%NOTFOUND来判断游标是否已经滚到最后了。最后,PL/SQL的close语句关闭游标的同时也释放了游标,不需要TSQL的显式释放语句deallocate。接下来需要对修改游标做一番说明。通常,不建议使用游标来进行修改,这样做的效率不是很好。SQL技巧高的开发人员通常可以避免这一点。当SQL语句过分复杂时,或者无法利用SQL语句做到时,只能利用游标来做修改。下面的例子很简单,只是为了说明问题。首先来看TSQL的修改游标内容的例子。declarefare_curcursorforselectclient_interest,client_integralfromclientfarewhereclient_nobetween100and200openfare_curfetchfare_curinto@client_interest,@client_integralwhile@@fetch_status=0beginupdateclientfaresetclient_interest=client_interest+@client_integral*0.0025wherecurrentoffare_curfetchfare_curinto@client_interest,@client_integralendclosefare_curdeallocatefare_cur下面是PL/SQL中修改游标内容的翻译代码:(ROWID是亮点!)declarecursorfare_curisselectclient_interest,client_integral,rowidfromclientfarewhereclient_nobetween100and200;v_rowidvarchar2(18);beginopenfare_cur;fetchfare_curintov_client_interest,v_client_integral,v_rowid;
whilefare_cur%FOUNDloopupdateclientfaresetclient_
interest=client_interest+v_client_integral*0.0025whererowid=v_rowid;fetchfare_curintov_client_interest,v_client_integral,v_rowid;endloop;closefare_cur;上面的代码中,在定义游标时使用了显式游标,而在select语句中则多了一个所谓的伪列“rowid”,并且使用一个18位字符长的变量来取得这个值。Rowid在常规表中表示了记录的物理位置,在索引表中表示逻辑位置。是访问行的最快途径。具体含义可以参考ORACLE的手册。当然,在PL/SQL中也可以使用wherecurrentof子句来修改游标。虽然这样做的效率没有使用rowid来的高,但是迁移的工作效率要高多了。PL/SQL的游标还有更多的内容,例如可以带参数,可以指明返回值,等等。此处不再赘述。关于游标,在后面的章节还会讲到其他的一些迁移问题,敬请关注。7.开关语句TSQL中具有case这个关键字,与C不同的是,MSSQL在说明这个关键字的时候称之为函数,而不是语句。而在ORACLE9i以前,与TSQL中的case中对应的PL/SQL元素是decode。这同样也是一个函数。关于如何将case翻译成decode的问题,在后面的章节中会详细介绍一些技巧。从ORACLE9i开始,case被引进到PL/SQL中,而且同时具有函数和语句的功能。这样,在翻译长的if…elseif语句时,就多了一种选择。而将TSQL的case迁移到ORACLE9i时,也不必多花费时间。下面的代码表中,左边是TSQL代码,而右边是使用case语句的PL/SQL代码:If@size<100Select@fare=@size*2Elseif@size<200Select@size=@size*3Elseif@size<500Select@size=@size*4ElseSelect@size=@size*5Casewhenv_size<100thenv_fare:=v_size*2;whenv_size<200thenv_fare:=v_size*3;whenv_size<500thenv_fare:=v_size*4;elsev_fare:=v_size*5;endcase;
8.基本SQL语句这里只讲述最基本的区别,其他内容将作为迁移技巧在后面的章节中讲述。首先是insert语句。TSQL用户经常省略into关键字,PL/SQL中请补上。作为一个好的习惯,请在所有insert语句中显式列出字段名称。在使用子查询向表中插入新行时,TSQL可以使用selectinto结构和insertintoselect结构,而PL/SQL则只使用insertintoselect语句。见下面的例子。
TSQL中:selectShop.*,@client_nameintonewshopfromshop或者insertintonewshopselectshop.*,@client_namefromshop上面两句话有什么区别呢?前者如果执行前newshop表不存在,则会自动创建,而后者则不能自动创建。前者似乎适合倒库时使用,但是TSQL的程序员经常只使用前者后不使用后面更符合标准的语句。要知道,让应用程序动态创建对象开销颇大。其次是delete语句。TSQL用户经常省略from关键字,PL/SQL中请
补上。当然,不补也没关系,但是还是请你不要吝啬这个行为。delete语句的语法在TSQL和PL/SQL中略有不同。TSQL中常常在from部分使用多个表进行连接,而PL/SQL中则必须使用子查询来完成。例如下面的TSQL语句:deletestudentsfromstudentss,depts.dwhered.dept_no>1000andd.dept_no=s.dept_no在PL/SQL语句中则可以写成:deletefromstudentsswhereexists(selectdept_nofromdepts.dwhered.dept_no>1000ands.dept_no=d.dept_no);这种语法上的差异导致迁移复杂的sql语句时需要付出艰辛的劳动。Update语句同样存在差异。TSQL可以具有一个from部分用于从多张表中提取数据,而PL/SQL中则不允许,此时可以使用子查询。例如下面的TSQL语句:updatestudentssetdept_name=d.dept_name,dept_sir=d.dept_sirfromstudentss,depts.dwhered.dept_no>1000andd.dept_no=s.dept_no在PL/SQL语句中则可以写成:updatestudentssset(dept_name,dept_sir)=(selectdept_name,dept_sirfromdepts.dwhered.dept_no>1000andd.dept_no=s.dept_no)TSQL用户可能一开始会对不一样的语法感到困惑。但这仅仅是开始而已。在后面的章节中,大量繁杂的例子将一一被说明。9.关键字在MSSQL中的有一些对象名称在ORACLE中是关键字,此时必须采用一种约定的方法进行转化。常见的关键字是date和online。MSSQL中可以用作列名,但是在ORACLE中不行。笔者在实际迁移时将这两个字段变成date_和online_,这样能够保留原来的意义并且修改很小。至于修改字段名带来的接口变更问题,在后面的章节会详细介绍。10.事务在TSQL中,事务通常以begintransaction开始,committransaction为提交,rollback以以transaction为回滚。另外,可以用savetransaction定义回滚点,在rollback事务时加上回滚点即可以回滚部分事务。PL/SQL同样具有类似的元素,不同的是不需要transaction指示字。特别要提出的事,PL/SQL不需要事务开始指示,上一个事务结束或者PL/SQL块开始出自动开始下一个事务。设定事务的隔离级别时,两个都遵循同样的标准。但是PL/SQL要求严格一点:只允许将隔离级别设定为SERIALIZABLE或者READCOMMITTED,不允许设定为TSQL有时可能使用的READUNCOMMITTED,因为ORACLE认为这个行为会破坏数据库数据一致性。因此,如果遇到TSQL中的以下语句:
settransactionisolationlevelreaduncommitted在PL/SQL中处理方式很简单,将其去除即可。当然,最好还是先注释一下,以便优化代码时参考。事实上,ORACLE事物默认隔离级别是readcommitted,因此,迁移时也可以将这样的事务隔离级别设置语句去除或者注释掉。关于事务有不少令人望而生畏的内容,将在后面的章
节中详细展开。11.SQL语句的影响行数在TSQL中,@@rowcount是一个神通广大的魔术变量,它不仅返回最近执行语句影响到的记录数量,而且允许通过设置其值以影响下一次执行时的影响行数。不过要注意,取的时候用@@rowcount,而设置的时候用setrowcountn,其中n为0时表示没有限制,为自然数则表示限制影响前n行。在TSQL中,如果使用了setrowcount语句,不要忘记在使用结束以后将值恢复为0,否则会影响后续语句的正确执行。此外,TSQL允许使用topn命令来限制select语句返回前n行记录。但是topn语句与setrowcount有所区别的是,后者会影响后续语句,而topn语句只对当前语句有效。例如下面的两组语句:setrowcount10select*fromsysobjectsselect*fromsysobjects上面两个语句返回都只有10行。但是下面的两个语句则不同:selecttop10*fromsysobjectsselect*fromsysobjects第一个语句返回前10行,而第二个语句则返回所有表的记录。另外,topn不允许使用变量来限制行数,在灵活性上不如setrowcount。在PL/SQL中,则使用不同的方法来限制返回前n行。取上一句语句影响的行数使用sql%rowcount属性。而返回行数则使用rownum来限制。Rownum的含义是结果集的行号,初看非常简单,但对于初次接触的人来说,具有某种不可思议的神秘性。例如下面的两个语句,原意是分别返回第一行记录和第二行记录:select*fromuser_objectswhererownum=1;select*fromuser_objectswhererownum=2;但是结果却并不如愿,第一个语句的确返回了第一行记录,但是,第二个语句什么也没有返回。这里要对rownum的性质作一番阐述。性质1:rownum是物理顺序的序号,其物理的序号是在orderby之前就已經決定好了的。性质2:rownum永远从结果集的第一行开始比较起,一旦比较不成功,就返回,后续行不再比较。因此,根据性质2,上面的第二个语句在第一次比较就失败了,后面的记录不再比较,所以没有记录返回。PL/SQL通常使用rownum<=n来返回前n行记录。rownum的性质1与TSQL的topn是不同的,这造成了迁移时的麻烦。要小心为之。下面来分析这种不同。Topn取orderby之后的顺序,而rownum取orderby之前的顺序,这样,如果需要取出按照某字段排序的前n行记录的语句有较大差别。例如,下面的语句表示根据对象名称倒排序后取出前10条记录,在TSQL中可以表示为:Selecttop10*fromsysobjectsorderbynamedesc
在PL/SQL中不能写成:Select*fromall_objectswhererownum<=10orderbyobject_namedesc;这样取出的结果只不过是表中前10行记录的倒序结果,而不是目的!为了取得与top
n同样的效果,可以使用以下的方法:(掌握此技巧)select*from(select*fromall_objectsorderbyobject_namedesc)whererownum<=10;上述语句使用子查询求出倒序后的结果,然后在中间表的基础上使用性质2求前10行记录。这里将涉及一些较复杂的技巧,不再分到后面的章节中去讲,这里就一并与读者分享。在应用中经常遇见类似这样的问题:需要取某个流水号开始的25条记录。这样的问题通常用于翻页。在TSQL中,可以使用下面的语句来实现这一目标:setrowcount@request_num--设置取的行数select*fromjourtablewhereserial_no>=@serial_noorderbyserial_no而在PL/SQL中,则可以写成:select*from(select*fromjourtablewhereserial_no>=v_serial_noorderbyserial_no)whererownum<=v_request_num;更复杂的例子如取101行开始的10行记录按照名字排序的结果。在TSQL中可以写成:select*from(selecttop10*from(selecttop110*fromsometable)--from(selecttop110*fromsometableorderbyname)orderbynamedesc)orderbyname在PL/SQL中该语句可以写成:selectras“行号”*from(selectrownumr,t.*from(select*fromuser_tablesorderbytable_name)twhererownum<=110)whererbetween101and110;上述语句返回结果集中包含了行号,别名为r。13.关于存储过程的基本区别存储过程是迁移的主要内容,其中涉及面非常广,这里先仅对基本的一些区别作一番列举。首先,创建存储的语法不同。PL/SQL允许用orreplace选项来覆盖已经存在的存储过程,而TSQL中,往往先判断一下将要创建的存储过程是否存在,如果存在的话先进行删除。第二,procedure关键字的含义不同。在TSQL中,该关键字既表示过程,也表示函数。但是PL/SQL中明确区分,procedure表示过程,不能有返回值;而function则表示函数,可以有返回值。(TSQL中procedure可以返回参数如:执行状态)第三,参数的区别。参数两者都允许有输入、输出、输入输出三种。但是具有一些重要的区别。其一,TSQL的参数可以具有精度,但是PL/SQL的参数除非使用%type方法获取类型,否则用原生类型的话不能拥有精度(关于该问题,参考“数据类型”一章)。其二,TSQL的参数虽然可以区分输入输出,但是在随后的代码中,可以对输入参数进行赋值,或者从输出参数取值,这些都不会导致编译错误。这种性质使得MSSQL程序员经常在代码中将参数作
为变量来使用。PL/SQL则比较严格,不允许对输入参数进行赋值,也不允许对输入参数取值。第四,返回结果集的方式不同。TSQL中,使用select语句返回结果集,并且使用条件语句的话可以返回不同的结果集。在PL/SQL中,select语句
一定要含有into部分才能单独存在,因此不能使用该方法返回结果集。那么,如何在PL/SQL的存储过程中返回结果集呢?笔者采用以下方法。①创建一个包,里面定义一个游标引用类型,例如:createorreplacepackagemytypeTYPEt_cursorISREFCURSOR;endmytype;/②在定义需要返回结果集的存储过程或者函数时,在参数末尾添加一个输出参数,其类型就是上面定义的游标引用类型。Createorreplaceproceduresp_need_result_set(action_invarchar2,p_cursoroutmytype.t_cursor)asbegin--??openp_cursorforselect*frommytable;return;endsp_need_result_set;/③在存储过程返回之前打开该游标即可。在前台取结果集时可以像TSQL那样取,而不必像其他参数那样取。使用该方法有几点好处:第一,简单易用,处理统一;第二,可以打开不同的结果集。在PL/SQL使用输出游标参数有几点需要注意:第一,在ORACLE9i以前不允许在动态调用子存储过程中使用输出游标参数,这一点在后面的章节中才能体会明白;第二,如果定义了游标输出参数,则一定要在存储过程返回之前打开,否则该存储过程执行会出错,一个常见的防止该类出错的方法是在存储过程开始处使用以下语句:Openp_cursorforselect*fromdualwhere1=2;第五,存储过程的编译方式不同。TSQL允许滞后编译,即编译时如果某个表或子过程不存在,编译有可能能通过。但是PL/SQL则不行。有时需要两次编译才能完全生成。第六,加密方式不同。TSQL的存储过程头部加上withencryption选项即可加密,是单独对一个存储过程进行的。PL/SQL则使用单独的一个执行程序wrap对整个sql文件进而行加密,加密后的文件长度通常是原来的3到4倍,可以与原文件一样在sqlplus等工具中执行。关于wrap的具体用法和注意点,将在后面章节中讲述。14.字符串相加运算在大多数语言里,字符串运算被特殊考虑。因为字符串运算实在太频繁了,如果不作特
殊处理,会影响语言的方便性。在TSQL里,字符串相加使用+号,而在PL/SQL里则使用||符号进行连接。在TSQL里,将整数或者其他类型的数据与字符串相加需要进行类型转换,例如下面的语句:select@sSQL=@sSQL+convert(varchar,@date)而在PL/SQL中,不需要显式转换,||符号会自动转换一切!v_sSQL:=v_sSQL||v_date;不但是整型,日期型也可以自动转换,但是请注意,缺省转换出来的日期格式可能不是你所希望的,所以最好还是用to_char进行格式转换。字符串操作函数中的大部分均可简单迁移,对于某些函数可能需要自定义函数,这在后面章节中讲述。15.Like操作符的不同TSQL中,like
关键字除了支持所有的RDBMS都支持的(%)和(_)通配符以外,还支持[]和[^]通配符,其中,[]用来查询一个范围内的所有单个字符,而[^]用来查询不在一个特定范围内的所有单个字符。例如,like‘[a-c]’或者like‘[abc]’表示abc三个字符都匹配,而like[^a-c]或者like‘[^abc]’表示除了abc三个字符外都支持。like的这种行为在PL/SQL中不支持,那么,如何迁移这种行为呢?在PL/SQL中通常我们采用or分割条件。例如下面的TSQL语句:Select*fromsysobjectswherenamelike‘[abc]%’语句的意思是选出名称以abc三个字母开头的所有记录,在PL/SQL中可以这么写:Select*whereororfromuser_objectsobject_namelike‘a%’object_namelike‘b%’object_namelike‘a%’;
如果是[^]符号,只要稍作变化即可。例如,TSQL语句:Select*fromsysobjectswherenamelike‘[^abc]%’可以变化为:Select*fromuser_objectswherenot(object_namelike‘a%’orobject_namelike‘b%’orobject_namelike‘a%’);当匹配的字符数比较少时,这的确是一个简单的方法,然而当匹配的字符数量很大时,显示不能用这种方法。例如下面的这个语句:Select*fromsysobjectswherenamelike‘[a-z]%’很显然,傻乎乎列出26个条件是不明智的。此时,不妨想想其他的办法。通常,我们使用字符串比较来实现上述语句。Select*fromuser_objectswhereobject_name>=‘a’andobject_name 求的,正好是’a’。16.触发器MSSQL的触发器是语句级的,即一个语句对一个触发器只能触发一次,而不管该语句影响了多少行记录。但是ORACLE不但允许语句级触发器,还允许行级触发器。ORACLE的触发器还分语句执行前和执行后。MSSQL则不分前后定义。在将MSSQL的触发器迁移到ORACLE中时,通常应该迁移为行级的执行后触发器。原因在于ORACLE的触发器中无法访问到类似MSSQL中提供的inserted、deleted虚表的存在,而只有虚记录:new和:old的存在。例如,下面的MSSQL触发器,定义为如果删除了fund表中的记录,那么也要删除fund1表中相同主键值的所有记录,并且如果删除fund1发生异常的话,回滚删除fund的动作。createtriggerfund_deleteonfundfordeleteasif@@rowcount=0returndeletefund1fromfund1f,deleteddwheref.fund_account=d.fund_accountandf.money_type=d.money_typeif@@error<>
0beginrollbacktransactionreturnendgo转换成ORACLE的触发器可以为:createorreplacetriggerfund_deleteafterdeleteonfundforeachrowbegindeletefromfund1wherefund1.fund_account=:old.fund_accountandfund1.money_type=:old.money_type;exceptionwhenothersthenrollbackwork;endfund_delete;/这里只用到了:old,如果在update动作上定义触发器,那么就可能要使用到:new虚记录了。对于insteadof类型的触发器,在MSSQL的联机手册中写道:执行触发器而不是执行
触发它的SQL语句,从而替代触发语句的操作。但是对于ORACLE来说,不管是8i还是9i,语法手册中都明确写着:insteadof类型的触发器只能对视图有效,不能定义在表上。由此看来,迁移insteadof触发器不太好办。如果读者愿意将对某个表的操作都改写成对该表上某个视图的操作就比较好办了,但是这样为了一个触发器而大量修改代码的做法有点得不偿失。最好还是建议分析一下业务,用其他的方法来避免这个。例如下面的这个MSSQL例子中,在表fund上定义一个insteadof触发器,如果修改了该表的金额字段cash_balance,则将修改记录到一个流水表中,但是不对fund表进行任何修改。createtriggertrg_fund_jouronfundinsteadofupdateasifUPDATE(cash_balance)begininsertintofund_jourselect*frominsertedendgo