Kodowanie znaków w MSSQL

Kiedy pisze się aplikacje oparte na takich frameworkach jak web2py jest wiele rzeczy o które programista nie musi się martwić, szczególnie jeśli korzysta w pełni z wbudowanych mechanizmów, takich jak choćby smartgrid. To one odpowiadają na przykład za wczytanie danych z bazy i ich prezentację. Ostatnio jednak przyszło mi zrobić coś nieco ambitniejszego. Jeden z naszych klientów potrzebuje przesłać dane ze swojej bazy do systemu RaksSQL, a jednym z lepszych na to sposobów jest przygotowanie odpowiedniego pliku XML. I tu zaczynają się problemy z kodowaniem znaków w bazie i pliku jaki chcemy wyprodukować.
Dane wczytane za pomocą ORM w jaki wyposażony jest web2py należy następnie w formie stringów przekazać do struktury opartej na obiekcie ElementTree, bo tak się najłatwiej w Pythonie tworzy struktury XML, przynajmniej moim zdaniem. A nawet jakby ktoś chciał to zrobić w inny sposób kluczowe jest przekazanie danych z bazy w postaci stringów. O bibliotece ElementTree napisze innym razem, tymczasem zajmijmy się problemem kodowania.

Strukturę XML przygotowany w ten czy inny sposób przetwarza się do postaci stringu w wybranym kodowaniu i zapisuje w pliku XML, który ma być plikiem wsadowym, na przykład do wspomnianego to programu RaksSQL. I to własnie ten moment ujawnił pewne problemy z kodowaniem. Pojawił się znany dobrze wielu programistom komunikat o błędzie:

UnicodeDecodeError: 'utf8' codec can't decode byte 0xf1 in position 6: invalid continuation byte

Albo przy próbie użycia innego kodowania:

UnicodeEncodeError: 'ascii' codec can't encode characters in position XXX: ordinal not in range(128)

Pierwsze co można znaleźć w sieci to informacje, że domyślne kodowanie nie jest zgodne z tym co chcemy ponownie zakodować, i trzeba je wymusić:

reload(sys)
sys.setdefaultencoding('utf8')

Niestety w tym wypadku na próżno.

Inną podpowiedzią było by wymusić odkodowanie danych z bazy funkcją unicode. I to jest dobry trop. Tylko aby to zrobić trzeba wiedzieć jak te dane są zakodowane. Można przypuszczać, że dane w bazie MSSQL z definicji są zakodowane za pomocą “jedynie słusznego” z punktu widzenia MS standardu cp1250, a jednak nie. Wykonanie prostego zapytania pomaga ustalić jak jest naprawdę:

SELECT data_type, character_set_catalog, character_set_schema, character_set_name, collation_catalog, collation_schema, collation_name, COUNT(*) count
FROM information_schema.columns
GROUP BY data_type, character_set_catalog, character_set_schema, character_set_name, collation_catalog, collation_schema, collation_name;

Kolumny typu varchar rzeczywiście domyślnie zakodowane są w cp1250, ale kolumny typu nvarchar to już UNICODE. Zatem, aby zrobić porządek w produkowanym pliku tekstowym należy dekodować jedne dane za komendą:

unicode(string_z_bazy, 'cp1250')

a inne komendą:

unicode(string_z_bazy, 'utf-16')

w zależności z jakiego typu kolumną mamy do czynienia.
Dopiero po takim porządkowaniu danych można je w pliku tekstowym zakodować za pomocą wybranego przez siebie kodowania.

Porządek w bazie (jak już się narobiła bałaganu i ma się w tabelach zarówno varchar jak i nvarchar) można zrobić również na poziomie bazy tworząc widoki i używając funkcji CONVERT() na wybranych kolumnach by ujednolicić kodowanie.

Leave a Reply

%d bloggers like this: