SQLAlchemy: Groß- und Kleinschreibung

Installation und Anwendung von Datenbankschnittstellen wie SQLite, PostgreSQL, MariaDB/MySQL, der DB-API 2.0 und sonstigen Datenbanksystemen.
Benutzeravatar
Sophus
User
Beiträge: 1109
Registriert: Freitag 25. April 2014, 12:46
Wohnort: Osnabrück

Ok, ich musste feststellen, dass gerade im Hinblick auf SQLite keine benutzerdefinierte Sortierung unterstützt wird. Und utf8_unicode_ci gilt eben als solches.

Aber hiermit klappt es:

Code: Alles auswählen

class FilmProductionCompany(Base):

    __tablename__ = "film_production_company"

    id = Column(Integer, primary_key = True, unique = True, autoincrement = True)
    production_company = Column(String(100, collation='NOCASE'), nullable = False, unique = True)
Ich hoffe auch, dass dies alles auf UTF8 bleibt, da ich es sonst, wie gewohnt, immer davon ausgehe, man müsse es der Datenbank explizit mitteilen, in welchem Format hier sortiert werden solle.

UPDATE
Bei Umlaute ist alles wieder beim Alten. Ich kann Ä und ä abspeichern. Bei A und a macht die Datenbank ihre erwünschte Arbeit.
Benutzeravatar
__blackjack__
User
Beiträge: 13004
Registriert: Samstag 2. Juni 2018, 10:21
Wohnort: 127.0.0.1
Kontaktdaten:

Ist `production_company` tatsächlich UNIQUE? Keine Chance das in verschiedenen Ländern oder in verschiedenen Zeiträumen unterschiedliche Produktionsfirmen mit dem gleichen Namen existieren können? Oder welche bei denen sich der Name nur durch Gross-/Kleinschreibung unterscheidet?
“Most people find the concept of programming obvious, but the doing impossible.” — Alan J. Perlis
Benutzeravatar
Sophus
User
Beiträge: 1109
Registriert: Freitag 25. April 2014, 12:46
Wohnort: Osnabrück

@__blackjack__: Ja, production_company ist tatsächlich UNIQUE. Hintergrund: In meinem Programm werden einmalig Stammdaten angelegt, darunter fällt auch diese Sparte. Es ist also beabsichtigt. Ich möchte Redundanz vermeiden - daher dieser Weg.

Zurück zum Problem: Ich fand auf der Seite SQLite Collating Sequences folgende Anmerkung:
NOCASE - It is almost same as binary, except the 26 upper case characters of ASCII are folded to their lower case equivalents before the comparison is performed.
Auf gut Deutsch: Sonderzeichen und Umlaute werden nicht berücksichtigt.
__deets__
User
Beiträge: 14494
Registriert: Mittwoch 14. Oktober 2015, 14:29

Das koennte unter Umstaenden mit der entsprechenden Normalform loesen. https://en.wikipedia.org/wiki/Unicode_e ... characters

Dadurch steht da dann wirklich ASCII-A, und danach ein UTF-8-Codepoint der das zu einem Umlaut kombiniert, und der nicht angefasst wird.
Benutzeravatar
Sophus
User
Beiträge: 1109
Registriert: Freitag 25. April 2014, 12:46
Wohnort: Osnabrück

@__deets__: Wie bitte?
__deets__
User
Beiträge: 14494
Registriert: Mittwoch 14. Oktober 2015, 14:29

Hast du den Artikel gelesen? Da wird’s eigentlich erklärt. Du kannst Ä als ein Unicode codepoint speichern. Oder als A+<oben-drauf-mit-tüddelchen>. Das gilt natürlich für alle romanischen Sprachen (wenn das der Oberbegriff ist). Also zb Aufschrift für Accents im französischen, kringel auf dem A und Strich durchs O.

Wenn du also die Daten so normalisiert ablegst, dann sollte die NOCASE klappen.

Alternativ kann man wohl auch SQLite eigene collations beibiegen - https://docs.python.org/3/library/sqlite3.html
Benutzeravatar
Sophus
User
Beiträge: 1109
Registriert: Freitag 25. April 2014, 12:46
Wohnort: Osnabrück

Meine Daten werden als unicode() in die Datenbank gespeichert.
__deets__
User
Beiträge: 14494
Registriert: Mittwoch 14. Oktober 2015, 14:29

