SQL*Loader
SQL*Loader(SQLLDR)是Oracle的高速批量数据加载工具。这是一个非常有用的工具,可用于多种平面文件格式向Oralce数据库中加载数据。
SQLLDR可以在极短的时间内加载数量庞大的数据。它有两种操作模式:
传统路径:(conventional path):SQLLDR会利用SQL插入为我们加载数据。
直接路径(direct path):采用这种模式,SQLLDR不使用SQL;而是直接格式化数据库块。
直接路径(direct path):采用这种模式,SQLLDR不使用SQL;而是直接格式化数据库块。
利用直接路径加载,你能从一个平面文件读数据,并将其直接写至格式化的数据库块,而绕过整个SQL引擎和undo生成,同时还可能避开redo生成。要在一个没有任何数据的数据库中充分加载数据,最快的方法就是采用并行直接路径加载。
命令行参数
userid -- ORACLE username/password
control -- control file name (该参数指定了一个决定Sql*Loader行为的配置文件,它决定了需要从哪个数据文件读取数据,载入到哪张表里,分别有哪些字段等等。)
log -- log file name
bad -- bad file name
data -- data file name (该参数指定了数据来源,也就是从哪个数据文件中读取记录。指定的数据文件每行的数据往往有特定的格式,有特定的分隔符区分每个字段的值。不常用,我们用control file)
discard -- discard file name (该参数指定了一个文件用于记录那些未被正常导入到数据库中的记录。)
discardmax -- number of discards to allow (Default all)
skip -- number of logical records to skip (Default 0)
load -- number of logical records to load (Default all)
errors -- number of errors to allow (Default 50) (允许出错的记录数,一般情况系此参数需要调整)
rows -- number of rows in conventional path bind array or between direct path data saves
control -- control file name (该参数指定了一个决定Sql*Loader行为的配置文件,它决定了需要从哪个数据文件读取数据,载入到哪张表里,分别有哪些字段等等。)
log -- log file name
bad -- bad file name
data -- data file name (该参数指定了数据来源,也就是从哪个数据文件中读取记录。指定的数据文件每行的数据往往有特定的格式,有特定的分隔符区分每个字段的值。不常用,我们用control file)
discard -- discard file name (该参数指定了一个文件用于记录那些未被正常导入到数据库中的记录。)
discardmax -- number of discards to allow (Default all)
skip -- number of logical records to skip (Default 0)
load -- number of logical records to load (Default all)
errors -- number of errors to allow (Default 50) (允许出错的记录数,一般情况系此参数需要调整)
rows -- number of rows in conventional path bind array or between direct path data saves
(Default: Conventional path 64, Direct path all) (每次Commit的记录数。在Conventional Path模式时,它限定了bind array最大记录数。在Direct Path模式时,它限定了保存之前从数据文件中读取的最大记录数。它的作用和BINDSIZE类似,只是一个限制了记录数,一个限制了记录大小。)
bindsize -- size of conventional path bind array in bytes (Default 256000)
(Sql*Loader分批从数据文件中读取记录并提交到数据库中,每批的大小是有限制。该参数决定了Sql*Loader从数据文件读取记录大小的上限,除了每次读取的记录数必须小于ROWS指定的数目外,大小上不得超过BINDSIZE所指定的数值。该参数计量单位是Byte。默认256K)
silent -- suppress messages during run (header,feedback,errors,discards,partitions)
(静默方式,不输出信息)
(Sql*Loader分批从数据文件中读取记录并提交到数据库中,每批的大小是有限制。该参数决定了Sql*Loader从数据文件读取记录大小的上限,除了每次读取的记录数必须小于ROWS指定的数目外,大小上不得超过BINDSIZE所指定的数值。该参数计量单位是Byte。默认256K)
silent -- suppress messages during run (header,feedback,errors,discards,partitions)
(静默方式,不输出信息)
direct -- use direct path (Default FALSE)
(direct=true 使用直通路径方式导入)
parfile -- parameter file: name of file that contains parameter specifications
parallel -- do parallel load (Default FALSE)
(并行导入)
file -- file to allocate extents from
skip_unusable_indexes -- disallow/allow unusable indexes or index partitions (Default FALSE)
skip_index_maintenance -- do not maintain indexes, mark affected indexes as unusable (Default FALSE)
commit_discontinued -- commit loaded rows when load is discontinued (Default FALSE)
readsize -- size of read buffer (Default 1048576)
(读缓存大小。该参数仅针对从数据文件载入数据的方式时有效,默认值为64k,最大值因系统平台各有不同。在Conventional Path模式时,bind array 受限于读缓存,也就是说,在系统内存和bind array足够大的前提下,如果读缓存越大,则可以有更多的记录在commit前被读取,这也就意味着载入性能越好。当READSIZE小于BINDSIZE时,则READSIZE会被自动加大。)
(读缓存大小。该参数仅针对从数据文件载入数据的方式时有效,默认值为64k,最大值因系统平台各有不同。在Conventional Path模式时,bind array 受限于读缓存,也就是说,在系统内存和bind array足够大的前提下,如果读缓存越大,则可以有更多的记录在commit前被读取,这也就意味着载入性能越好。当READSIZE小于BINDSIZE时,则READSIZE会被自动加大。)
external_table -- use external table for load; NOT_USED, GENERATE_ONLY, EXECUTE (Default NOT_USED)
columnarrayrows -- number of rows for direct path column array (Default 5000)
streamsize -- size of direct path stream buffer in bytes (Default 256000)
multithreading -- use multithreading in direct path
resumable -- enable or disable resumable for current session (Default FALSE)
resumable_name -- text string to help identify resumable statement
resumable_timeout -- wait time (in seconds) for RESUMABLE (Default 7200)
date_cache -- size (in entries) of date conversion cache (Default 1000)
...
以上参数经常用的为,userid,control,errors,rows,bindsize/readsize,direct,parallel。
提醒:如果数据量较大需要显式设定rows(比如:10000),以及bindsize/readsize(比如:5120000),direct=true,parallel=true,以及设置errorx=n(n为允许出错的条数)。
control文件
// 建表
create table dept
(
deptno number(2) constraint dept_pk primary key,
dname varchar2(14),
loc varchar2(13)
);
// control说明
LOAD DATA
INFILE 'data.dat' // 要导入的文件
// INFILE 'data.date' // 导入多个文件
// INFILE * // 表示要导入的内容就在control文件里,下面的BEGINDATA后面就是导入的内容
BADFILE 'data.bad' // 可选,指定坏文件地址,缺省在当前目录下生成与原文件名一致的.bad文件
INFILE 'data.dat' // 要导入的文件
// INFILE 'data.date' // 导入多个文件
// INFILE * // 表示要导入的内容就在control文件里,下面的BEGINDATA后面就是导入的内容
BADFILE 'data.bad' // 可选,指定坏文件地址,缺省在当前目录下生成与原文件名一致的.bad文件
INTO TABLE dept // 指定装入的表
// 以下是4种装入表的方式
APPEND // 原先的表有数据 就加在后面
// INSERT // 装载空表 如果原先的表有数据 sqlloader会停止(默认值)
// REPLACE // 原先的表有数据 原先的数据会全部删除(原表数据量大则较慢,慎重使用)
// TRUNCATE // 指定的内容和replace的相同,会用truncate语句删除现存数据(慎重使用)
APPEND // 原先的表有数据 就加在后面
// INSERT // 装载空表 如果原先的表有数据 sqlloader会停止(默认值)
// REPLACE // 原先的表有数据 原先的数据会全部删除(原表数据量大则较慢,慎重使用)
// TRUNCATE // 指定的内容和replace的相同,会用truncate语句删除现存数据(慎重使用)
// 指定分隔符
FIELDS TERMINATED BY ',' OPTIONALLY ENCLOSED BY '"' // 它指定用逗号分隔数据字段,每个字段可以用双引号括起。
// FIELDS TERMINATED BY X’09’ // 使用十六进制格式的制表符(tab符),采用ASCII时,制表符为9。
// FIELDS TERMINATED BY ' ' // 使用空格
// FIELDS TERMINATED BY WRITESPACE // 以空白分割,查找空白符(制表符、空格和换行符)的第一次出现,
// 然后继续查找,直至找到下一个非空白符。注意和' '的区别
FIELDS TERMINATED BY ',' OPTIONALLY ENCLOSED BY '"' // 它指定用逗号分隔数据字段,每个字段可以用双引号括起。
// FIELDS TERMINATED BY X’09’ // 使用十六进制格式的制表符(tab符),采用ASCII时,制表符为9。
// FIELDS TERMINATED BY ' ' // 使用空格
// FIELDS TERMINATED BY WRITESPACE // 以空白分割,查找空白符(制表符、空格和换行符)的第一次出现,
// 然后继续查找,直至找到下一个非空白符。注意和' '的区别
TRAILING NULLCOLS // 表的字段没有对应的值时允许为空
//设置表的字段
(
DEPTNO,
DNAME,
LOC
//LOC FILLER // FILLER,表示此列的数值不会被装载,即忽略此列的存在(跳过此列)
)
(
DEPTNO,
DNAME,
LOC
//LOC FILLER // FILLER,表示此列的数值不会被装载,即忽略此列的存在(跳过此列)
)
// BEGINDATA // 对应开始的 INFILE * 要导入的内容就在control文件里
// 0,Sales,Virginia,USA
// 20,Accounting,"Va,USA"
// 30,Consulting,Virginia
// 40,Finance,Virginia
// 0,Sales,Virginia,USA
// 20,Accounting,"Va,USA"
// 30,Consulting,Virginia
// 40,Finance,Virginia
// 加载固定位置格式数据
这种定宽的固定位置数据是最适合SQLLDR加载的数据格式。要加载这种数据,使用SQLLDR是最快的处理方法,因为解析输入数据流相当容易。SQLLDR会在数据记录中存储固定字节的偏移量和长度,因此抽取某个给定字段相当简单。如果要加载大量数据,将其转换为一种固定位置格式通常是最好的办法。当然,定宽文件也有一个缺点,它比简单的定界文件格式可能要大得多。
这种定宽的固定位置数据是最适合SQLLDR加载的数据格式。要加载这种数据,使用SQLLDR是最快的处理方法,因为解析输入数据流相当容易。SQLLDR会在数据记录中存储固定字节的偏移量和长度,因此抽取某个给定字段相当简单。如果要加载大量数据,将其转换为一种固定位置格式通常是最好的办法。当然,定宽文件也有一个缺点,它比简单的定界文件格式可能要大得多。
1、
要加载定宽的固定位置数据,将会在控制文件中使用POSITION关键字,例如:
LOAD DATA
INFILE *
INTO TABLE DEPT
REPLACE
INFILE *
INTO TABLE DEPT
REPLACE
(
DEPTNO position(1:2),
DNAME position(3:16),
LOC position(17:29),
// ENTIRE_LINE position(1:29) // POSITION子句可以使用重叠的位置,可以在记录中来回反复。
)
DEPTNO position(1:2),
DNAME position(3:16),
LOC position(17:29),
// ENTIRE_LINE position(1:29) // POSITION子句可以使用重叠的位置,可以在记录中来回反复。
)
BEGINDATA
0,Sales,Virginia,USA
2、
使用POSITION时,可以使用相对偏移量,也可以使用绝对偏移量。
在前面的例子中使用了绝对偏移量,我们明确地指示了字段从哪里开始,到哪里结束。也可以把前面的控制文件写作:
LOAD DATA
INFILE *
INTO TABLE DEPT
REPLACE
INFILE *
INTO TABLE DEPT
REPLACE
(
DEPTNO position(1:2),
DNAME position(*:16), // DNAME position(3:16),
//DNAME position(*+2:16), // DNAME position(5:16),
LOC position(*:29),
//ENTIRE_LINE position(1:29)
)
DEPTNO position(1:2),
DNAME position(*:16), // DNAME position(3:16),
//DNAME position(*+2:16), // DNAME position(5:16),
LOC position(*:29),
//ENTIRE_LINE position(1:29)
)
BEGINDATA
0,Sales,Virginia,USA
"*"号指示控制文件得出上一个字段在哪里结束。因此,在这种情况下,(*:16)与(3:16)是一样的。注意,控制文件中可以混合使用相对位置和绝对位置。另外。使用*表示法时,可以把它与偏移量相加。例如,如果DNAME从DEPTNO结束之后的2个字节处开始,可以使用(*+2:16)。在这个例子中,其作用就相当于使用(5:16)。
3、POSITION子句中的结束位置必须是数据结束的绝对列位置。
有时,可能指定每个字段的长度更为容易,特别是如果这些字段是连续的(就像前面的例子一样)。采用这种方式,只需告诉SQLLDR:记录从第1个字节开始,然后指定每个字段的长度就行了。这样我们就可以免于计算记录中的开始和结束偏移量,这个计算有时可能很困难。为此,可以不指定结束位置,而是指定定长记录中各个字段的长度,如下:
LOAD DATA
INFILE *
INTO TABLE DEPT
REPLACE
INFILE *
INTO TABLE DEPT
REPLACE
(
DEPTNO position(1) char(2),
DNAME position(*) char(14),
LOC position(*) char(13),
ENTIRE_LINE position(1) char(29)
)
DEPTNO position(1) char(2),
DNAME position(*) char(14),
LOC position(*) char(13),
ENTIRE_LINE position(1) char(29)
)
BEGINDATA
0,Sales,Virginia,USA
在此只需告诉SQLLDR第一个字段从哪里开始及其长度。后面的每个字段都从上一个字段结束处开始,并具有指定的长度。直至最后一个字段才需要再次指定位置,因为这个字段又要从记录起始处开始。
// 加载日期
使用SQLLDR加载日期相当简单,但是看起来这个方面经常导致混淆。你只需在控制文件中使用DATE数据类型,并指定要使用的日期掩码。这个日期掩码与数据库中TO_CHAR和TO_DATE中使用的日期掩码是一样的。SQLLDR会向数据应用这个日期掩码,并为你完成加载。
1、增加字段
alter table dept add last_updated date;
2、配置修改
LOAD DATA
INFILE *
INTO TABLE DEPT
REPLACE
FIELDS TERMINATED BY ','
INFILE *
INTO TABLE DEPT
REPLACE
FIELDS TERMINATED BY ','
(
DEPTNO,
DNAME,
LOC,
LAST_UPDATED date 'dd/mm/yyyy'
)
BEGINDATA
10,Sales,Virginia,1/5/2000
20,Accounting,Virginia,21/6/1999
DEPTNO,
DNAME,
LOC,
LAST_UPDATED date 'dd/mm/yyyy'
)
BEGINDATA
10,Sales,Virginia,1/5/2000
20,Accounting,Virginia,21/6/1999
// 使用函数加载数据
1、单字段处理
LOAD DATA
INFILE *
INTO TABLE DEPT
REPLACE
FIELDS TERMINATED BY ','
INFILE *
INTO TABLE DEPT
REPLACE
FIELDS TERMINATED BY ','
(
DEPTNO,
DNAME "upper(:dname)", //将字段数据转换为大写
LOC "upper(:loc)",
LAST_UPDATED date 'dd/mm/yyyy'
)
BEGINDATA
10,Sales,Virginia,1/5/2000
20,Accounting,Virginia,21/6/1999
DEPTNO,
DNAME "upper(:dname)", //将字段数据转换为大写
LOC "upper(:loc)",
LAST_UPDATED date 'dd/mm/yyyy'
)
BEGINDATA
10,Sales,Virginia,1/5/2000
20,Accounting,Virginia,21/6/1999
2、多字段处理
LOAD DATA
INFILE *
INTO TABLE DEPT
REPLACE
FIELDS TERMINATED BY ','
INFILE *
INTO TABLE DEPT
REPLACE
FIELDS TERMINATED BY ','
TRAILING NULLCOLS // 如果输入记录中不存在某一列的数据,SQLLDR就会为该列绑定一个NULL值
(
DEPTNO,
DNAME "upper(:dname)",
LOC "upper(:loc)",
LAST_UPDATED date 'dd/mm/yyyy',
ENTIRE_LINE ":deptno||:dname||:loc||:last_updated"
)
BEGINDATA
10,Sales,Virginia,1/5/2000
20,Accounting,Virginia,21/6/1999
(
DEPTNO,
DNAME "upper(:dname)",
LOC "upper(:loc)",
LAST_UPDATED date 'dd/mm/yyyy',
ENTIRE_LINE ":deptno||:dname||:loc||:last_updated"
)
BEGINDATA
10,Sales,Virginia,1/5/2000
20,Accounting,Virginia,21/6/1999
3、多种日期格式字段
LOAD DATA
INFILE *
INTO TABLE DEPT
REPLACE
FIELDS TERMINATED BY ','
INFILE *
INTO TABLE DEPT
REPLACE
FIELDS TERMINATED BY ','
TRAILING NULLCOLS
(
DEPTNO,
DNAME "upper(:dname)",
LOC "upper(:loc)",
LAST_UPDATED
"case // 进行条件判断
when length(:last_updated) > 9
then to_date(:last_updated,'hh24:mi:ss dd/mm/yyyy')
when instr(:last_updated,':') > 0
then to_date(:last_updated,'hh24:mi:ss')
else to_date(:last_updated,'dd/mm/yyyy')
end"
)
BEGINDATA
10,Sales,Virginia,12:03:03 17/10/2005
20,Accounting,Virginia,02:23:54
30,Consulting,Virginia,01:24:00 21/10/2005
40,Finance,Virginia,17/8/2005
(
DEPTNO,
DNAME "upper(:dname)",
LOC "upper(:loc)",
LAST_UPDATED
"case // 进行条件判断
when length(:last_updated) > 9
then to_date(:last_updated,'hh24:mi:ss dd/mm/yyyy')
when instr(:last_updated,':') > 0
then to_date(:last_updated,'hh24:mi:ss')
else to_date(:last_updated,'dd/mm/yyyy')
end"
)
BEGINDATA
10,Sales,Virginia,12:03:03 17/10/2005
20,Accounting,Virginia,02:23:54
30,Consulting,Virginia,01:24:00 21/10/2005
40,Finance,Virginia,17/8/2005
// 加载有内嵌换行符的数据
方式如下:
方式如下:
加载数据,其中用非换行符的其他字符来表示换行符(例如,在文本中应该出现换行符的位置上放上串\n),并在加载时使用一个SQL函数用一个CHR(10)替换该文本。
在INFILE指令上使用FIX属性,加载一个定长平面文件。
在INFILE指令上使用VAR属性,加载一个定宽文件,在该文件使用的格式中,每一行的前几个字节指定了这一行的长度(字节数)。
在INFILE指令上使用STR属性,加载一个变宽文件,其中用某个字符序列来表示行结束符,而不是用换行符来表示。
1、使用一个非换行符的字符
如果你能对如何生成输入数据加以控制,这就是一种很容易的方法。如果创建数据文件时能很容易地转换数据,这种方法就能奏效。其思想是,就数据加载到数据库时对数据应用一个SQL函数,用某个字符串来替换换行符。
下面向DEPT表再增加另一个列:
SQL>alter table dept add comments varchar2(4000);
SQL>alter table dept add comments varchar2(4000);
我们将使用这一列来加载文本。下面是一个有内联数据的示例控制文件:
LOAD DATA
INFILE *
INTO TABLE DEPT
REPLACE
FIELDS TERMINATED BY ','
INFILE *
INTO TABLE DEPT
REPLACE
FIELDS TERMINATED BY ','
TRAILING NULLCOLS
(
DEPTNO,
DNAME "upper(:dname)",
LOC "upper(:loc)",
COMMENTS "replace(:comments,'\\n',chr(10))"
(
DEPTNO,
DNAME "upper(:dname)",
LOC "upper(:loc)",
COMMENTS "replace(:comments,'\\n',chr(10))"
)
BEGINDATA
10,Sales,Virginia,This is the Sales\nOffice in Virginia
20,Accounting,Virginia,This is the Accounting\nOffice in Virginia
30,Consulting,Virginia,This is the Consulting\nOffice in Virginia
40,Finance,Virginia,This is the Finance\nOffice in Virginia
BEGINDATA
10,Sales,Virginia,This is the Sales\nOffice in Virginia
20,Accounting,Virginia,This is the Accounting\nOffice in Virginia
30,Consulting,Virginia,This is the Consulting\nOffice in Virginia
40,Finance,Virginia,This is the Finance\nOffice in Virginia
注意,调用中必须使用\\n
来替换换行符,而不只是\n。这是因为\n会被SQLLDR识别为一个换行符,而且SQLLDR会把它转换为一个换行符,而不是一个两字符的串。利用以上控制文件执行SQLLDR时,DEPT表中将加载以下数据:
SQL>select deptno, dname, comments from dept;
DEPTNO DNAME COMMENTS
---------- -------------- -------------------------
10 SALES This is the Sales
Office in Virginia
20 ACCOUNTING This is the Accounting
Office in Virginia
... ...
---------- -------------- -------------------------
10 SALES This is the Sales
Office in Virginia
20 ACCOUNTING This is the Accounting
Office in Virginia
... ...
2、使用IFX属性
另一种可用的方法是使用FIX属性。如果使用这种方法,输入数据必须出现在定长记录中。每个记录与输入数据集中所有其他记录的长度都相同,即有相同的字节数。对于固定位置的数据,使用FIX属性就特别适合。这些文件通常是定长输入文件。使用自由格式的定界数据时,则不太可能是一个定长文件,因为这些文件通常是变长的(这正是定界文件的关键:每一行不会不必要地过长)。
使用FIX属性时,必须使用一个INFILE子句,因为FIX属性是INFILE的一个选项。另外,如果使用这个选项,数据必须在外部存储,而并非存储在控制文件本身。因此,假设有定长的输入记录,可以使用如下的一个控制文件:
LOAD DATA
INFILE demo.dat "fix 80"
INTO TABLE DEPT
REPLACE
FIELDS TERMINATED BY ','
TRAILING NULLCOLS
(
DEPTNO,
DNAME "upper(:dname)",
LOC "upper(:loc)",
COMMENTS
)
INFILE demo.dat "fix 80"
INTO TABLE DEPT
REPLACE
FIELDS TERMINATED BY ','
TRAILING NULLCOLS
(
DEPTNO,
DNAME "upper(:dname)",
LOC "upper(:loc)",
COMMENTS
)
这个文件指定了一个输入数据文件,这个文件中每个记录有80字节,这包括尾部的换行符(每个记录最后可能有换行符,也可能没有)。在这种情况下,输入数据文件中的换行符并不是特殊字符。这只是要加载(或不加载)的另一个字符而已。要知道:记录的最后如果有换行符,它会成为这个记录的一部分。为了充分理解这一点,我们需要一个实用程序将文件的内容转储在屏幕上,以便我们看到文件中到底有什么。使用UNIX(或任何Linux版本),利用od就很容易做到,这个程序可以将文件以八进制(和其他格式)转储到屏幕上。我们将使用下面的demo.dat文件。注意以下输入中的第一列实际上是八进制,所以第2行上的数字0000012是一个八进制数,不是十进制数10.由此我们可以知道所查看的文件中有哪些字节。我对这个输出进行了格式化,使得每行显示10个字符(使用-w10),所以0、12、24和36实际上就是0、10、20和30。
$ od -c -w10 -v demo.dat
0000000 1 0 , S a l e s , V
0000012 i r g i n i a , T h
0000024 i s i s t h e
0000036 S a l e s /n O f f i
0000050 c e i n V i r g
0000062 i n i a
0000074
0000106
0000120 2 0 , A c c o u n t
0000132 i n g , V i r g i n
0000144 i a , T h i s i s
0000156 t h e A c c o u
0000170 n t i n g /n O f f i
0000202 c e i n V i r g
0000214 i n i a
0000226
0000240 3 0 , C o n s u l t
0000252 i n g , V i r g i n
......
0000000 1 0 , S a l e s , V
0000012 i r g i n i a , T h
0000024 i s i s t h e
0000036 S a l e s /n O f f i
0000050 c e i n V i r g
0000062 i n i a
0000074
0000106
0000120 2 0 , A c c o u n t
0000132 i n g , V i r g i n
0000144 i a , T h i s i s
0000156 t h e A c c o u
0000170 n t i n g /n O f f i
0000202 c e i n V i r g
0000214 i n i a
0000226
0000240 3 0 , C o n s u l t
0000252 i n g , V i r g i n
......
注意,在这个输入文件中,并没有用换行符(\n)来指示SQLLDRE记录在哪里结束;这里的换行符只是要加载的数据而已。SQLLDR使用FIX宽度(80字节)来得出要读取多少数据。实际上,如果查看输入数据,可以看到,输入文件中提供给SQLLDR的记录甚至并非以/n结束。部门20的记录之前的字符是一个空格,而不是换行符。
既然我们知道了每个记录的长度为80字节,现在就可以用前面有FIX80子句的控制文件来加载这些数据了。完成加载后,可以看到以下结果:
SQL> select '"' || comments || '"' comments from dept;
SQL> select '"' || comments || '"' comments from dept;
COMMENTS
-------------------------------------------------------------------------------
"This is the Sales
Office in Virginia "
"This is the Accounting
Office in Virginia "
"This is the Consulting
Office in Virginia "
"This is the Finance
Office in Virginia "
"This is the Sales
Office in Virginia "
"This is the Accounting
Office in Virginia "
"This is the Consulting
Office in Virginia "
"This is the Finance
Office in Virginia "
你可能需要“截断“这个数据,因为尾部的空白符会保留。可以在控制文件中使用TRIM内置SQL函数来完成截断。
如果你恰好同时在使用Windows和UNIX,能你很“幸运“,在此需要提醒一句:这两个平台上的行结束标记是不同的。在UNIX上,行结束标记就是\n(SQL中的CHR(10))。在Windows NT上,行结束标记却是\r\n(SQL中的CHR(13)||CHR(10))。一般来讲,如果使用FIX方法,就要确保是在同构平台上创建和加载文件(UNIX上创建,UNIX上加载;或者Windows上创建,Windows上加载)。
如果你恰好同时在使用Windows和UNIX,能你很“幸运“,在此需要提醒一句:这两个平台上的行结束标记是不同的。在UNIX上,行结束标记就是\n(SQL中的CHR(10))。在Windows NT上,行结束标记却是\r\n(SQL中的CHR(13)||CHR(10))。一般来讲,如果使用FIX方法,就要确保是在同构平台上创建和加载文件(UNIX上创建,UNIX上加载;或者Windows上创建,Windows上加载)。
3、使用VAR属性
要加载有内嵌换行符的数据,另一种方法是使用VAR属性。使用这种格式时,每个记录必须以某个固定的字节数开始,这表示这个记录的总长度。通过使用这种格式,可以加载包含内嵌换行符的变长记录,但是每个记录的开始处必须有一个记录长度字段。因此,如果使用如下的一个控制文件:
LOAD DATA
INFILE demo.dat "var 3"
INTO TABLE DEPT
REPLACE
FIELDS TERMINATED BY ','
TRAILING NULLCOLS
(
DEPTNO,
DNAME "upper(:dname)",
LOC "upper(:loc)",
COMMENTS
)
INFILE demo.dat "var 3"
INTO TABLE DEPT
REPLACE
FIELDS TERMINATED BY ','
TRAILING NULLCOLS
(
DEPTNO,
DNAME "upper(:dname)",
LOC "upper(:loc)",
COMMENTS
)
VAR 3指出每个输入记录的前3个字节是输入记录的长度。如果取以下数据文件:
$ cat demo.dat
05510,Sales,Virginia,This is the Sales
Office in Virginia
06520,Accounting,Virginia,This is the Accounting
Office in Virginia
06530,Consulting,Virginia,This is the Consulting
Office in Virginia
05940,Finance,Virginia,This is the Finance
Office in Virginia
$ cat demo.dat
05510,Sales,Virginia,This is the Sales
Office in Virginia
06520,Accounting,Virginia,This is the Accounting
Office in Virginia
06530,Consulting,Virginia,This is the Consulting
Office in Virginia
05940,Finance,Virginia,This is the Finance
Office in Virginia
可以使用该控制文件来加载。在我们的输入数据文件中有4行数据。第一行从055开始,这说明接下来55字节是第一个输入记录。这55字节包括单词Virginia后的结束换行符。下一行从065开始。这一行有65字节的文本,依此类推。使用这种格式数据文件,可以很容易地加载有内嵌换行符的数据。
同样,如果你在使用UNIX和Windows(前面的例子都在UNIX上完成,其中换行符只是一个字符长),就必须调整每个记录的长度字段。在Windows上,前例.dat文件中的长度字段应该是56、66、66和60.
4、使用STR属性
要加载有内嵌换行符的数据,这可能是最灵活的一种方法。通过使用STR属性,可以指定一个新的行结束符(或字符序列)。 就能创建一个输入数据文件,其中每一行的最后有某个特殊字符、换行符不再有“特殊“含义。
我更喜欢使用字符序列,通常会使用某个特殊标记,然后再加一个换行符。这样,在一个文本编辑器或某个实用程序中查看输入数据时,就能很容易地看到行结束符,因为每个记录的最后仍然有一个换行符。STR属性以十六进制指定,要得到所需的具体十六进制串,最容易的方法是使用SQL和UTL_RAW来生成十六进制串。例如,假设使用的是UNIX平台,行结束标记是CHR(10)(换行),我们的特殊标记字符是一个管道符号(|),则可以写为:
SQL> select utl_raw.cast_to_raw( '|'||chr(10) ) from dual;
UTL_RAW.CAST_TO_RAW('|'||CHR(10))
-------------------------------------------------------------------------------
7C0A
-------------------------------------------------------------------------------
7C0A
由此可知,在UNIX上我们需要使用的STR是X’7C0A’。
注意 在Windows上,要使用UTL_RAW.CAST_TO_RAW(‘|”||chr(13)||chr(10))。
注意 在Windows上,要使用UTL_RAW.CAST_TO_RAW(‘|”||chr(13)||chr(10))。
为了使用这个方法,要有以下控制文件:
LOAD DATA
INFILE demo.dat "str X'7C0A'"
INTO TABLE DEPT
REPLACE
FIELDS TERMINATED BY ','
TRAILING NULLCOLS
(
DEPTNO,
DNAME "upper(:dname)",
LOC "upper(:loc)",
COMMENTS
)
LOAD DATA
INFILE demo.dat "str X'7C0A'"
INTO TABLE DEPT
REPLACE
FIELDS TERMINATED BY ','
TRAILING NULLCOLS
(
DEPTNO,
DNAME "upper(:dname)",
LOC "upper(:loc)",
COMMENTS
)
因此,如果输入数据如下:
$ cat demo.dat
10,Sales,Virginia,This is the Sales
Office in Virginia|
20,Accounting,Virginia,This is the Accounting
Office in Virginia|
30,Consulting,Virginia,This is the Consulting
Office in Virginia|
40,Finance,Virginia,This is the Finance
Office in Virginia|
10,Sales,Virginia,This is the Sales
Office in Virginia|
20,Accounting,Virginia,This is the Accounting
Office in Virginia|
30,Consulting,Virginia,This is the Consulting
Office in Virginia|
40,Finance,Virginia,This is the Finance
Office in Virginia|
其中,数据文件中的每个记录都以|\n结束,前面的控制文件就会正确地加载这些数据。
5、内嵌换行符小结
关于加载有内嵌换行符的数据,这一节讨论了至少4种方法。在后面的“平面文件卸载“一节中,我们还将看到会使用这里的一种技术,可以在一个通用卸载实用程序使用STR属性来避免与文本中换行符有关的问题。
另外要注意一个问题,我先前已经多次提到,Windows(包括各种版本)上的文本文件可能以\r\n(ASCII 13+ASCII 10,回车/换行)结束。\r是记录的一部分,控制文件必须适应这一点。具体地将,FIX和VAR中的字节数已经STR使用的串必须有所调整。例如,如果取先前的某个.dat文件(目前其中只包含\n),并使用一个ASCII传输工具(默认)将其通过FTP传输到Windows,将各个\n将转换为\r\n。原来UNIX中能工作的控制文件现在却不能加载数据了。这一点你必须当心,建立控制文件时一定要有所考虑。
// 通过SQLLDR加载LOB数据
现在我们来分析如何通过SQLLDR向LOB加载数据。对此方法不止一种,但是我们主要讨论两种最常用的方法:
.数据“内联“在其他数据中。
.数据外联存储(在外部存储),输入数据包含一个文件名,指示该行要加载的数据在哪个文件中。在SQLLDR术语中,这也称为二级数据文件(secondary data file,SDF)。
.数据外联存储(在外部存储),输入数据包含一个文件名,指示该行要加载的数据在哪个文件中。在SQLLDR术语中,这也称为二级数据文件(secondary data file,SDF)。
1、加载内联的LOB数据。
这些LOB通常内嵌有换行符和其他特殊字符。因此,往往会使用“如何加载有内嵌换行符的数据?“一节中详细讨论的4种方法之一来加载这种数据。下面先来修改DEPT表,使COMMENTS列是一个CLOB而不是一个大的VARCHAR2字段:
这些LOB通常内嵌有换行符和其他特殊字符。因此,往往会使用“如何加载有内嵌换行符的数据?“一节中详细讨论的4种方法之一来加载这种数据。下面先来修改DEPT表,使COMMENTS列是一个CLOB而不是一个大的VARCHAR2字段:
SQL> truncate table dept;
SQL> alter table dept drop column comments;
SQL> alter table dept add comments clob;
SQL> alter table dept drop column comments;
SQL> alter table dept add comments clob;
例如,假设有一个数据文件(demo.dat),它有以下内容:
10, Sales,Virginia,This is the Sales
Office in Virginia|
20,Accounting,Virginia,This is the Accounting
Office in Virginia|
30,Consulting,Virginia,This is the Consulting
Office in Virginia|
40,Finance,Virginia,"This is the Finance
Office in Virginia, it has embedded commas and is
much longer than the other comments field. If you
feel the need to add double quoted text in here like
this: ""You will need to double up those quotes!"" to
preserve them in the string. This field keeps going for up to
1000000 bytes (because of the control file definition I used)
or until we hit the magic end of record marker,
the | followed by an end of line - it is right here ->"|
Office in Virginia|
20,Accounting,Virginia,This is the Accounting
Office in Virginia|
30,Consulting,Virginia,This is the Consulting
Office in Virginia|
40,Finance,Virginia,"This is the Finance
Office in Virginia, it has embedded commas and is
much longer than the other comments field. If you
feel the need to add double quoted text in here like
this: ""You will need to double up those quotes!"" to
preserve them in the string. This field keeps going for up to
1000000 bytes (because of the control file definition I used)
or until we hit the magic end of record marker,
the | followed by an end of line - it is right here ->"|
每个记录最后都是一个管道符号(|),后面是行结束标记。部门40的文本比其他部门的文本长得多,有多个换行符、内嵌的引号以及逗号。给定这个数据文件,可以创建一个如下的控制文件:
LOAD DATA
INFILE demo.dat "str X'7C0A'"
INTO TABLE DEPT
REPLACE
FIELDS TERMINATED BY ',' OPTIONALLY ENCLOSED BY '"'
TRAILING NULLCOLS
(
DEPTNO,
DNAME "upper(:dname)",
LOC "upper(:loc)",
COMMENTS char(1000000)
)
INFILE demo.dat "str X'7C0A'"
INTO TABLE DEPT
REPLACE
FIELDS TERMINATED BY ',' OPTIONALLY ENCLOSED BY '"'
TRAILING NULLCOLS
(
DEPTNO,
DNAME "upper(:dname)",
LOC "upper(:loc)",
COMMENTS char(1000000)
)
注意 这个例子在UNIX上执行,UNIX平台上行结束标记长度为1字节,因此可以使用以上控制文件中的STR设置。在Windows上,STR设置则必须是’7C0D0A’。
要加载这个数据文件,我们在COMMENTS列上指定了CHAR(1000000),因为SQLLDR默认所有人们字段都为CHAR(255)。CHAR(1000000)则允许SQLLDR处理多达1,000,000字节的输入文本。可以把这个长度值设置为大于输入文件中任何可能文本块的大小。通过查看所加载的数据,可以看到以下结果:
SQL> select comments from dept;
我们可以观察到:原来重复两次的引号不再重复。SQLLDR去除了在此放置的额外的引号。
2、加载外联的LOB数据
可能要把包含有一些文件名的数据文件加载在LOB中,而不是让LOB数据与结构化数据混在一起,这种情况很常见。这提供了更大程度的灵活性,因为提供给SQLLDR的数据文件不必使用上述的4种方法之一来避开输入数据中的内嵌换行符问题,而这种情况在大量的文本或二进制数据中会频繁出现。SQLLDR称这种额外的数据文件为LOBFILE。
可能要把包含有一些文件名的数据文件加载在LOB中,而不是让LOB数据与结构化数据混在一起,这种情况很常见。这提供了更大程度的灵活性,因为提供给SQLLDR的数据文件不必使用上述的4种方法之一来避开输入数据中的内嵌换行符问题,而这种情况在大量的文本或二进制数据中会频繁出现。SQLLDR称这种额外的数据文件为LOBFILE。
SQLLDR还可以支持加载结构化数据文件(指向另外单独一个数据文件)。我们可能告诉SQLLDR如何从另外这个文件分析LOB数据,这样就可以加载其中的一部分作为结构化数据中的每一行。我认为这种模式的用途很有限(到目前为止,我自己还从来没有见过哪里用到这种方法),在此也不做过多的讨论。SQLLDR把这种外部引用的文件称为复杂二级数据文件(complex secondary data file)。
LOBFILE是一种相对简单的数据文件,旨在简化LOB加载。在LOBFILE中,没有记录的概念,因此换行符不会成为问题,正是这个性质使得LOBFILE与主要数据文件有所区别。在LOBFILE中,数据总是采用以下某种格式:
定长字段(例如,从LOBFILE加载字节100到1,000)
定界字段(以某个字符结束,或者用某个字符括起)
长度/值对,这是一个变长字段
定界字段(以某个字符结束,或者用某个字符括起)
长度/值对,这是一个变长字段
其中最常见的类型是定界字段,实际上就是以一个文件结束符(EOF)结束。一般来讲,可能有这样一个目录,其中包含你想加载到LOB列中的文件,每个文件都要完整地放在一个BLOB中。此时,就可以使用带TERMINATED BY EOF子句的LOBFILE语句。
假设我们有一个目录,其中包含想要加载到数据库中的文件。我们想加载文件的OWNER、文件的TIME_STAMP、文件的NAME以及文件本身。要加载数据的表如下所示:
create table lob_demo
(
owner varchar2(255),
time_stamp date,
filename varchar2(255),
data blob
);
(
owner varchar2(255),
time_stamp date,
filename varchar2(255),
data blob
);
在UNIX上使用一个简单的ls –l来捕获输出(或者在Windows上使用dir \q \n),我们就能生成输入文件,并使用如下的一个控制文件加载(这里使用UNIX平台):
LOAD DATA
INFILE *
REPLACE
INTO TABLE LOB_DEMO
(
owner position(17:25),
time_stamp position(44:55) date "Mon DD HH24:MI",
filename position(57:100),
data LOBFILE(filename) TERMINATED BY EOF
)
INFILE *
REPLACE
INTO TABLE LOB_DEMO
(
owner position(17:25),
time_stamp position(44:55) date "Mon DD HH24:MI",
filename position(57:100),
data LOBFILE(filename) TERMINATED BY EOF
)
BEGINDATA
-rw-r--r-- 1 tkyte tkyte 1220342 Jun 17 15:26 classes12.zip
-rw-r--r-- 1 tkyte tkyte 1220342 Jun 17 15:26 classes12.zip
现在,运行SQLLDR之后检查LOB_DEMO表的内容,会发现以下结果:
SQL> select owner, time_stamp, filename, dbms_lob.getlength(data) from lob_demo;
OWNER TIME_STAM FILENAME DBMS_LOB.GETLENGTH(DATA)
-------- --------- -------------- ------------------------
tkyte 17-JUN-05 classes12.zip 1220342
-------- --------- -------------- ------------------------
tkyte 17-JUN-05 classes12.zip 1220342
这不光适用于BLOB,也适用于CLOB。以这种方式使用SQLLDR来加载文本文件的目录会很容易。
3、将LOB数据加载到对象列
既然知道了如何将数据加载到我们自己创建的一个简单表中,可能会发现,有时需要将数据加载到一个复杂的表中,其中可能有一个包含LOB的复杂对象类型(列)。使用图像功能时这种情况最为常见。图像功能使用一个复杂的对象类型ORDSYS.ORDIMAGE来实现。我们需要告诉SQLLDR如何向其中加载数据。
要把一个LOB加载到一个ORDIMAGE类型的列中,首先必须对ORDIMAGE类型的结构有所了解。在SQL*Plus中使用要加载的一个目标表以及该表上的DESCRIBE,可以发现表中有一个名为IMAGE的ORDSYS.ORDIMAGE列,最终我们想在这一列中加载IMAGE.SOURCE.LOCALDATA。只有安装并配置好interMedia,项目的例子才能正常工作;否则,数据类型ORDSYS.ORDIMAGE将是一个未知类型:
create table p_w_picpath_load
(
id number,
name varchar2(255),
p_w_picpath ordsys.ordp_w_picpath
);
(
id number,
name varchar2(255),
p_w_picpath ordsys.ordp_w_picpath
);
SQL> desc p_w_picpath_load
Name Null? Type
---------------------------------------- -------- ----------------------------
ID NUMBER
NAME VARCHAR2(255)
IMAGE ORDSYS.ORDIMAGE
---------------------------------------- -------- ----------------------------
ID NUMBER
NAME VARCHAR2(255)
IMAGE ORDSYS.ORDIMAGE
SQL> desc ordsys.ordp_w_picpath
Name Null? Type
---------------------------------------- -------- ----------------------------
SOURCE ORDSYS.ORDSOURCE
HEIGHT NUMBER(38)
WIDTH NUMBER(38)
CONTENTLENGTH NUMBER(38)
...
---------------------------------------- -------- ----------------------------
SOURCE ORDSYS.ORDSOURCE
HEIGHT NUMBER(38)
WIDTH NUMBER(38)
CONTENTLENGTH NUMBER(38)
...
SQL> desc ordsys.ordsource
Name Null? Type
---------------------------------------- -------- ----------------------------
LOCALDATA BLOB
SRCTYPE VARCHAR2(4000)
SRCLOCATION VARCHAR2(4000)
...
---------------------------------------- -------- ----------------------------
LOCALDATA BLOB
SRCTYPE VARCHAR2(4000)
SRCLOCATION VARCHAR2(4000)
...
注意: 可以在SQL*Plus中执行SET DESC DEPTH ALL或SET DESC DEPTH 一次显示整个层次结构。
由于ORDSYS.ORDIMAGE的输出可能有几项的篇幅,所以我打算逐部分地介绍。
加载这种数据的控制文件可能如下所示:
LOAD DATA
INFILE *
INTO TABLE p_w_picpath_load
REPLACE
FIELDS TERMINATED BY ','
(
ID,
NAME,
file_name FILLER,
IMAGE column object
(
SOURCE column object
(
LOCALDATA LOBFILE (file_name) TERMINATED BY EOF
NULLIF file_name = 'NONE'
)
)
)
BEGINDATA
1,icons,icons.gif
LOAD DATA
INFILE *
INTO TABLE p_w_picpath_load
REPLACE
FIELDS TERMINATED BY ','
(
ID,
NAME,
file_name FILLER,
IMAGE column object
(
SOURCE column object
(
LOCALDATA LOBFILE (file_name) TERMINATED BY EOF
NULLIF file_name = 'NONE'
)
)
)
BEGINDATA
1,icons,icons.gif
这里我引入了两个新构造:
.COLUMN OBJECT:这会告诉SQLLDR这不是一个列名;而是列名的一部分。它不会映射到输入文件中的一个字段,只是用来构建正确的对象列引用,从而在加载中使用。在前面的文件中有两个列对象标记,其中一个(SOURCE)嵌入在另一个(SOURCE)嵌入在另一个(IMAGE)中。因此,根据我们的需要,要使用的列名是IMAGE.SOURCE.LOCALDATA。注意,我们没有加载这两个对象类型的任何其他属性(例如,IMAGE.HEIGHT、IMAGE.CONTENTLENGTH和IMAGE.SOURCE.SRCTYPE)。稍后,我们将介绍如何填充这些属性。
.NULL IF FILE_NAME = ‘NONE’:这会告诉SQLLDR,如果字段FILE_NAME包含单词NONE,则向对象列中加载一个NULL。
一旦已经加载了一个interMedia类型,通常需要使用PL/SQL对已经加载的数据进行后处理,以便interMedia能够处理该数据。例如,对于前面的数据,可能想运行以下代码来正确地为图像设置属性:
begin
for c in ( select * from p_w_picpath_load ) loop
c.p_w_picpath.setproperties;
end loop;
end;
/
for c in ( select * from p_w_picpath_load ) loop
c.p_w_picpath.setproperties;
end loop;
end;
/
SETPROPERTIES 是ORDSYS.ORDIMAGE类型提供的对象方法,它处理图像本身,并用适当的值更新对象的其余属性。
SQLLDR 警告
1、TRUNCATE的工作好像不太一样
SQLLDR的TRUNCATE选项看上去好像与SQL*Plus(或其他如何工具)中的TRUNCATE有所不同。SQLLDR有一个假设,认为你会向表中重新加载同样数目的数据,因此会使用一种扩展形式的TRUNCATE。具体地将,它会执行以下命令:
truncate table t reuse storage
truncate table t reuse storage
REUSE STORAGE选项并不释放已分配的区段,它只是将这些区段标记为“空闲空间”。如果这不是你想要的结果,就应当在执行SQLLDR之前先对表完成截除(truncate)。
2、SQLLDR默认地使用CHAR(255)
默认的输入字段长度为255字符。如果你的字段比这要长,就会将收到一个错误消息:
Record N: Rejected - Error on table T, column C.
Field in data file exceeds maximum length
Record N: Rejected - Error on table T, column C.
Field in data file exceeds maximum length
这并不是说这个数据无法放在数据库列中;而是说,它指示SQLLDR希望有不少或等于255字节的输入数据,不过稍多一些也会接收。对此解决方案很简单,只需在控制文件中使用CHAR(N),在此N要足够大,能容纳输入文件中最长的字段长度。
3、命令行会覆盖控制文件
SQLLDR的许多选项既可以放在控制文件中,也可以在命令行上使用。例如,可以使用INFILE FILENAME,也可以使用SQLLDR…DATA=FILENAME。命令行会覆盖控制文件中的任何选项。不能指望一定会使用控制文件中的选项,因为执行SQLLDR的人可能会通过命令行覆盖这些选项。
SQLLDR 小结
我们分析了SQLLDR加载数据的许多方面。在此介绍了每天可能遇到的一些典型问题:加载定界文件、加载定长文件、加载包含图像文件的一个目录,以及在输入数据上使用函数来转换输入等。我们没有详细介绍如何使用直接路径加载工具来加载大量数据;而只是简单地提了一下。我们的目标是回答使用SQLLDR时最常出现而且影响面最广的问题。