【问题标题】:What's the cause of this UnicodeDecodeError with an nvarchar field using pyodbc and MSSQL?使用 pyodbc 和 SQL 的带有 nvarchar 字段的 UnicodeDecodeError 的原因是什么?
【发布时间】:2016-08-10 04:18:16
【问题描述】:

我可以通过 pypyodbc 在 python 中发送查询来读取 MSSQL 数据库。

大多数 unicode 字符都得到了正确处理,但我遇到了某个导致错误的字符。

相关字段的类型为nvarchar(50),并以该字符“????”开头这对我来说有点像这样......

-----
|100|
|111| 
-----

如果该数字是十六进制 0x100111,那么它就是字符 supplementary private use area-b u+100111。虽然有趣的是,如果它是二进制0b100111,那么它是一个撇号,是不是在上传数据时使用了错误的编码?此字段存储部分中国邮政地址。

错误信息包括

UnicodeDecodeError: 'utf16' codec can't decode bytes in position 0-1: unexpected end of data

这里是完整的...

Traceback (most recent call last):   File "question.py", line 19, in <module>
    results.fetchone()   File "/VIRTUAL_ENVIRONMENT_DIR/local/lib/python2.7/site-packages/pypyodbc.py", line 1869, in fetchone
    value_list.append(buf_cvt_func(from_buffer_u(alloc_buffer)))   File "/VIRTUAL_ENVIRONMENT_DIR/local/lib/python2.7/site-packages/pypyodbc.py", line 482, in UCS_dec
    uchar = buffer.raw[i:i + ucs_length].decode(odbc_decoding)   File "/VIRTUAL_ENVIRONMENT_DIR/lib/python2.7/encodings/utf_16.py", line 16, in decode
    return codecs.utf_16_decode(input, errors, True) UnicodeDecodeError: 'utf16' codec can't decode bytes in position 0-1: unexpected end of data

这里有一些最小的复制代码...

import pypyodbc

connection_string = (
    "DSN=sqlserverdatasource;"
    "UID=REDACTED;"
    "PWD=REDACTED;"
    "DATABASE=obi_load")

connection = pypyodbc.connect(connection_string)

cursor = connection.cursor()

query_sql = (
    "SELECT address_line_1 "
    "FROM address "
    "WHERE address_id == 'REDACTED' ")

with cursor.execute(query_sql) as results:
    row = results.fetchone() # This is the line that raises the error.
    print row

这是我/etc/freetds/freetds.conf的一部分

[global]
;   tds version = 4.2
;   dump file = /tmp/freetds.log
;   debug flags = 0xffff
;   timeout = 10
;   connect timeout = 10
    text size = 64512

[sqlserver]
host = REDACTED
port = 1433
tds version = 7.0
client charset = UTF-8

我也尝试过使用 client charset = UTF-16 并一起省略该行。

这是来自我的/etc/odbc.ini的相关块

[sqlserverdatasource]
Driver = FreeTDS
Description = ODBC connection via FreeTDS
Trace = No
Servername = sqlserver
Database = REDACTED

这是来自我的/etc/odbcinst.ini的相关块

[FreeTDS]
Description = TDS Driver (Sybase/MS SQL)
Driver = /usr/lib/x86_64-linux-gnu/odbc/libtdsodbc.so
Setup = /usr/lib/x86_64-linux-gnu/odbc/libtdsS.so
CPTimeout =
CPReuse =
UsageCount = 1

我可以通过在 try/except 块中获取结果、丢弃任何引发 UnicodeDecodeError 的行来解决此问题,但有解决方案吗?我可以只扔掉不可解码的字符,还是有办法在不引发错误的情况下获取这一行?

有些不良数据最终出现在数据库中并非不可想象。

我在谷歌上搜索并检查了这个网站的相关问题,但没有运气。

【问题讨论】:

  • SELECT master.sys.fn_varbintohexstr(CONVERT(VARBINARY, [address_line_1])) AS foo FROM [address] ... 会得到什么?这应该会准确地显示文本值开头的内容。
  • @GordThompson 0x4700520045004e00410044004a00c400520047004100540041004e002000
  • 有趣。这些绝对是有效的 NVARCHAR 字节(UTF-16LE),但它们代表 'GRENADJÄRGATAN ',这在我看来并不特别中国化。不过,这就是 pyodbc 应该返回的。
  • @GordThompson 我很抱歉,这是上一行的内容。

标签: python sql-server unicode pyodbc pypyodbc


【解决方案1】:

我自己用这个解决了这个问题:

conn.setencoding('utf-8')

就在创建光标之前。

conn 是连接对象。

我正在使用fetchall() 获取数千万行,并且正在处理手动撤消非常昂贵的事务,所以我不能简单地跳过无效的行。

我找到解决方案的来源:https://github.com/mkleehammer/pyodbc/issues/112#issuecomment-264734456

【讨论】:

  • 有趣,但是 MSSQL 使用 UTF-16LE,pyodbc 默认也使用它,所以我不希望指定 UTF-8 在正常情况下会有所帮助。不过,如果它似乎对您有所帮助,那么它可能会帮助其他确实将 UTF-8 填充到 MSSQL 数据库中的人。
  • 我正在使用 Azure SQL,它可能有一些使其与众不同的怪癖?无论哪种方式,我都觉得很奇怪,因为数据进入该数据库的唯一方式是通过我用来获取数据的同一个 codebase/pyodbc 驱动程序。
【解决方案2】:

这个问题最终得到了解决,我怀疑问题是文本有一个编码的字符在设置表时通过一些 hacky 方法将一个编码的字符锤击到另一个声明的编码的字段中。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2015-01-26
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-04-17
    • 1970-01-01
    相关资源
    最近更新 更多