Nein. Werden sie nicht. Das geht gar nicht. Sie werden enkodiert, und ziemlich sicher als UTF-8. Aber das ist eine Detail. Du übergibst Unicode Objekte, und das ist ja auch gut so.

Probier es doch einfach mal aus. Speicher die verschiedenen Normierungen ab, und schau, ob das einen Unterschied macht.

Code: Alles auswählen

>>> unicodedata.normalize("NFD", "Ä").encode("unicode_escape")
b'A\\u0308'
>>> unicodedata.normalize("NFC", "Ä").encode("unicode_escape")
b'\\xc4'
Du willst also eine der D (decomposed) Varianten. Und das encode ist nur zur Illustration! Es wird nur das normalize benötigt!

Und um das nochmal ganz klar zu machen: der erste Fall zeigt A gefolgt von combining character with diaresis oder so was ähnlichem. Und darauf sollte die Kleinschreibung anwendbar sein.

Nachtrag: so bekommt man die Namen (Achtung, andere Reihenfolge der Normalisierung):

Code: Alles auswählen

>>> [unicodedata.name(c) for c in unicodedata.normalize("NFC", "Ä")]
['LATIN CAPITAL LETTER A WITH DIAERESIS']
>>> [unicodedata.name(c) for c in unicodedata.normalize("NFD", "Ä")]
['LATIN CAPITAL LETTER A', 'COMBINING DIAERESIS']
Benutzeravatar
Sophus
User
Beiträge: 1109
Registriert: Freitag 25. April 2014, 12:46
Wohnort: Osnabrück

@__deets__: Ja, ich übergebe meiner Datenbank statt QString die unicode()-Objecte. Und zu deinen Beispielen. Ich werde sie einmal ausprobieren. Aber vorweg: Mir war nocht klar, dass Python unicodedata hat. Ich saß hier und dachte mir "Wie kriege ich dir Normalforms" hin? Aber jetzt habe ich was hinzugelernt. Und du verwendest in deinem Beispiel LCs. Für das Abspeichern der einzelnen Daten wäre eine LC zu viel, hm? Wenn ich "Äpfel" abspeichern will, zum Beispiel.
Benutzeravatar
Sophus
User
Beiträge: 1109
Registriert: Freitag 25. April 2014, 12:46
Wohnort: Osnabrück

@: __deets__: Es ist grausam, mit den Zeichenfolgen außerhalb des ASCII zu experimentieren.

Ich habe mir mal ein kleine Beispiel geschrieben. Ein paar Anmerkungen habe ich direkt als Kommentar im Quelltext geschrieben und weiter unten habe ich meine Befürchtung geäußert.

Code: Alles auswählen

from unicodedata import name, normalize

a = u"Ä"

print "Name of NFC", [name(c) for c in normalize("NFC", a)]
print "Name of NFD", [name(c) for c in normalize("NFD", a)]

normal = normalize("NFC", a).encode("unicode_escape")
print type(normal)
print "NFC with unicode_escape", normal
print "NFC with utf-8", normal.encode("utf-8") # Hier wollte ich wieder zurück wandeln, damit ich mein 'Ä' wieder bekomme.

for norm in ('NFC', 'NFKC', 'NFD','NFKD'):
    b = normalize(norm, a).encode("unicode_escape")
    print b, len(b)
Ausgabe:
Name of NFC ['LATIN CAPITAL LETTER A WITH DIAERESIS']
Name of NFD ['LATIN CAPITAL LETTER A', 'COMBINING DIAERESIS']
<type 'str'>
NFC with unicode_escape \xc4
NFC with utf-8 \xc4 # <-- Leider ist es kein 'Ä' geworden, wie es ursprünglich war.
\xc4 4
\xc4 4
A\u0308 7
A\u0308 7
Die For-Schleife zeigt mir, dass NFC' und 'NFKC' im Zuge der Normalisierung 4 Längen haben und das 'NFD und 'NFKD' 7 längen. Und da ich mich frage, welche von den Funktionen nun geeignet sind, bin ich hierauf gestoßen: When to use Unicode Normalization Forms NFC and NFD?. Der Fragensteller hatte wohl auch die Qual der Wahl. Aber was mich sehr beunruhigt, ist, dass der Antworter namens Jukka K. Korpela folgendes erwähnt: [...] normalization may destroy essential information in the data.. Und er führt ein ausführliches Beispiel an. Da ich mit der Datenbank arbeite, ist es für mich wichtig, dass keine Informationen zerstört werden.
Benutzeravatar
Sophus
User
Beiträge: 1109
Registriert: Freitag 25. April 2014, 12:46
Wohnort: Osnabrück

