多表关联下的测试数据制造方法(三):使用SQL Loader导入数据
多表关联下的测试数据制造方法系列
在上一节,D瓜哥给大家介绍了一下如何使用存储过程,将数据导出成CSV文件。既然有数据文件了,我们该如何将这些数据导入到数据库中呢?!Oracle数据库提供了SQL Loader这样一个将数据文件导入到数据库中的功能。这篇文章将给大家介绍一下SQL Loader的用法。另外,再炒炒剩饭,把D瓜哥以前使用TCL语言写的脚本分享给大家。
SQL Loader简介
概念
SQL Loader是ORACLE的数据加载工具,通常用来将操作系统文件(数据)迁移到ORACLE数据库中。SQL Loader是大型数据仓库选择使用的加载方法,因为它提供了最快速的途径(DIRECT,PARALLEL)。
用法
首先,建立需要导入数据的表。如果存在就不需要新建了。
其次,编写一个控制文件。内容如下:
LOAD DATA INFILE 'data.dat' BADFILE 'data.bad' DISCARDMAX 1000 INSERT INTO TABLE emp FIELDS TERMINATED BY "," ( empno NUMBER(4) NOT NULL, ename CHAR(10), job CHAR(9), mgr NUMBER(4), hiredate DATE 'YYYYMMDD', sal NUMBER(7,2), comm NUMBER(7,2), deptno NUMBER(2) )
最后,SQL Loader的使用也很简单,只需要一条指令。如下:
sqlldr USERID=scott/tiger CONTROL=load1.ctl DIRECT=TRUE PARALLEL=true # USERID是配置用户名和密码的 # CONTROL是制定配置文件的 # DIRECT设置导入模式为Direct模式 # PARALLEL设置可以并行,同时导入多个文件
补充一句,控制文件高亮的一行是控制SQL Loader进行何种操作的。SQL Loader共提供四种操作分类:
- insert,为缺省方式,在数据装载开始时要求表为空;
- append,在表中追加新记录
- replace,删除旧记录,替换成新装载的记录;
- truncate,同上
这里,只给大家介绍这么多。如果想了解详细情况,请阅读Oracle提供的官方文档,文档中详细描述了SQL Loader的方方面面。
TCL导入脚本
上面简单介绍了一些SQL Loader的一些知识。下面给大家分享要给由TCL写成的使用SQL Loader导入数据的脚本。
关于TCL语言运行环境的配置,请看“D瓜哥的开源项目:CompareDatamodel”,D瓜哥在这篇文章里做过介绍。
配置文件
这个配置文件不是上面提到的”控制文件”。废话不多,直接上内容:
data_repeat_msg|已经汇总过数据,请勿重复汇总。 # 下面指定文件存放路径 file_path|c:// # 数据文件的分隔符。 separater|, # 包裹数据项的符号 enclosedSymbol|, # data_ok_msg|数据汇总完毕。 insert_table_failure_prompt|导入失败 # 数据库连接信息 db_sid|ORCL db_user|SCOTT db_password|tiger
读取配置文件
有配置文件了,需要有读取配置文件的脚本,如下:
# ** 此脚本用来读取data_dic.txt字典文件,并将相应信息保存到label全局数组里 # ** 创建日期:20100816 # ** 创建人员:付骋 # ** 修改日期: # ** 修改人员: global label set fid [open "$absoluteCurrentPath//data_dic.txt" r] while {[eof $fid] != 1} { set curLine [gets $fid] # 遇到空行或者以#开头的行,忽略 if {[regexp {(^(\s)*#)|(^(\s)*$)} $curLine]} { continue } set curLineList [split $curLine "|"] # 数组索引 set index [lindex $curLineList 0] # 对应值 set value [lindex $curLineList 1] set label($index) $value } close $fid
注意,这个脚本不能直接使用。需要导入到其他文件才能用。具体用法见下面。
导入脚本
上面所有的铺垫工作已经做完。现在该主角登场了。导入脚本如下:
#!/bin/bash # 040_load_data.tcl \ exec tclsh "$0" ${1+"$@"} #encoding convertto "UTF-8" # ** 此脚本用来读取data_dic.txt字典文件,并将相应信息保存到label全局数组里 # ** 创建日期:20100816 # ** 创建人员:D瓜哥,http://www.diguage.com/ # ** 修改日期: # ** 修改人员: encoding system identity package require Oratcl # 获得当前路径 set currentPath [file dirname [info script]] cd $currentPath #获得当前绝对路径 set absoluteCurrentPath [pwd] # 导入字典文件读取源文件 source "$absoluteCurrentPath//read_data_dic.tcl" ################################################################ #procedure query_tableFieldtype #查询要插入数据表的表结构类型说明 # sInsertTableName 需要插入的表名称 # columnNamesList 列顺序 # dateFormat 数据文件中的日期格式。默认是YYYYMMDDHH24MISS。请根据实际情况修改。 ################################################################ proc query_tableFieldtype {sInsertTableName columnNamesList fhandleSelect {dateFormat "YYYYMMDDHH24MISS"} } { global label set listFieldLength [llength $columnNamesList] set strSql "select column_name, decode(data_type, 'VARCHAR2', 'CHAR NULLIF (' || column_name || '=BLANKS) \"trim(:' || column_name || ')\"', 'FLOAT', 'DECIMAL EXTERNAL NULLIF(' || column_name || '=BLANKS)', 'NUMBER', decode(data_precision, 0, 'INTEGER EXTERNAL NULLIF (' || column_name || '=BLANKS)', decode(data_scale, 0, 'INTEGER EXTERNAL NULLIF (' || column_name || '=BLANKS)', 'DECIMAL EXTERNAL NULLIF (' || column_name || '=BLANKS)')), 'DATE', 'DATE \"[string trim $dateFormat]\" NULLIF (' || column_name || '=BLANKS)', null) data_type" append strSql " from USER_TAB_COLUMNS where table_name =upper('" append strSql $sInsertTableName "') and column_name in (" append strSql upper('[join $columnNamesList '),upper(']')) puts "数据类型:$strSql" if [catch {orasql $fhandleSelect $strSql} errmsg] { error $errmsg } elseif {$errmsg != 0 && $errmsg != 100 && $errmsg != 1403} { error $errmsg } set listFieldAndType [list] while {[oramsg $fhandleSelect] == 0} { orafetch $fhandleSelect -datavariable rowResult -command { set sFieldName [lindex $rowResult 0] lappend listFieldAndType $sFieldName set sFieldType [lindex $rowResult 1] lappend listFieldAndType $sFieldType } } #获取表字段类型时,因查询不能保证字段先后顺序,加入如下判断 set listFieldLength [llength $columnNamesList] set listFieldType [list] for {set i 0} {$i<$listFieldLength} {incr i} { set fieldName [lindex $columnNamesList $i] set fieldName [string trim $fieldName '] for {set j 0} {$j<$listFieldLength} {incr j} { set dbFieldName [lindex $listFieldAndType [expr $j*2]] if {[string toupper $fieldName]==[string toupper $dbFieldName]} { lappend listFieldType [lindex $listFieldAndType [expr $j*2+1]] break } } } return $listFieldType } ################################################################# #procedure creat_sqlLoaderCtl #拼装sqlLoader控制文件 # tableName 表名 # columnName 列名 # columnType 列数据类型 # dataFileName 源数据文件名 # loadType 导入类型 # ctlFileName 需要生成临时的控制文件名 # separater 数据文件中,各个数据项的分隔符 # isAppend 数据的添加方式:如果传true,则是添加;如果传false,则会将表中原来的数据删除再添加。 # (注意:删除时使用truncate,不可恢复。) ################################################################# proc creat_sqlLoaderCtl {tableName columnName columnType dataFileName ctlFileName separater enclosedSymbol {isAppend true} } { set ctlFile [open $ctlFileName w] set fieldLength [llength $columnName] puts $ctlFile "LOAD DATA" puts $ctlFile "INFILE \"$dataFileName\"" if {$isAppend} { puts $ctlFile "APPEND" } else { puts $ctlFile "TRUNCATE" } puts $ctlFile "INTO TABLE $tableName" puts $ctlFile "FIELDS TERMINATED BY \"[string trim $separater]\"" ;#文件的分隔符 puts $ctlFile "OPTIONALLY ENCLOSED BY '[string trim $enclosedSymbol]'" ;#文件的分隔符 #puts $ctlFile "TRAILING NULLCOLS" puts $ctlFile ( for {set i 0} {$i<$fieldLength } {incr i} { if {$i==[expr $fieldLength-1]} { puts $ctlFile "[string toupper [lindex $columnName $i]] [lindex $columnType $i] " } else { puts $ctlFile "[string toupper [lindex $columnName $i]] [lindex $columnType $i]," } } puts $ctlFile ) close $ctlFile return $ctlFileName } proc getDealFileInfo {sInsertTableName columnNamesList isAppend periodType indiDate fileNamesList} { global label global env global argv # global sDataBaseUser global fhandleSelect # global fhandleInsert if [catch {set dbhandle [db_connect $label(db_sid) $label(db_user) $label(db_password)]} logon_errmsg] { error logon_errmsg } if [catch {set fhandleSelect [db_open $dbhandle] } open_errmsg] { error $open_errmsg } global sSysDate global sWorkFilePath set listField [list] set sFilePath [string trim "$label(file_path)"] set sWorkFilePath [string trim "$label(file_path)"] set sFieldString "" #set sStartTime [get_currentDate $fhandleSelect] set llRecNum 0 set sqldrLog $env(AGENTAPPLOG)/[lindex $argv 1]\_[lindex $argv 3]_ set listFieldType [query_tableFieldtype $sInsertTableName $columnNamesList $fhandleSelect] #=====根据文件路径加文件名称判断改目录下是否存在改文件===== foreach sFileName $fileNamesList { set sPathAndFileName "" append sPathAndFileName $sFilePath append sPathAndFileName [string trim $sFileName] puts $sPathAndFileName if { ! [file exists $sPathAndFileName] } { error "文件不存在,请检查文件:$sPathAndFileName" } #=====生成临时SQLLADER控件文件===== set ctlFileName [string trim $sWorkFilePath]sqlloader/sql_loader_[string trim $sInsertTableName]_[string trim $indiDate]_[string trim $periodType].ctl puts "控制文件:$ctlFileName" set ctlFileName [creat_sqlLoaderCtl $sInsertTableName $columnNamesList $listFieldType $sPathAndFileName $ctlFileName $label(separater) $label(enclosedSymbol) $isAppend] #拼接SQL Loader日志文件名称 append sqldrLog [clock format [clock seconds] -format "%Y%m%d%H%M%S"] append sqldrLog ".sqldr.log" #执行SQLLOADER,导入数据到表 set loadHasError false set errorInfo false if {[catch {exec sqlldr $label(db_user)/$label(db_password)@$label(db_sid) control=$ctlFileName log=$sqldrLog errors=0 parallel=true} selectError]} { set loadHasError true set errorInfo $selectError puts $selectError } if {$loadHasError} { error "$label(insert_table_failure_prompt)$errorInfo" } puts "OK,check data" #=====文件处理完成,存放备份目录===== #从/data目录备份到/backup/data/YYYY-MM-DD/文件需要移走 set sFileDescPath $indiDate # set sDescFileName [string trim $sFileDescPath][string trim $sFileName] puts "[string trim $sFileDescPath][string trim $sFileName]" # file copy -force $sPathAndFileName $sDescFileName # file delete $sPathAndFileName #=====记录文件处理的日志===== } } proc run {} { global label #参数 global argv # 使用的时候,根据实际情况修改这里 set tableName "CMN_COL_CFG" set columnList [split "COL_CFG_ID,COL_NAME,CREATE_DATE" ","] set isAppend true set periodType [lindex $argv 7] set date [lindex $argv 5] set dataFiles "td.csv" ;#多个文件,请使用逗号(,)分割 set dataFileList [split $dataFiles ","] getDealFileInfo $tableName $columnList $isAppend $periodType $date $dataFileList } # 调用 run
由于,SyntaxHighlighter不能提供TCL语言的高亮显示。所以,这里的高亮可能还有一些问题。抱歉!
D瓜哥最近接触了一些Ruby语言,Ruby真是一个灵活的语言,同样也很好玩。非常适合自定义DSL,以后有时间D瓜哥把这个脚本再用Ruby重写一遍。
到这里,这个系列基本结束。可能还有一些问题。例如:
- 使用先制作成数据文件,然后在使用SQL Loader导入到数据库中这种方式,需要监控数据文件的生成完成时间,然后再使用进行导入。但是,制作和导入,前者在数据库中使用存储过程完成;后者使用TCL(当然也可以使用其他语言完成)。横跨两个环境,监控起来不方便;
- 使用这种方式,需要在数据库服务器上安装TCL运行环境;
综上,D瓜哥认为使用Shell脚本应该可以完整完成这个全部过程。不过,D瓜哥目前还不会写Shell,这个就留给感兴趣的朋友自己练手吧。当然,如果以后D瓜哥使用Shell实现了,还会发布出来的。
参考资料
修改日志
2013年3月28日 补充问题列表。
原文链接:https://wordpress.diguage.com/archives/109.html
版权声明:非特殊声明均为本站原创作品,转载时请注明作者和原文链接。
哈哈瓜哥被你QQ空间引过来了,我这还有最新一版sqlloader要不要