pandas 不允许从数据类型varchar(max)到varbinary(max)的隐式转换

hi3rlvi2  于 2023-04-28  发布在  其他
关注(0)|答案(1)|浏览(296)

我有一个名为appendTable()的方法,它本质上接受表的名称和columns=data作为关键字参数。我接受关键字参数并使用它来构建DataFrame对象,然后使用dataframe.to_sql()方法将行追加到数据库表中。如下所示:

def appendTable(self, tableName, **kwargs):
        dataFrame = pd.DataFrame(data=[kwargs])
        print(dataFrame)

        with self.connection_handling():
            with threadLock:
                dataFrame.to_sql(tableName, con=self.connection.dbEngine, schema="dbo", index=False, if_exists='append')

例如,我会使用这样的方法:

self.appendTable(tableName="Notebook", FormID=ID, CompressedNotes=notebook)

我的表设计是在Microsoft SQL Server中,看起来像这样:

NotebookID       | int            | primary auto-incrementing key
FormID           | int            | foreign key to a form table
Notes            | varchar(MAX)   | allow-nulls : True
CompressedNotes  | varbinary(MAX) | allow-nulls : True

我传递的数据来自PyQt 5 TextEdit(用作Notebook),它将文本/图像作为HTML代码提供给我,然后我编码数据并使用zlib.compress()压缩它,如下所示:

notebook_html = self.noteBookTextEdit.toHtml()
notebookData  = zlib.compress(notebook_html.encode())

我打印了数据类型和数据框,发现它一直是预期的数据类型。我还添加了一个数据库表/服务器,我已经使用了多年。

Notebook data type: <class 'bytes'>
        FormID                   CompressedNotes
0          163  b'x\x9c\x03\x00\x00\x00\x00\x01'

生成的SQL看起来像这样:

SQL: INSERT INTO dbo.[Notebook] ([FormID], [CompressedNotes]) VALUES (?, ?)
parameters: ('163', b'x\x9c\x03\x00\x00\x00\x00\x01')

最近,当我传递一个VARBINARY(MAX)列的二进制信息时,出现了这个错误:

Could not execute cursor!
    Reason: (pyodbc.ProgrammingError) ('42000', '[42000] [Microsoft][ODBC Driver 17 for SQL Server][SQL Server]Implicit conversion from data type varchar(max) to varbinary(max) is not allowed. Use the CONVERT function to run this query. (257) (SQLExecDirectW); 
    [42000] [Microsoft][ODBC Driver 17 for SQL Server][SQL Server]Statement(s) could not be prepared. (8180)')
    [SQL: INSERT INTO dbo.[Notebook] ([FormID], [CompressedNotes]) VALUES (?, ?)]
    [parameters: ('163', b'x\x9c\x03\x00\x00\x00\x00\x01')]
    (Background on this error at: https://sqlalche.me/e/20/f405)

自从这个问题开始以来,我所做的唯一区别是我通过QThread()而不是使用threading.Thread()运行appendTable()方法,因为我想访问一些signalsslots。但我仍然使用线程锁来确保多个线程不会同时尝试使用我的数据库引擎。我已经这样做了很长一段时间,但我不确定线程锁是否适用于QThreads(我认为它可以)。
更新:
当我使用pyodbc游标自己编写SQL语句而不是使用pandas.DataFrame.to_sql()方法来生成看起来相同的语句时,它都可以工作。我传递了具有相同数据类型的完全相同的变量,并且它可以工作,即使没有使用错误解释的CONVERT方法。

cursor.execute('INSERT INTO Notebook (FormID, CompressedNotes) VALUES (?, ?)', (FormID, notebook))

pandas.DataFrame()将我的class <bytes>对象转换成其他对象,还是我只是缺少了一些东西?我使用的是python 3.11.2pandas 1.5.3。尽管在将任何东西放入QThread()之前,它以前可以使用这些版本。

z8dt9xmd

z8dt9xmd1#

正如Gord Thompson的评论所建议的,我更改了appendTable方法来检查任何二进制字段,如果我们有二进制字段,则将该信息传递给df.to_sql()方法。
所以现在appendTable()方法看起来像这样(我在一些print语句中留下了,我用来仔细检查值是否如我所期望的那样):

def appendTable(self, tableName, **kwargs):
        print("Checking Binary")
        binary_data = {}
        for keyword, value in kwargs.items():
            print(f"{keyword} Data Type: {type(value)}")
            if isinstance(value, bytes) or isinstance(value, bytearray):
                binary_data[keyword] = LargeBinary

        dataFrame = pd.DataFrame(data=[kwargs])
        print(dataFrame)

        with self.connection_handling():
            with threadLock:
                print(binary_data)
                if binary_data:
                    dataFrame.to_sql(tableName, con=self.connection.dbEngine, schema="dbo", index=False, if_exists='append', dtype=binary_data)
                else:
                    dataFrame.to_sql(tableName, con=self.connection.dbEngine, schema="dbo", index=False, if_exists='append')

下面是print语句的输出:

Checking Binary

FormId Data Type: <class 'int'>

CompressedNotes Data Type: <class 'bytes'>

         FormId                                    CompressedNotes
0           168  b'x\x9cUP\xb1N\xc30\x14\xdc\xf9\x8a\x87\x97.8\...

{'CompressedNotes': <class 'sqlalchemy.sql.sqltypes.LargeBinary'>}

并且没有写入表的错误,我能够按预期的那样将数据加载回来。

相关问题