Postgres COPY FROM 文件抛出 unicode 错误,而引用的字符显然不在文件中

Postgres COPY FROM file throwing unicode error while referenced character apparently not in file

首先,感谢 Stack Overflow 上的所有人过去、现在和未来的帮助。你们都把我从灾难中救了出来(包括我自己设计的和其他的),数不清了。

目前的问题是我公司决定从 Microsoft SQL Server 2005 数据库过渡到 PostgreSQL 9.4 的一部分。我们一直在关注 Postgres wiki (https://wiki.postgresql.org/wiki/Microsoft_SQL_Server_to_PostgreSQL_Migration_by_Ian_Harding) 上的注释,这些是我们针对有问题的 table 所遵循的步骤:

  1. 下载 table 数据 [在 Windows 客户端]:

    bcp "Carbon.consensus.observations" out "Carbon.consensus.observations" -k -S [servername] -T -w
    
  2. 复制到 Postgres 服务器 [运行 CentOS 7]

  3. 运行 Python 在 Postgres 服务器上预处理脚本以更改编码和清理:

    import sys
    import os
    import re
    import codecs
    import fileinput
    
    base_path = '/tmp/tables/'
    cleaned_path = '/tmp/tables_processed/'
    files = os.listdir(base_path)
    
    for filename in files:
    
        source_path = base_path + filename
        temp_path = '/tmp/' + filename
        target_path = cleaned_path + filename
    
        BLOCKSIZE = 1048576 # or some other, desired size in bytes
        with open(source_path, 'r') as source_file:
            with open(target_path, 'w') as target_file:
                start = True
                while True:
                    contents = source_file.read(BLOCKSIZE).decode('utf-16le')
                    if not contents:
                        break
                    if start:
                        if contents.startswith(codecs.BOM_UTF8.decode('utf-8')):
                            contents = contents.replace(codecs.BOM_UTF8.decode('utf-8'), ur'')
                    contents = contents.replace(ur'\x80', u'')
                    contents = re.sub(ur'[=11=]0', ur'', contents)
                    contents = re.sub(ur'\r\n', ur'\n', contents)
                    contents = re.sub(ur'\r', ur'\r', contents)
                    target_file.write(contents.encode('utf-8'))
                    start = False
    
        for line in fileinput.input(target_path, inplace=1):
            if '\x80' in line:
                line = line.replace(r'\x80', '')
            sys.stdout.write(line)
    
  4. 执行SQL加载table:

    COPY consensus.observations FROM '/tmp/tables_processed/Carbon.consensus.observations';
    

问题是 COPY 命令失败并出现 unicode 错误:

[2015-02-24 19:52:24] [22021] ERROR: invalid byte sequence for encoding "UTF8": 0x80
Where: COPY observations, line 2622420: "..."

鉴于这很可能是因为 table 中的错误数据(其中也包含合法的非 ASCII 字符),我试图在上下文中找到实际的字节序列,我可以' 在任何地方找到它(sed 查看有问题的行,正则表达式替换字符作为预处理的一部分,等等)。作为参考,这个 grep returns nothing:

cat /tmp/tables_processed/Carbon.consensus.observations | grep --color='auto' -P "[\x80]"

我在追踪这个字节序列在上下文中的位置时做错了什么?

我建议加载 SQL 文件(看起来是 /tmp/tables_processed/Carbon.consensus.observations) 到具有 hex 模式的编辑器中。这应该允许您在上下文中看到它(取决于确切的编辑器)。

gVim(或基于终端的 Vim)是我推荐的一种选择。

例如,如果我在 gVim 中打开一个 SQL copy 包含此内容的文件:

1   1.2
2   1.1
3   3.2

我可以通过命令 %!xxd(在 gVim 或终端 [=79= 中将其转换为十六进制模式]) 或菜单选项 工具 > 转换为十六进制.

产生这个显示:

0000000: 3109 312e 320a 3209 312e 310a 3309 332e  1.1.2.2.1.1.3.3.
0000010: 320a                                     2.

然后您可以 运行 %!xxd -r 将其转换回来,或者菜单选项 工具 > 转换回来

注意:这实际上修改了文件,因此建议对原始文件的副本执行此操作,以防更改以某种方式被写入(你会显式保存缓冲区 Vim).

这样,您可以在左侧看到十六进制序列,在右侧看到它们的 ASCII 等效项。如果您搜索 80,您应该能够在上下文中看到它。但是,对于 gVim,两种模式的行编号将不同,如本例所示。

您找到的第一个 80 可能就是那一行,因为如果有更早的那些,它可能会在那些上失败。

我过去使用过的另一个可能有帮助的工具是图形十六进制编辑器 GHex. Since that's a GNOME project, not quite sure it'll work with CentOS. wxHexEditor 据说可以与 CentOS 一起使用并且从网站上看起来很有前途,尽管我还没有使用它。它被定位为 "hex editor for massive files",因此如果您的 SQL 文件很大,这可能是可行的方法。