Ok, um mein 'Ä' wieder zu bekommen, musste ich folgenden weg gehen:

Code: Alles auswählen

from unicodedata import name, normalize

a = u"Ä"

normal = normalize("NFC", a).encode("unicode_escape")

print bytes(normal).decode('unicode_escape')

Da normal tatsächlich ein str-Objekt ist, so muss ich wohl dieses Objekt zuerst in bytes() kodieren und dann anschließend mit unicode_escape dekodieren. Vermutlich hatte ich einfach nur Glück?

Wobei mir aufgefallen ist, dass mit NFC sowohl Ä als auch ä abgespeichert werden können. Nur mit NFD ist es nicht möglich Ä und ä zu speichern.
narpfel
User
Beiträge: 643
Registriert: Freitag 20. Oktober 2017, 16:10

Mal ein anderer Ansatz: Die SQLite-Erweiterung ICU kann anscheinend locale- und Unicode-spezifische Collations.

Ohne dass ich jetzt groß Ahnung von SQLite hätte:

Code: Alles auswählen

» wget https://sqlite.org/src/tarball/release/sqlite.tar.gz
[...]
» tar xzf sqlite.tar.gz
» cd sqlite/ext/icu
» gcc -fPIC -Wall -Wextra -pedantic -O2 -shared icu.c $(icu-config --ldflags) -o libIcu.so
icu.c: In Funktion »sqlite3_icu_init«:
icu.c:545:10: Warnung: unverwendeter Parameter »pzErrMsg« [-Wunused-parameter]
   char **pzErrMsg,
   ~~~~~~~^~~~~~~~
» sqlite3
SQLite version 3.23.1 2018-04-10 17:39:29
Enter ".help" for usage hints.
Connected to a transient in-memory database.
Use ".open FILENAME" to reopen on a persistent database.
sqlite> .load ./libIcu.so
sqlite> SELECT icu_load_collation('@colStrength=secondary', 'test');

sqlite> SELECT 'Ä' = 'ä' COLLATE test;
1
sqlite> SELECT 'Ä' = 'a' COLLATE test;
0
sqlite> SELECT 'A' = 'a' COLLATE test;
1
sqlite>
Die Dokumentation dafür ist ein wenig knapp: Hier wird die Erweiterung beschrieben, der richtige Collation-Identifier ergibt sich irgendwo aus der Collation-API von `icu`. Wie du das jetzt per SQLAlchemy der Datenbank beibringst: Keine Ahnung. Müsstest du selber rausfinden.

Edit: In Python 2 sind `str` und `bytes` das selbe.
__deets__
User
Beiträge: 14494
Registriert: Mittwoch 14. Oktober 2015, 14:29

Hast du gelesen was ich schrieb? Das encode IST NICHT NÖTIG UND DIENT NUR DER ILLUSTRATION!

Damit du sehen kannst, das ein um dasselbe Zeichen intern als mehrere codepoints dargestellt werden. Für die Speicherung in der DB oder die Anzeige ändert sich jenseits des normalize-calls nichts. Das gleiche gilt auch für die LCs, die waren rein illustrativer Natur.

Und deine Bedenken bezüglich Datenverlust sind etwas seltsam. Sinn und Zweck der ganzen Übung hier war die verbesserung der Suche alleine das bedeutet ja schon “Datenverlust”, denn aus Python wird zur Suche auch PYTHON. Oder pYthon. Oder PYtHoN. Und es geht eigentlich sogar noch weiter: normalerweise will man auch, das Mähdrescher durch eingabe von Mahdrescher und Maehdrescher gefunden wird. Noch mehr Normalisierung. Und auch bei der Abbildung von ä auf ae verliert man Informationen.

All das macht man, wie du ja auch eingangs schreibst, um die Nummer nützlicher zu machen für den User. EDIT: das macht es allerdings ggf sinnvoll, einen expliziten Index zur Suche anzulegen, mit normalisierten und abgeleiteten Daten. Also in einer extra Spalte.

