SQLite Output formatieren

Installation und Anwendung von Datenbankschnittstellen wie SQLite, PostgreSQL, MariaDB/MySQL, der DB-API 2.0 und sonstigen Datenbanksystemen.
Antworten
may24x
User
Beiträge: 48
Registriert: Montag 2. September 2013, 06:44

Hi Zusammen,

Ich hab gleich zwei Probleme hier.
Ich lese aus einer SQLite Datenbank den Zeitstempel und die dazugehörige Message aus.
Der Zeitstempel ist ein int und repräsentiert unix-Epoch Zeitstempel
Die Messages sind in Unicode

Erst mal zum Zeitstempel. In Oracle SQL gibt es die Möglichkeit via "SET Output format" das Ausgabeformat aller Zeitstempel von vornherein festzulegen ... gibt's das auch mit SQLite ?
Ich habe das hier mal Versucht, funktioniert aber nicht. Gibt mir NULL zurück:

Code: Alles auswählen

SELECT datetime('timestamp', '%d.%m.%Y %H:%M:%S') FROM messages
Das zweite ist die Ausgabe des Textes. Der ist in Unicode. Nur da werden die Umlaute nicht dargestellt (ä,ü,ö...)

Das einsetzen der 'Magic Line' hat keinen Effekt: # -*- coding: utf-8 -*-
Und auch explizites decodieren funktioniert überhaut nicht.

Code: Alles auswählen

query = "SELECT datetime('timestamp', '%d.%m.%Y %H:%M:%S'), data FROM messages 
                conversation = cur.execute(query).fetchall()
                for row in conversation:
                        print str(row).decode('latin-1','ignore')
Bekomme immer "\xf6" als ö, "\xfc" als ü ...
Benutzeravatar
sparrow
User
Beiträge: 4193
Registriert: Freitag 17. April 2009, 10:28

1. Die Magic-Line gibt nur die Codierung deines Quelltextes an. Nichts sonst.

2. http://stackoverflow.com/questions/3439 ... formatting (erste Google-Treffer)

3. Was genau möchtest du erecihen, wenn du einen string nach latin-1 decodierst? Was kommt denn aus der Datenbank zurück?
BlackJack

@may24x: Was SQLite an Datums- und Zeitfunktionen bietet steht in dessen Dokumentation. Ich schaue da jetzt mal nicht nach weil das IMHO nicht die Aufgabe der Datenbank ist die Ausgabeformatierung zu erledigen und weil das in Python sehr gut mit der `format()`-Methode und `datetime.datetime`-Objekten geht.

Bei dem Text musst Du halt den *Text* ausgeben und nicht eine Zeichenkettenrepräsentation des `row`-Objektes. Das wird in der Regel ein Tupel oder etwas davon abgeleitetes sein und ist nicht dazu gedacht für einen Endbenutzer ausgegeben zu werden. Die Ausgabe ist zur Fehlersuche geeignet(er) und da möchte man gerne sehen was für Werte in Zeichenketten enthalten sind, unabhängig von der Kodierung die das Ausgabe”medium” erwartet (solange das ASCII 1:1 darstellen kann).

Selbst wenn Du den Text mit ``print`` ausgibst, kann es Dir immer noch passieren das es eine Ausnahme gibt, nämlich dann wenn Python nicht ermitteln/erraten kann welche Kodierung für die Ausgabe verwendet werden soll. Wenn man also auf Nummer sicher gehen will muss man diesen Fall berücksichtigen und immer selber kodieren.

Der Kodierungskommentar am Anfang einer Quelltextdatei sagt dem Compiler in welcher Kodierung der Quelltext selbst vorliegt. Das hat keinen Einfluss auf irgendwelche Zeichenketten oder Unicode-Objekte die während des Programmablaufs irgendwo verarbeitet werden.

Wenn Du Quelltext zeigst, dann am besten welchen der auch tatsächlich läuft, denn ``cur.execute(query).fetchall()`` ist falsch. PEP249 sagt zu `Cursor.execute()` nämlich: „Return values are not defined.“
Sirius3
User
Beiträge: 17749
Registriert: Sonntag 21. Oktober 2012, 17:20

@BlackJack: "falsch" ist relativ. SQLite liefert bei execute das/ein Cursor-Objekt zurück.

@may24x: SQLite3 kennt keinen Datentyp TIMESTAMP, sondern einen Mix aus entweder Strings oder Zahlen, die unterschiedlich interpretiert werden können. Am besten nimmst Du gleich die ISO-Format Stringdarstellung, dann kann Python das auch automatisch in Datetime-Objekte umwandeln.

Code: Alles auswählen

db = sqlite3.connect(':memory:', detect_types=sqlite3.PARSE_DECLTYPES)
>>> cursor = db.cursor()
>>> cursor.execute('create table test (a text, b timestamp)')
>>> cursor.execute('instart into test (?, ?)', "abc", datetime.datetime.now())
>>> cursor.execute('select * from test')
>>> cursor.fetchall()                    
[(u'abc', datetime.datetime(2015, 10, 14, 11, 34, 59, 727455))] 
BlackJack

@Sirius3: Ich finde nicht das ”falsch” *so* relativ ist. Sich auf explizit undefiniertes Verhalten zu verlassen ist IMHO nicht richtig.
may24x
User
Beiträge: 48
Registriert: Montag 2. September 2013, 06:44

Ich bin sparrow link gefolgt und habe das Beispiel von Stackoverflow benutzt. Das funktioniert auch ... fast ;)
Das "Jahr" wird mit '-141' ausgegeben. Warscheinlich stehen also nicht Sekunden sonder Millisekunden drinn ...

