Das Symptom
Die Mitgliederliste ist alphabetisch sortiert, aber Frau Öztürk steht hinter
Herrn Zylinder, ganz unten, hinter allem. Oder die Liste zerfällt in zwei Hälften: erst
alle Namen mit großem Anfangsbuchstaben, dann alle mit kleinem. Oder – der teure
Fall – jemand sucht einen Kunden, findet ihn nicht, legt ihn neu an, und ab
diesem Tag existiert derselbe Mensch zweimal im System.
Besonders verwirrend ist die Version, in der beide Beteiligten recht haben: Der
Bericht vom Server sortiert anders als derselbe Bericht vom Arbeitsplatzrechner,
mit denselben Daten und derselben Software.
Die „Layer 8“-Wahrheit
„Alphabetisch“ ist keine Eigenschaft der Buchstaben, sondern eine Regel, die
jemand aufgestellt hat – und es gibt mehrere. Der Unicode-Standard für Kollation
sagt es im ersten Absatz: Deutsche, Franzosen und Schweden sortieren dieselben
Zeichen unterschiedlich. Ein ä steht im Schwedischen hinter dem z und im Deutschen
neben dem a. Sogar innerhalb einer Sprache gibt es mehr als eine richtige Antwort:
Dasselbe Dokument führt deutsche Wörterbuch- und deutsche Telefonbuchordnung als
zwei verschiedene Reihenfolgen auf.
Wenn die Umlaute ganz am Ende landen, ist gar keine Sprache im Spiel: Dann wird
nach Zahlenwerten sortiert, und in UTF-8 liegen alle Nicht-ASCII-Zeichen hinter
allen ASCII-Zeichen. Die Handbuchseite von sort sagt genau das, mit drei
Sternchen davor. Diese Reihenfolge ist schnell, stabil und für Maschinen ideal –
und für jede Liste, die ein Mensch liest, unbrauchbar.
Der Layer-8-Anteil ist der Reflex, das für einen Schönheitsfehler zu halten. Ist
es nicht. Der Unicode-Standard beschreibt selbst den Fall, auf den es hinausläuft:
Ein Nutzer sucht einen Namen dort, wo er ihn erwartet, findet ihn nicht und hält
ihn für nicht vorhanden – bei einer Datenbankabfrage merkt er nicht einmal, welche
Datensätze fehlen. Aus einer Sortierfrage wird so eine Doppelanlage, eine
verschickte Mahnung an den falschen Kunden oder ein Bericht mit Lücke. Deshalb
gehört die Sortierregel dorthin, wo sortiert wird, ausdrücklich hingeschrieben,
statt sie sich von der Umgebung des jeweiligen Rechners geben zu lassen.
Die echte, verifizierte Lösung
1000 % faktenbasiert. Jeder Befehl ist real, jeder Pfad existiert. Schritt für Schritt:
1.Zuerst feststellen, nach welcher Regel gerade sortiert wird
Die Reihenfolge kommt nicht aus dem Programm, sondern aus der Umgebung. Ein Blick auf die Locale-Variablen sagt, welche Sprache gerade gilt – und erklärt in den meisten Fällen sofort, warum Server und Arbeitsplatz zwei verschiedene Listen erzeugen.
locale | grep -E 'LC_ALL|LC_COLLATE|LANG='
# LANG=de_DE.UTF-8
# LC_COLLATE="de_DE.UTF-8"
# LC_ALL=
2.Den Unterschied an derselben Datei sichtbar machen
Drei Aufrufe, eine Datei, drei Reihenfolgen. Das ist der Beweis, den man in jeder Diskussion braucht: Nicht die Daten sind unterschiedlich, sondern die Regel. Unter C landen die Umlaute am Ende, weil dort nach Zahlenwerten sortiert wird; unter deutscher Locale stehen sie beim Grundbuchstaben; unter schwedischer hinter dem Z, wie es UTS #10 beschreibt.
printf 'Zylinder\nAerzte\nÄrzte\nOrder\nÖrtlich\nZuse\n' > /tmp/n.txt
LC_ALL=C sort /tmp/n.txt
# Aerzte / Order / Zuse / Zylinder / Ärzte / Örtlich
LC_ALL=de_DE.UTF-8 sort /tmp/n.txt
# Aerzte / Ärzte / Order / Örtlich / Zuse / Zylinder
LC_ALL=sv_SE.UTF-8 sort /tmp/n.txt
# Aerzte / Order / Zuse / Zylinder / Ärzte / Örtlich
3.Die Groß- und Kleinschreibung als zweiten Effekt prüfen
Dieselbe Umschaltung verändert auch, wie Groß- und Kleinschreibung behandelt wird. Nach Zahlenwerten kommen erst alle großen, dann alle kleinen Buchstaben – eine Namensliste zerfällt damit in zwei Blöcke. Unter einer Sprach-Locale stehen die Wörter dort, wo ein Mensch sie sucht.
printf 'apfel\nApfel\nBirne\nbanane\n' > /tmp/gk.txt
LC_ALL=C sort /tmp/gk.txt
# Apfel / Birne / apfel / banane
LC_ALL=de_DE.UTF-8 sort /tmp/gk.txt
# apfel / Apfel / banane / Birne
4.Prüfen, ob die Programmiersprache überhaupt sprachbewusst sortiert
Viele eingebaute Sortierfunktionen sortieren nach Codepunkten, nicht nach Sprache – und liefern damit dasselbe Ergebnis wie die Byte-Sortierung. Das ist kein Fehler der Sprache, sondern eine Voreinstellung, die man kennen muss. Für Listen, die Menschen lesen, braucht man ausdrücklich eine sprachbewusste Sortierung.
python3 -c "print(sorted(['Zylinder','Ärzte','Aerzte','Örtlich','Order']))"
# ['Aerzte', 'Order', 'Zylinder', 'Ärzte', 'Örtlich']
5.Die Kollation dort festlegen, wo sortiert wird
Verlass dich nie darauf, dass der Server dieselbe Locale hat wie dein Rechner. In PostgreSQL wird die Kollation an der Spalte oder direkt an der Abfrage gesetzt, damit die Reihenfolge nicht mit der Umgebung wandert. Wichtig ist die Absicht: Eine Liste für Menschen bekommt eine Sprachkollation, ein technischer Schlüssel bekommt die stabile Byte-Kollation.
SELECT name FROM kunden ORDER BY name COLLATE "de-DE-x-icu";
SELECT collname FROM pg_collation WHERE collname LIKE 'de%';
6.Sortierung und Suche nicht durcheinanderbringen
Zwei getrennte Fragen: Wo steht der Eintrag in der Liste, und findet ihn die Suche? Die zweite ist die wichtigere, weil ein nicht gefundener Datensatz aussieht wie ein nicht vorhandener. Für die Suche gilt deshalb: Bei Umlauten und Akzenten nicht auf eine ASCII-Faltung wie SQLites NOCASE verlassen, sondern entweder eine passende Kollation oder eine ausdrücklich normalisierte Suchspalte verwenden.
7.Beim Serverumzug die Locale mitnehmen und die Indizes neu bauen
Eine Datenbank, die auf ein System mit anderer Locale wandert, sortiert danach anders – und Indizes, die nach der alten Regel gebaut wurden, passen dann nicht mehr zu den Vergleichen, die nach der neuen Regel gestellt werden. Deshalb gehört die Locale in die Umzugscheckliste, und nach dem Umzug werden die Indizes über Textspalten neu aufgebaut. Das ist billiger als die Suche nach einem Datensatz, den es angeblich nicht gibt.
8.Mit Namen testen, die nicht aus dem Duden stammen
In den Testbestand gehören Umlaute, ein scharfes S, ein französischer Akzent, ein skandinavisches ø und ein slowakisches ch. Die Handbuchseite zu den Locales nennt gleich ein Beispiel, das man sonst übersieht: Unter LC_COLLATE werde das deutsche scharfe S wie ss sortiert. Wer nur Meier und Schmidt testet, sieht von alldem nichts – und liefert eine Liste aus, die für jeden zweiten Nachnamen falsch ist.