首页 > 动态语言, 挨踢(IT), 数据库 > 多表关联下的测试数据制造方法(三):使用SQL Loader导入数据

多表关联下的测试数据制造方法(三):使用SQL Loader导入数据

2013年3月12日 发表评论 阅读评论 990 人阅读    

多表关联下的测试数据制造方法系列

  1. 多表关联下的测试数据制造方法(一):思路详解
  2. 多表关联下的测试数据制造方法(二):将数据导出为CSV文件

  在上一节,D瓜哥给大家介绍了一下如何使用存储过程,将数据导出成CSV文件。既然有数据文件了,我们该如何将这些数据导入到数据库中呢?!Oracle数据库提供了SQL Loader这样一个将数据文件导入到数据库中的功能。这篇文章将给大家介绍一下SQL Loader的用法。另外,再炒炒剩饭,把D瓜哥以前使用TCL语言写的脚本分享给大家。

SQL Loader简介

概念

  SQL Loader是ORACLE的数据加载工具,通常用来将操作系统文件(数据)迁移到ORACLE数据库中。SQL Loader是大型数据仓库选择使用的加载方法,因为它提供了最快速的途径(DIRECT,PARALLEL)。

用法

  首先,建立需要导入数据的表。如果存在就不需要新建了。

  其次,编写一个控制文件。内容如下:

01LOAD DATA
02INFILE 'data.dat'
03BADFILE 'data.bad'
04DISCARDMAX 1000
05 
06INSERT
07 INTO TABLE emp
08FIELDS TERMINATED BY ","
09(
10empno NUMBER(4) NOT NULL,
11ename CHAR(10),
12job CHAR(9),
13mgr NUMBER(4),
14hiredate DATE 'YYYYMMDD',
15sal NUMBER(7,2),
16comm NUMBER(7,2),
17deptno NUMBER(2)
18)

  最后,SQL Loader的使用也很简单,只需要一条指令。如下:

1sqlldr USERID=scott/tiger CONTROL=load1.ctl DIRECT=TRUE PARALLEL=true
2# USERID是配置用户名和密码的
3# CONTROL是制定配置文件的
4# DIRECT设置导入模式为Direct模式
5# PARALLEL设置可以并行,同时导入多个文件

  补充一句,控制文件高亮的一行是控制SQL Loader进行何种操作的。SQL Loader共提供四种操作分类:

  1. insert,为缺省方式,在数据装载开始时要求表为空;
  2. append,在表中追加新记录
  3. replace,删除旧记录,替换成新装载的记录;
  4. truncate,同上

  这里,只给大家介绍这么多。如果想了解详细情况,请阅读Oracle提供的官方文档,文档中详细描述了SQL Loader的方方面面。

TCL导入脚本

  上面简单介绍了一些SQL Loader的一些知识。下面给大家分享要给由TCL写成的使用SQL Loader导入数据的脚本。

  关于TCL语言运行环境的配置,请看“D瓜哥的开源项目:CompareDatamodel”,D瓜哥在这篇文章里做过介绍。

配置文件

  这个配置文件不是上面提到的”控制文件”。废话不多,直接上内容:

01data_repeat_msg|已经汇总过数据,请勿重复汇总。
02# 下面指定文件存放路径
03file_path|c:// 
04# 数据文件的分隔符。
05separater|,
06# 包裹数据项的符号
07enclosedSymbol|,
08# data_ok_msg|数据汇总完毕。
09insert_table_failure_prompt|导入失败
10 
11# 数据库连接信息
12db_sid|ORCL
13db_user|SCOTT
14db_password|tiger

读取配置文件

  有配置文件了,需要有读取配置文件的脚本,如下:

01# **  此脚本用来读取data_dic.txt字典文件,并将相应信息保存到label全局数组里
02# **  创建日期:20100816
03# **  创建人员:付骋
04# **  修改日期:
05# **  修改人员:
06global label
07set fid [open "$absoluteCurrentPath//data_dic.txt" r]
08while {[eof  $fid] != 1} {
09    set curLine [gets $fid]
10    # 遇到空行或者以#开头的行,忽略
11    if {[regexp {(^(\s)*#)|(^(\s)*$)} $curLine]} {
12        continue
13    }
14    set curLineList [split $curLine "|"]
15    # 数组索引
16    set index [lindex $curLineList 0]
17    # 对应值
18    set value [lindex $curLineList 1]
19    set label($index) $value
20}
21close $fid

  注意,这个脚本不能直接使用。需要导入到其他文件才能用。具体用法见下面。

导入脚本

  上面所有的铺垫工作已经做完。现在该主角登场了。导入脚本如下:

001#!/bin/bash
002# 040_load_data.tcl \
003exec tclsh "$0" ${1+"$@"}
004#encoding convertto "UTF-8"
005 
006# **  此脚本用来读取data_dic.txt字典文件,并将相应信息保存到label全局数组里
007# **  创建日期:20100816
008# **  创建人员:D瓜哥,http://www.diguage.com/
009# **  修改日期:
010# **  修改人员:
011 
012encoding system identity
013 
014package require Oratcl
015 
016# 获得当前路径
017set currentPath [file dirname [info script]]
018cd $currentPath
019#获得当前绝对路径
020set absoluteCurrentPath [pwd]
021 
022# 导入字典文件读取源文件
023source "$absoluteCurrentPath//read_data_dic.tcl"
024 
025################################################################
026#procedure query_tableFieldtype
027#查询要插入数据表的表结构类型说明
028#    sInsertTableName 需要插入的表名称
029#    columnNamesList  列顺序
030#    dateFormat       数据文件中的日期格式。默认是YYYYMMDDHH24MISS。请根据实际情况修改。
031################################################################
032proc query_tableFieldtype {sInsertTableName columnNamesList fhandleSelect {dateFormat "YYYYMMDDHH24MISS"} } { 
033    global label
034     
035    set listFieldLength [llength $columnNamesList]
036    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"
037    append strSql " from USER_TAB_COLUMNS where table_name =upper('"
038    append strSql $sInsertTableName "') and column_name in ("
039    append strSql upper('[join $columnNamesList '),upper(']'))
040     
041    puts "数据类型:$strSql"
042     
043    if [catch {orasql $fhandleSelect $strSql} errmsg] {
044        error $errmsg
045    } elseif {$errmsg != 0 && $errmsg != 100 && $errmsg != 1403} {
046        error $errmsg
047    }
048     
049    set listFieldAndType [list]
050    while {[oramsg $fhandleSelect] == 0} {
051        orafetch $fhandleSelect  -datavariable rowResult -command {
052            set sFieldName [lindex $rowResult 0]
053            lappend listFieldAndType $sFieldName
054            set sFieldType [lindex $rowResult 1]
055            lappend listFieldAndType $sFieldType
056        }
057    }
058     
059    #获取表字段类型时,因查询不能保证字段先后顺序,加入如下判断
060    set listFieldLength [llength $columnNamesList]
061    set listFieldType [list]
062    for {set i 0} {$i<$listFieldLength} {incr i} {
063       set fieldName [lindex $columnNamesList $i]
064       set fieldName [string trim $fieldName ']
065       for {set j 0} {$j<$listFieldLength} {incr j} {
066          set dbFieldName [lindex $listFieldAndType [expr $j*2]]
067          if {[string toupper $fieldName]==[string toupper $dbFieldName]} {
068            lappend listFieldType [lindex $listFieldAndType [expr $j*2+1]]
069            break
070          }
071       }
072    }
073    return $listFieldType 
074}
075 
076 
077#################################################################
078#procedure creat_sqlLoaderCtl
079#拼装sqlLoader控制文件
080#    tableName    表名
081#    columnName   列名
082#    columnType   列数据类型
083#    dataFileName 源数据文件名
084#    loadType     导入类型
085#    ctlFileName  需要生成临时的控制文件名
086#    separater    数据文件中,各个数据项的分隔符
087#    isAppend     数据的添加方式:如果传true,则是添加;如果传false,则会将表中原来的数据删除再添加。
088#   (注意:删除时使用truncate,不可恢复。)
089#################################################################
090proc creat_sqlLoaderCtl {tableName columnName columnType dataFileName ctlFileName separater enclosedSymbol {isAppend true} } {
091    set ctlFile [open $ctlFileName w]
092    set fieldLength [llength $columnName]
093    puts $ctlFile "LOAD DATA"
094    puts $ctlFile "INFILE \"$dataFileName\""
095    if {$isAppend} {
096        puts $ctlFile "APPEND"
097    } else {
098        puts $ctlFile "TRUNCATE"
099    }
100    puts $ctlFile "INTO TABLE $tableName"
101    puts $ctlFile "FIELDS TERMINATED BY \"[string trim $separater]\""  ;#文件的分隔符
102    puts $ctlFile "OPTIONALLY ENCLOSED BY '[string trim $enclosedSymbol]'"  ;#文件的分隔符
103    #puts $ctlFile "TRAILING NULLCOLS"
104    puts $ctlFile (
105    for {set i 0} {$i<$fieldLength } {incr i} {
106       if {$i==[expr $fieldLength-1]} {
107           puts $ctlFile "[string toupper [lindex $columnName $i]] [lindex $columnType $i] "
108       } else {
109           puts $ctlFile "[string toupper [lindex $columnName $i]] [lindex $columnType $i],"
110       }
111    }
112    puts $ctlFile )
113    close $ctlFile
114    return $ctlFileName
115}
116 
117proc getDealFileInfo {sInsertTableName columnNamesList isAppend  periodType indiDate fileNamesList} {
118     
119    global label
120    global env
121    global argv
122#   global sDataBaseUser
123     
124    global fhandleSelect
125#   global fhandleInsert
126     
127    if [catch {set dbhandle [db_connect $label(db_sid) $label(db_user) $label(db_password)]} logon_errmsg] {
128          error logon_errmsg
129    }
130     
131    if [catch {set fhandleSelect [db_open $dbhandle] } open_errmsg]   {
132          error $open_errmsg
133    }
134     
135    global sSysDate
136    global sWorkFilePath
137     
138    set listField [list]
139    set sFilePath [string trim "$label(file_path)"]
140    set sWorkFilePath [string trim "$label(file_path)"]
141    set sFieldString ""
142    #set sStartTime [get_currentDate $fhandleSelect]
143    set llRecNum 0
144     
145    set sqldrLog $env(AGENTAPPLOG)/[lindex $argv 1]\_[lindex $argv 3]_
146     
147    set listFieldType [query_tableFieldtype $sInsertTableName $columnNamesList $fhandleSelect]
148     
149    #=====根据文件路径加文件名称判断改目录下是否存在改文件=====   
150    foreach sFileName $fileNamesList {
151        set sPathAndFileName ""
152        append sPathAndFileName $sFilePath
153        append sPathAndFileName [string trim $sFileName]
154         
155        puts $sPathAndFileName
156         
157        if { ! [file exists $sPathAndFileName] } {
158            error "文件不存在,请检查文件:$sPathAndFileName"
159        }
160        #=====生成临时SQLLADER控件文件=====
161        set ctlFileName [string trim $sWorkFilePath]sqlloader/sql_loader_[string trim $sInsertTableName]_[string trim $indiDate]_[string trim $periodType].ctl
162        puts "控制文件:$ctlFileName"
163         
164        set ctlFileName [creat_sqlLoaderCtl $sInsertTableName $columnNamesList $listFieldType $sPathAndFileName $ctlFileName $label(separater) $label(enclosedSymbol) $isAppend]
165         
166        #拼接SQL Loader日志文件名称
167        append sqldrLog [clock format [clock seconds] -format "%Y%m%d%H%M%S"]
168        append sqldrLog ".sqldr.log"
169         
170        #执行SQLLOADER,导入数据到表
171        set loadHasError false
172        set errorInfo false
173        if {[catch {exec sqlldr $label(db_user)/$label(db_password)@$label(db_sid) control=$ctlFileName  log=$sqldrLog errors=0 parallel=true} selectError]} {
174            set loadHasError true
175            set errorInfo $selectError
176            puts $selectError
177        }
178        if {$loadHasError} {
179            error "$label(insert_table_failure_prompt)$errorInfo"
180        }
181         
182        puts "OK,check data"
183        #=====文件处理完成,存放备份目录=====
184        #从/data目录备份到/backup/data/YYYY-MM-DD/文件需要移走
185        set sFileDescPath $indiDate
186#       set sDescFileName [string trim $sFileDescPath][string trim $sFileName]
187        puts "[string trim $sFileDescPath][string trim $sFileName]"
188#       file copy -force $sPathAndFileName $sDescFileName
189#       file delete $sPathAndFileName
190        #=====记录文件处理的日志=====
191    }
192}
193 
194proc run {} {
195    global label
196 
197    #参数
198    global argv
199     
200    # 使用的时候,根据实际情况修改这里
201    set tableName "CMN_COL_CFG"
202    set columnList [split "COL_CFG_ID,COL_NAME,CREATE_DATE" ","]
203    set isAppend  true
204    set periodType [lindex $argv 7]
205    set date [lindex $argv 5]
206    set dataFiles "td.csv"     ;#多个文件,请使用逗号(,)分割
207    set dataFileList [split $dataFiles ","]
208     
209     
210    getDealFileInfo $tableName $columnList $isAppend $periodType $date $dataFileList
211}
212 
213# 调用
214run

  由于,SyntaxHighlighter不能提供TCL语言的高亮显示。所以,这里的高亮可能还有一些问题。抱歉!

  D瓜哥最近接触了一些Ruby语言,Ruby真是一个灵活的语言,同样也很好玩。非常适合自定义DSL,以后有时间D瓜哥把这个脚本再用Ruby重写一遍。

  到这里,这个系列基本结束。可能还有一些问题。例如:

  1. 使用先制作成数据文件,然后在使用SQL Loader导入到数据库中这种方式,需要监控数据文件的生成完成时间,然后再使用进行导入。但是,制作和导入,前者在数据库中使用存储过程完成;后者使用TCL(当然也可以使用其他语言完成)。横跨两个环境,监控起来不方便;
  2. 使用这种方式,需要在数据库服务器上安装TCL运行环境;

  综上,D瓜哥认为使用Shell脚本应该可以完整完成这个全部过程。不过,D瓜哥目前还不会写Shell,这个就留给感兴趣的朋友自己练手吧。当然,如果以后D瓜哥使用Shell实现了,还会发布出来的。

 

参考资料

  1. sqlldr的介绍
  2. SQL*Loader 详解
  3. 使用SQL Loader导入大量数据,避免使用SQL频繁写库
  4. Oracle官方文档:SQL*Loader

 

修改日志

  2013年3月28日  补充问题列表。

作 者: D瓜哥,https://www.diguage.com/
原文链接:https://wordpress.diguage.com/archives/109.html
版权声明:非特殊声明均为本站原创作品,转载时请注明作者和原文链接。

  1. 树林
    2014年3月31日23:03 | #1

    哈哈瓜哥被你QQ空间引过来了,我这还有最新一版sqlloader要不要

  1. 本文目前尚无任何 trackbacks 和 pingbacks.