Nun, was der Cursor zurück gibt ist eine Tulpe. Aber selbst wenn ich eine Zeile aus besagter Tulpe mir via print anzeigen lasse und sie dann mit 'str()' zu einem String caste, werden mit immern noch nicht die Umlaute angezeigt.
Und wie schon erwähnt funktioniert das "decodieren" nach latin-1 nicht
BlackJack

@may24x: Ich glaube nicht das der Cursor Blumen zurück gibt. :-D Das ist ein Tupel und keine Tulpe. ;-)

Ich würde zum kodieren die `encode()`-Methode einem `str()`-Aufruf vorziehen. Und es ist *kodieren*, nicht *de*kodieren. Du hast ja schon Unicode und möchtest für die Ausgabe eine Bytekette haben.
may24x
User
Beiträge: 48
Registriert: Montag 2. September 2013, 06:44

@BlackJack: Ooops, ja da haste recht. Blumen gibt's hier nicht :D

Egal ob mit de oder encodieren es passt trotzdem nicht.
Ich bekomme z.B. Beispiel:

Code: Alles auswählen

(u'01.03.-141 13:07:12', u'Macht mir bestimmt mehr spass als die Ger\xe4te und das bl\xf6de umbauen f\xe4llt weg.')
Und das nach:

Code: Alles auswählen

print str(row).encode('latin-1')
Und noch 'ne Frage zum Zeitstempel. Scheint ja so als ob da nich Sekunden sondern Millisekunden angegeben sind.
Langt es einfach diese Milli-sec's durch 1000 zu teilen ? Bekommt man trotzdem so das richtige Ergebnis/Datum ?

Code: Alles auswählen

SELECT strftime('%d.%m.%Y %H:%M:%S', datetime(timestamp/1000, 'unixepoch')), data FROM messages
BlackJack

@may24x: Das hatten wir doch schon: Tupel sind nicht dazu gedacht die direkt auszugeben, jedenfalls nicht für den Endbenutzer. Die werden so in eine Zeichenkette umgewandelt das sie nur ASCII-Zeichen enthalten, mit Escape-Sequenzen für alles ausserhalb von ASCII, damit man sieht was da tatsächlich enthalten ist, auch ohne wissen zu müssen welche Kodierung für die Anzeige angenommen wird/wurde. Und es macht keinen Sinn auf einer Bytekette (`str`) *en*code aufzurufen, das ist ja bereits eine Bytekette. Du musst das zweite Element aus dem Tupel nehmen, das ist ein Unicode-Objekt, und *das* kodieren, und dann ausgeben.

Du musst Dir a) klarmachen was in jedem Zwischenschritt die jeweiligen Datentypen und Werte sind, und b) Dich mal mit Unicode, Byteketten, de- und enkodieren und den Zusammenhängen auseinandersetzen.
may24x
User
Beiträge: 48
Registriert: Montag 2. September 2013, 06:44

Also gut, das mit dem String direkt aus der Tuple lesen und encoden hat jetzt funktioniert :)
Nur, was mache ich mit "Sonderzeichen" die nicht in einem ISO-Standard sind ?
Z.B: wurde hier in der Konversation Emoji's benutzt. Und die bekomme ich ja nicht richtig angezeigt.
Wie kann man eine neue Codepage erstellen ? Und wie wird die dann verwendet ?
BlackJack

@may24x: Wenn man Sonderzeichen hat die nicht in der Zielkodierung enthalten sind, dann kann man die ignorieren oder durch irgendwas ersetzen, aber halt nicht in der Kodierung kodieren.

Codepage klingt jetzt nach Windows-Konsole. Der muss man halt irgendwie sagen was sie erwarten soll. Das sollte mit dem Programm ``chcp`` gehen.
Sirius3
User
Beiträge: 17749
Registriert: Sonntag 21. Oktober 2012, 17:20

@may24x: Das Datumsformat ist nicht das, was Du denkst, dass es ist, wenn das Jahr bei -141 ist. Und als Output hast Du ja Latin-1 Kodierung gewählt, und da wird eben nicht der gesamte Unicode-Zeichenvorrat abgebildet. Wer erzeugt also die Daten (dem sollte man dann beibringen, Datumsangaben als Strings in die Datenbank zu schreiben) und wer arbeitet mit dem Output weiter (dem sollte man dann UTF-8 beibringen, falls das möglich ist).
may24x
User
Beiträge: 48
Registriert: Montag 2. September 2013, 06:44

@Sirius3: Nein, ist nicht möglich. Ich ahb auf die DB programmierung keinen Einfluß.
Der Zeitstempel liegt im Epoch Format vor. Nur halt in Milli und nicht in Sekunden. Deshalb wird das falsche Jahr ausgegeben.
Benutzeravatar
noisefloor
User
Beiträge: 3856
Registriert: Mittwoch 17. Oktober 2007, 21:40
Wohnort: WW
Kontaktdaten:

Hallo,

was kommt denn raus, wenn du die Umwandlung nicht in der DB machst, sondern den Wert aus der DB als Integer ausliest und dann `datetime.datetime.fromtimestamp(der_wert)` machst?

Gruß, noisefloor
may24x
User
Beiträge: 48
Registriert: Montag 2. September 2013, 06:44

@noisefloor: Da kommt: ValueError: year is out of range
Benutzeravatar
noisefloor
User
Beiträge: 3856
Registriert: Mittwoch 17. Oktober 2007, 21:40
Wohnort: WW
Kontaktdaten:

Hallo,

und was passiert, wenn du den Wert als Integer ausliest, durch 1000 teilst, den erhaltenen Float-Wert in einen Integer "kürzt" und dann `datetime.datetime.fromtimestamp(der_wert)` machst?

Gruß, noisefloor
Antworten