从 dBASE IV 移植数据到 PostgreSQL

来自 PostgreSQL wiki
跳转到导航跳转到搜索

从 dBASE IV 移植数据到 PostgreSQL

最后更新于 2002 年 9 月 7 日。

版权所有 © 2002 Vijay Deval。

我将一些表格从 dBASE IV 移植到 PostgreSQL。本页说明了我在其中一个表格上所遵循的步骤,以备他人参考。

我们要移植的表格名为 client。其中有一个名为 *CLID* 的列,在 PostgreSQL 中定义为一个序列。由于 PostgreSQL 自动生成其序列号,因此无法使用 **COPY** 命令加载转换后的数据。必须逐条记录地传输数据。这可以通过将 dBASE 中的记录转换为一系列 SQL 语句来完成。

在我们开始之前,需要从文本文件中排除 *CLID* 列。一个 "cut"(这是一个 *nix 命令)或一个 (g)awk 脚本可以轻松地为我们做到这一点。为了对传输的数据进行简单的检查,文本文件中的 *CLID* 数据被插入到 PostgreSQL 表定义中的一个新的 *OLDCLID* 列中。如果在传输后 *CLID* 和 *OLDCLID* 一直匹配,则表示数据传输正确且成功。一旦我们知道发生了这种情况,就可以删除 *OLDCLID* 列。

如果 dBASE IV 表中并非所有列都需要,则需要删除不需要的列。文本数据文件中剩余列的顺序也可能需要重新排列。如果是这种情况,则使用逗号作为字段分隔符将不适用,因为逗号可能作为文本数据的一部分出现。用于重新排列和/或删除文本数据文件中某些字段的 (g)awk 脚本无法区分文本数据中的逗号和作为字段分隔符插入的逗号。字段分隔符需要是数据中不太可能出现的字符。

在本例中,由于 *CLID* 需要 client 表的所有列,因此使用逗号作为字段分隔符就可以了。不过,我选择在这里使用 | 字符作为字段分隔符,只是为了说明选择的自由性。'|' 在将文本文件转换为 SQL 命令(作为 *clipop.sql*)之前被替换为 ','。

SQL **COPY** 命令允许您将字段分隔符从逗号更改为其他字符,但对于 SQL 语句,仍然需要只有逗号作为字段分隔符。

实际步骤如下。

在 dBASE IV 中

  1. 使用 client
  2. 将 stru extended 复制到 clistru
  3. 从控制中心将 *clientf.dbf* 和 *clistru.dbf* 导出为固定字段长度的文本文件。*client.txt* 是数据,*clistru.txt* 给出 *client.dbf* 结构的详细信息。
  4. 回到 Linux 中,mread *client.txt* 和 *clistru.txt*
  5. 使用 C 脚本在文本文件中添加字段分隔符。我将它命名为 *fields.c*。
  6. 创建一个 gawk 脚本,并将每条记录转换为一个 sql 语句。

*clistru.txt* 文件,描述了 dBASE IV 中 *client* 表的结构,如下所示。

CLID N 4 0Y
NAME C 45 0N
ADDR1 C 40 0N
ADDR2 C 40 0N
ADDR3 C 30 0N

使用此文件,可以在 PostgreSQL 中创建相应的表。

*client.txt* 的一小段摘录

1
Philips India Limited.
Loni Kalbhor
Near(C.Rly)

Pune 412 201
2
Champion Electronics Pvt. Ltd.
S-17, M.I.D.C.
Bhosari
Pune 411 026

名为 *client* 的表是用以下命令创建的

create table CLIENT (
    CLID         serial,
    OLDCLID      INT,
    NAME         VARCHAR(40),
    ADDR1        VARCHAR(45),
    ADDR2        VARCHAR(45),
    ADDR3        VARCHAR(45),
    PRIMARY KEY(CLID) );

*CLID* 是此表的主键。

在确认数据迁移准确无误后,可以删除 *OLDCLID* 列。

编译 fields.c

$ a.out < client.txt > tmp

这将创建名为 *tmp* 的文件。它与 *client.txt* 相同,但具有字段分隔符。以下是 *tmp* 的摘录

1|'Philips India Limited. '|'Loni Kalbhor '|'Near(C.Rly) '|'Pune 412201 ' 2|'Champion Electronics Pvt. Ltd. '|'S-17, M.I.D.C. '|'Bhosari '|'Pune 411 026 '

借助 sed 或 emacs 将 '|' 替换为 ','。

gawk 脚本

我将 gawk 脚本命名为 *clipop.awk*。它如下所示

{print "insert into client (oldclid, name,addr1,addr2,addr3) values (" $0, ");"}

$ gawk -f clipop.awk tmp>clipop.sql

将文件转换为 SQL 语句。

*clipop.sql* 中的一行示例

insert into client (oldclid, name,addr1,addr2,addr3) values ( 1,'Philips India Limited. ','Loni Kalbhor ','Near(C.Rly) ','Pune 412 201 ');
\i clopop.sql

将文件加载到 PostgreSQL 中。


C 脚本,在字段边界添加字段分隔符

#include <stdio.h>

#define FNM 5 /* number of fields */
#define FS "|" /* field separator */
#define BLANKDATE "NULL" /* select default for blank date field. (8) */
#define BLANKCHAR "NULL" /* select default value for blank char field */
#define BLANKNUM "0" /* NULL if required */

main()
{
int c,i,len,count,blank;

int flen[] = {4,45,40,40,30};
int nmchr[] = {0,1,1,1,2}; /* (1) */
/* User need not edit the code that follows. */
/*---------------------------------------------------------------------*/
 blank=0;
 count=0;
 i=0;
 len=flen[0];
 c=getchar();
 while (c != EOF){
   if( (nmchr[0]==1)&&(count==0))
     printf("'"); /* (2) */
   if(count==len) { /* at the boundary, 1st char of new field */
     if ((nmchr[i]==1)||(nmchr[i]==2)) /* value of i is of prev field */
       printf("'"); /* (3) */
     if(i<FNM-1)
       printf("%s", FS); /*(4)*/
     i++;
     len=len+flen[i]; /* add len of new field */
     if((nmchr[i]==1)||(nmchr[i]==2))
       printf("'"); /* leading inv comma if reqd */
     blank=0; /* before starting work on new field balnk is set to zero */
   }
   if (c==' ')
     blank++;
   else
     blank=0;

   if((blank>1)||((count==(len-flen[i]))&&(c==' '))) /* (5) */
     ;
   else
     putchar(c);

   count=count+1;
   c=getchar();

   if ((count==len-1)&&(c== ' ')&&(blank==flen[i]-1)){
      if (nmchr[i]==0)
         printf("%s",BLANKNUM); /*(6) */
      if (nmchr[i]==2)
         printf("%s",BLANKDATE); /*(9)*/
      if (nmchr[i]==1)
         printf("%s",BLANKCHAR);
   }
   if(c=='\n'){
     if ((nmchr[i]==1)||(nmchr[i]==2))
       printf("'"); /*(7)*/
       count=0;
       i=0;
       len=flen[0];
       putchar(c);
       c=getchar();
     }
 }
 return 0;
}

程序概述 当在字段边界时,执行以下操作

  1. 如果它是 char 或 date 类型,则在先前字段的末尾添加一个反引号,或者
  2. 添加一个字段分隔符,或者
  3. 如果它是 char 或 date,则在当前字段的开头添加一个反引号
  4. 修剪空白

识别任何空白字段,并将其替换为预设的默认值

  1. flen[ ] 保存有关字段长度的信息。nmchr[ ] 保存有关字段类型的信息。数字字段分配值 0(零),字符字段分配值 1,日期字段分配值 2。
  2. 如果第一个字段是 char 或 date,则首先添加反引号
  3. 读取的 char 是字段 i+1 的第一个字符。它还没有被添加。如果 nmchr[i] 不是数字,则需要一个尾随的反引号。
  4. 在决定尾随反引号后,添加字段分隔符。不要在最后一个字段的末尾添加 FS。(i<FNM-1)
  5. 如果读取到 "c" 中的 char 是空白,则计数器 "blank" 加 1。如果计数器超过 1,或者任何字段的第一个字符是空白,则程序在不进行任何输出的情况下继续执行。这将修剪空白。当 c 非空白时,计数器重置为 0。条件 "((count==(len-flen[i]))&&(c==' '))" 识别出字段的第一个字符是空白,并阻止输出。
  6. 如果 blank 增加到 flen[i]-1,则表示该字段是空白。dBASE 将未定义的数字字段导出为空白字符串。这会导致数据类型不匹配错误。如果数字字段为空,则将其替换为零。
  7. 遇到回车符时,如果最后一个字段是非数字或日期,则添加反引号。
  8. 日期格式为 yyyymmdd */
  9. dBASE 会将未定义的日期字段导出为空白字段。如果遇到空白日期字段,则需要将其替换为某个日期。