头儿让我将现在的项目数据库从oracle10g迁移到sql server2008,为了以后的需要吧。数据库大概是370张表,70个视图,30多个函数,十几个存储过程,我搞了将近两周,效率比较低,下面就把这两周所遇到的问题总结一下。
因为以前没做过,所以就上网找找有什么工具之类的没,结果只找到两个方法,一个是微软的工具SSMA,一个是用sql server管理工具自带的导入功能。
关于SSMA(后边的确是用上了这个工具,使用起来挺简单,具体方法自己查),上来就遇到一个头疼的问题,就是连接oracle,如图
其中server name我弄了很长时间,试了很多也没试出来是什么东东,以为跟登录oracle时的Datebase有关,如图
后来在网上查到了,在oracle中查询select * from v$instance,结果中的HOST_NAME就是这个server name,sid(数据库的唯一标示符)在安装oracle时默认是orcl,如果你在安装的时候修改了,在oracle的安装目录下,如D:\oracle\product\10.2.0\db_1\dbs,看文件名如SPFILEORCL.ORA,那么就是orcl,也就是说文件名字是 "SPFILE******.ORA ",其中的 "**** "就是SID,剩下的几项就好填了。然后就是SSMA连接sql server如图
这里边的server name就是在登录sql server管理工具时候的服务器名称。这是SSMA的第一个问题。第二个问题就是转换出了很多问题,oracle表里边number类型的数据,它都给转为了float(53),三百多张表,太扯淡了,因为我们数据库中number大部分都是int型的(也不能怪SSMA,float(53)是最保险的类型),还是老老实实自己转吧,其他的视图,存储过程,函数都有错,看来微软的这个东西还不是很智能。
还是不甘心,再找其他方法,发现有人用sql server管理工具自带的导入功能,右键数据库->任务->导入数据,这个东西也不好使,具体不多说了,最后终于做了一个艰难的决定,自己动手,丰衣足食。
一、转表
先用pl/sql Developer查看该表的建表sql,然后把建表语句复制出来,我把所有表的sql都搞到一个文件中去,方便一次执行就把表都生成了。如果这个表有外键,索引,先不要管,把创建外键索引的语句复制到另一个文件,等你把表都创建好了,再把这些创建外键索引的语句执行一下就行了。转表的问题不大,就是一些数据类型的转换(需要注意number类型),如下。
Oracle 数据类型 SQL Server 数据类型 备用
BFILE VARBINARY(MAX) 是
BLOB VARBINARY(MAX) 是
CHAR([1-2000]) CHAR([1-2000]) 是
CLOB VARCHAR(MAX) 是
DATE DATETIME 是
FLOAT FLOAT 否
FLOAT([1-53]) FLOAT([1-53]) 否
FLOAT([54-126]) FLOAT 否
INT NUMERIC(38) 是
INTERVAL DATETIME 是
LONG VARCHAR(MAX) 是
LONG RAW IMAGE 是
NCHAR([1-1000]) NCHAR([1-1000]) 否
NCLOB NVARCHAR(MAX) 是
NUMBER FLOAT 是
NUMBER([1-38]) NUMERIC([1-38]) 否
NUMBER([0-38],[1-38]) NUMERIC([0-38],[1-38]) 是
NVARCHAR2([1-2000]) NVARCHAR([1-2000]) 否
RAW([1-2000]) VARBINARY([1-2000]) 否
REAL FLOAT 否
ROWID CHAR(18) 否
TIMESTAMP DATETIME 是
UROWID CHAR(18) 否
VARCHAR2([1-4000]) VARCHAR([1-4000]) 是
二、我第二个转的是视图,后来证明,视图是第二个好转的东东,下面是视图中遇到的问题和需要注意的地方。
1.oracle中如果有select ... from dual的语句,sql server中把from dual去掉即可。
2.关于二者的函数,我基本是从http://www.cnblogs.com/wuchaocug/archive/2011/05/27/2059490.html来对比转换的。
3.sql server没有wm_concat函数(连接一个字段中的字符串),需要自己写一个函数来替代了,给一个参考:
SELECT USUID ,
RESUME = STUFF(( SELECT ',' + (StartDate+'-'+EndDate+' ' +CropName+''+Duty)
FROM tbWorkResume t
WHERE t.USUID = tbWorkResume.USUID
FOR
XML PATH('')
), 1, 1, '')
FROM tbWorkResume
GROUP BY USUID
或
select userid,STUFF((select ','+JOBNAME from tbjoborder j,TBUSERJOB u
where j.joid= u.JOID and u.USERID=uj.USERID
for xml path('')
),1,1,'') jobname from tbuserjob uj
group by userid
5.oracle的start with... connect by树查询语法sql server是没有的,这里用with as替代(http://blog.csdn.net/key_the_one/article/details/7222128写的更清楚),如下:
with node AS ( select organid FROM tborgan
WHERE organid IN (SELECT FOREIGNKEYID as organid FROM tbTPUser a WHERE SELECTMODE=1)
UNION ALL SELECT bb.organid
FROM tborgan bb inner join node no ON no.organid = bb.organfatherid )
--此后跟的插入或查询语句
select * from node
这里需要注意,如果查询出来的结果是要作为其他查询的子条件的话,上边的sql语句是不能用小括号包起来的,解决办法可以讲其再写入一个视图(假设起名conn),然后用SELECT ORGANID from conn调用即可。
6.其中可能会调用一些自定义的函数,我先不管他,等把函数转完后,再回头生成这些视图。
7.SSMA转的视图还凑合,你想省事可以从它那复制,不过必须注意,你复制之前必须看一下,有一些东西也是必须再手动转的,可能你复制过来执行的话,不会报错,但有些东西一看明显是不对的,比如SSMA把
Trunc(months_between(SysDate,f.born)/12) UserAge
转为
sysdb.ssma_oracle.trunc(sysdb.ssma_oracle.months_between(sysdatetime(), f.BORN) / 12, DEFAULT) AS UserAge
执行时候是不报错的。
三、自定义函数的转换,这个不太好转(当然如果函数简单的话,还是挺好转的),但感觉可说的,可注意的地方也不太多。
1.自定义函数里边不支持begin try和begin catch,可以用if @@error<>0(如果上边的语句执行不正确)替代。
2.if和else、else if里边的内容最好用begin end包裹。
3.oracle不支持返回table类型,一般用管道类型pipelined返回,转换的时候sql server可直接返回table类型,而在定义的时候,虽然我看网上有人直接这样写:
create function test()
returns table
as
begin
return select * from userTable
end
go
但是执行的时候会报'BEGIN' 附近有语法错误。这样定义一个table就对了:
create function test()
returns @Table table(userid int,username varchar(10))
as
begin
insert into @Table select userid,username from userTable
return
end
go
4.sql server返回游标的问题,这个还没解决,弄好了再更新在这里吧。
create function dbo.myCharIndex
(
@partString varchar(max),
@fullString varchar(max),
@startIndex int,
@time int
)
returns int
as
begin
declare
@tempIndex int,
@tempString varchar(max)
set @tempIndex = 0
if(@time < 1)
return 0
if(@time = 1)
return charindex(@partString,@fullString,@startIndex)
if(@time > 1)
begin
set @tempString = subString(@fullString,@startIndex,len(@fullString))
if(len(@tempString)=0)
return 0
while @time >= 1
begin
if(@tempIndex+1 > len(@fullString))
return 0
else
begin
set @tempIndex = charindex(@partString,@tempString,@tempIndex+1)
if(@tempIndex = 0)
return 0
else
set @time = @time - 1
end
end
return charindex(@partString,@fullString,@startIndex+@tempIndex-1)
end
return 0
end
go
四、存储过程的转换
1.oracle支持视图式的更新(使用/*+*/)
update (
select /*+ BYPASS_UJVC */ a.tpid,nvl(a.cid,0) cid,a.cexamgrade,c.exscore,a.egid tpegid,c.egid
from tbtpcourseresult a
inner join tbtestpaper b on a.tpid=b.relakey and a.cid=nvl(b.relakey2,0) and b.papermode=16
inner join tbexamineegrade c on b.tpid=c.tpid and a.userid=c.userid )
set cexamgrade=exscore,tpegid=egid;
sql server只能这样(不知道这样转正确不?哪位大侠给个答案)
update tbtpcourseresult set cexamgrade=c.exscore,egid = c.egid from
tbtpcourseresult a
inner join tbtestpaper b on a.tpid=b.relakey and a.cid=isnull(b.relakey2,0) and b.papermode=16
inner join tbexamineegrade c on b.tpid=c.tpid and a.userid=c.userid
select * from (select Row_Number() over
(Order by UserId) as RowId ,* from UserInfo) U
where U.RowId between 10 and 20
3.sql server用下面方法控制事务
BEGIN
IF @@TRANCOUNT > 0
COMMIT WORK
IF @@TRANCOUNT > 0
ROLLBACK WORK
END
oracle用下面方法控制事务
COMMIT;
EXCEPTION
WHEN OTHERS THEN
ROLLBACK;