2016-04-18 16 views
5

Posso leggere da un database MSSQL inviando query in python tramite pypyodbc.Qual è la causa di questo errore UnicodeDecodeError con un campo nvarchar che utilizza pyodbc e MSSQL?

La maggior parte dei caratteri unicode viene gestita correttamente, ma ho colpito un determinato carattere che causa un errore.

Il campo in questione è di tipo nvarchar(50) e inizia con questo carattere "" che rende per me un po 'come questo ...

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

Se questo numero è esadecimale 0x100111 allora è il carattere supplementary private use area-b u+100111. Anche se interessante, se è binario 0b100111 allora è un apostrofo, potrebbe essere che la codifica sbagliata è stata utilizzata quando i dati sono stati caricati? Questo campo sta memorizzando parte di un indirizzo postale cinese.

Il messaggio di errore include

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

Qui è in piena ...

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 

Ecco po 'di codice riproduzione minimal ...

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 

Ecco un pezzo di my /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 

Ho anche provato con client charset = UTF-16 e omettendo quella linea tutti insieme.

Ecco il pezzo in questione dal mio /etc/odbc.ini

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

Ecco il pezzo rilevante dal mio /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 

posso risolvere questo problema prendendo i risultati in un blocco try/except, buttare via qualsiasi riga che genera un UnicodeDecodeError, ma c'è una soluzione? Posso buttare via solo il personaggio indecodibile, o c'è un modo per recuperare questa riga senza generare un errore?

Non è inconcepibile che alcuni dati non validi siano finiti nel database.

Ho cercato su Google e controllato le domande relative a questo sito, ma non ho avuto fortuna.

+1

Hai visto http://stackoverflow.com/questions/18357675/unicodedecodeerror-unexpected-end-of-data? – snakecharmerb

+1

Cosa ottieni se si seleziona SELECT master.sys.fn_varbintohexstr (CONVERT (VARBINARY, [address_line_1])) AS foo FROM [address] ... '? Questo dovrebbe mostrarti esattamente ciò che è all'inizio del valore del testo. –

+0

@GordThompson 0x4700520045004e00410044004a00c400520047004100540041004e002000 –

risposta

0

Questo problema è stato alla fine risolto, ho il sospetto che il problema fosse che il testo aveva un carattere di una codifica martellato in un campo con un'altra codifica dichiarata tramite un metodo hacky durante la configurazione del tavolo.

0

ho risolto il problema io stesso utilizzando questo:

conn.setencoding('utf-8') 

immediatamente prima di creare un cursore.

Dove conn è l'oggetto di connessione.

Stavo recuperando decine di milioni di righe con fetchall() e nel mezzo di una transazione che sarebbe estremamente costosa da annullare manualmente, quindi non potevo permettermi di saltare semplicemente quelle non valide.

Fonte dove ho trovato la soluzione: https://github.com/mkleehammer/pyodbc/issues/112#issuecomment-264734456

+0

Interessante, ma MSSQL usa UTF-16LE, che pyodbc usa anche per impostazione predefinita, quindi non mi aspetto di specificare UTF-8 come aiuto in circostanze normali. Tuttavia, se sembra averti aiutato, potrebbe aiutare gli altri che hanno realmente UTF-8 inserito in un database MSSQL. –

+0

Sto usando SQL di Azure, che potrebbe avere qualche stranezza che lo rende diverso? Ad ogni modo, lo trovo bizzarro, dal momento che l'unico modo in cui i dati entrano in quel database è attraverso lo stesso driver codebase/'pyodbc' che stavo usando per recuperare i dati. –