Und dein ignorieren der Normalisierung (zuzgebendermassen tut das fast jeder) führt ja zu einer ganzen Klasse von Fehlern. Denn ohne Normalisierung kann jemand mit Umlauten auf mehrere Arten arbeiten, und dadurch doppelte Einträge erzeugen, wo nur einer geplant war.
Benutzeravatar
Sophus
User
Beiträge: 1109
Registriert: Freitag 25. April 2014, 12:46
Wohnort: Osnabrück

__deets__ hat geschrieben: Dienstag 5. Juni 2018, 14:12 Hast du gelesen was ich schrieb? Das encode IST NICHT NÖTIG UND DIENT NUR DER ILLUSTRATION!
Mein Fehler, habe ich gänzlich überlesen.
__deets__ hat geschrieben: Dienstag 5. Juni 2018, 14:12 Und deine Bedenken bezüglich Datenverlust sind etwas seltsam. Sinn und Zweck der ganzen Übung hier war die verbesserung der Suche alleine das bedeutet ja schon “Datenverlust”, denn aus Python wird zur Suche auch PYTHON. Oder pYthon. Oder PYtHoN. Und es geht eigentlich sogar noch weiter: normalerweise will man auch, das Mähdrescher durch eingabe von Mahdrescher und Maehdrescher gefunden wird. Noch mehr Normalisierung. Und auch bei der Abbildung von ä auf ae verliert man Informationen.
Das klingt nach einem Google-Niveau. Du kennst es: Da gibt man 'pytohn' und Google fragt dich "Meinen Sie: Python?". Hierbei sprechen wir von einem extrem hohen Such-Algorithmus.
__deets__ hat geschrieben: Dienstag 5. Juni 2018, 14:12 All das macht man, wie du ja auch eingangs schreibst, um die Nummer nützlicher zu machen für den User. EDIT: das macht es allerdings ggf sinnvoll, einen expliziten Index zur Suche anzulegen, mit normalisierten und abgeleiteten Daten. Also in einer extra Spalte.
In meinem derzeitigen Anliegen geht es um die UNIQUE-Felder. Wird in diesem Zuge nicht ein Index zur Suche automatisch angelegt, wenn ein Feld auf UNIQUE = True gesetzt wird? Und was genau meinst du mit abgeleiteten Daten? Etwa Mähdrescher, Maedrescher, Mähdräscher (für jemanden, der nicht weißt wie man das schreibt), Mehdräscher (ebenfalls für Unwissende) und Mahdrescher?
__deets__ hat geschrieben: Dienstag 5. Juni 2018, 14:12 Und dein ignorieren der Normalisierung (zuzgebendermassen tut das fast jeder) führt ja zu einer ganzen Klasse von Fehlern. Denn ohne Normalisierung kann jemand mit Umlauten auf mehrere Arten arbeiten, und dadurch doppelte Einträge erzeugen, wo nur einer geplant war.
Ich habe bis dato nicht einmal gewusst, dass es diese Art von Normalisierung gibt. Hättest du mir das nicht gezeigt, wäre ich nie darauf gekommen. Wie gesagt, es war nur ein Zufall, dass ich dahinter kam, dass Ä und ä oder A und a als UNIQUE-Werte in die Datenbank aufgenommen werden.
Benutzeravatar
Sophus
User
Beiträge: 1109
Registriert: Freitag 25. April 2014, 12:46
Wohnort: Osnabrück

@__deets__; Wenn ich die Daten vor dem Abspeichern mit NFC behandle, dann kann ich trotzdem Ä und ä als UNIQUE-Werte abspeichern. Wenn ich NFD benutze, dann erziele ich den gewünschten erfolgt: Ä und ä werden NICHT als UNIQUE-Werte abgespeichert. Ich habe mir dazu noch einmal deinen Link zu Normalization durchgelesen. Aber irgendwie werde ich daraus nicht wirklich schlau.
__deets__
User
Beiträge: 14494
Registriert: Mittwoch 14. Oktober 2015, 14:29

Ich habe doch bereits erwaehnt, dass du die D-Varianten brauchst. Ich glaube du musst mal ein bisschen genauer lesen, und versuchen nachzuvollziehen, was ich dir hier schreibe. Und was die Ausgaben bedeuten. Ich hab das wirklich alles schon mal erklaert, und woher der Unterschied kommt.
Antworten