从pyodbc调用MS SQL Server的编码问题

5 投票
4 回答
13012 浏览
提问于 2025-04-16 04:21

我正在通过 SQL Alchemy 连接到 MS SQL 服务器,使用的是 pyodbc 模块。一切看起来都很正常,直到我开始遇到编码的问题。有些非 ASCII 字符被替换成了 '?'。

数据库的排序规则是 'Latin1_General_CI_AS'(我也检查了具体的字段,它们保持相同的排序规则)。我开始在 create_engine 的调用中选择 'latin1' 编码,这对西欧字符(比如法语或西班牙语的字符,如 é)似乎有效,但对东欧字符就不行了。具体来说,我遇到了字符 ć 的问题。

我尝试选择其他编码,正如 Python 文档所述,特别是微软的编码,比如 cp1250cp1252,但我仍然面临同样的问题。

有没有人知道如何解决这些差异?'Latin1_General_CI_AS' 的排序规则在 Python 编码中有对应吗?

我当前连接的代码如下:

for sqlalchemy import *

def connect():
    return pyodbc.connect('DSN=database;UID=uid;PWD=password')

engine = create_engine('mssql://', creator=connect, encoding='latin1')
connection = engine.connect()

说明和评论:

  • 这个问题发生在从数据库检索信息时。我不需要存储任何东西。
  • 一开始我没有指定编码,结果是每当数据库中遇到非 ASCII 字符时,pyodbc 就会抛出 UnicodeDecodeError。我通过使用 'latin1' 作为编码来纠正这个问题,但这并没有解决所有字符的问题。
  • 我承认服务器并不是使用 latin1,之前的评论不正确。我一直在检查数据库的排序规则和具体字段的排序规则,似乎都是 'Latin1_General_CI_AS',那么 ć 是怎么存储的呢?也许我对排序规则的理解不够准确。
  • 我稍微修改了一下问题,具体来说,我尝试了比 latin1 更多的编码,包括 cp1250cp1252(根据 msdn,这似乎是 'Latin1_General_CI_AS' 使用的编码)。

更新:

好的,按照这些步骤,我发现数据库使用的编码似乎是 cp1252:http://bytes.com/topic/sql-server/answers/142972-characters-encoding。不过,这似乎是一个错误的假设,答案中也反映了这一点。

更新2:
无论如何,在正确配置 ODBC 驱动程序后,我在 Python 代码中不需要指定编码。

4 个回答

1

试着用 pyodbc.connect() 这个方法连接数据库,并设置参数 convert_unicode=True,比如在使用 sqlalchemy 的时候:

engine = create_engine('mssql://yourdb', connect_args={'convert_unicode': True})

这样做可以确保你得到的所有结果(不仅仅是 nvarchar 等类型的结果)都是 Unicode 格式的,这些结果会根据数据库使用的编码正确转换过来。

至于往数据库写数据,记得始终使用 Unicode。如果我没记错的话(稍后会确认),pyodbc 会确保这些数据也能正确写入数据库。

当然,如果数据库使用的编码不支持你想写入的字符,你还是会遇到错误:如果你希望列能够支持任何字符,你也需要在数据库中使用 Unicode 列。

2

原评论变成的回答:

cp1250和cp1252并不是“latin1编码”。排序规则和编码是两回事。关于你的评论:谁说“服务器是用latin1编码的”?如果服务器要求所有的输入/输出都用latin1编码(我对此表示怀疑),那么你就无法把一些东欧字符存入数据库(包括俄语、中文、希腊语等等)。

更新:

你需要关注的不仅仅是排序规则。"""msdn.microsoft.com/en-us/library/ms174596(v=SQL.90).aspx提到,对于Latin1_General_CI_AS使用的编码是cp1252"""这句话是错误的。这个表提供了每个地区的LCID(地区标识符)、默认排序规则和代码页。没错,排序规则“Latin1_General_CI_AS”确实和cp1252代码页在几个地区是相关的。但是在两个地区(亚美尼亚和格鲁吉亚),它却和“Unicode”代码页有关联(!!!)。

简单来说,你需要找出数据库使用的代码页

尝试从数据库中提取数据时,不要指定任何编码。不要去猜测你的控制台可能使用的编码,这样只会增加混淆。相反,使用print repr(data)。然后把你在预期非Latin1字符的地方从repr()得到的结果反馈回来。

2

你应该停止使用代码页,改用Unicode。这是解决这类问题的唯一方法。

撰写回答