Wanneer een ‘é’ geen ‘é’ is
Twee namen kunnen er exact hetzelfde uitzien en toch uit verschillende Unicode-tekens bestaan. Voor een mens is er geen verschil. Voor een database soms wel.
De dubbelganger in uw database
Stel dat twee klanten José heten. De namen zien er op het scherm volkomen identiek uit. Toch vindt een zoekopdracht maar één van beide records, beschouwt een koppeling de waarden als verschillend of laat een unieke sleutel beide namen toe.
De oorzaak kan in één letter zitten. De letter é kan in Unicode namelijk op meer dan één manier worden opgebouwd. Dat is geen corrupte data en evenmin een fout in Unicode. Het zijn verschillende technische representaties van hetzelfde abstracte teken.
Voor een databaseman is dat fascinerend: wat voor een gebruiker aantoonbaar hetzelfde is, kan op het niveau van codepoints en bytes aantoonbaar verschillend zijn.
Eén letter, twee representaties
Unicode kent voor veel letters met accenten zowel een samengestelde vorm als een ontlede vorm. Bij de samengestelde vorm is de letter één codepoint. Bij de ontlede vorm worden de gewone letter en het accent afzonderlijk opgeslagen.
U+00E9
UTF-8: C3 A9
Één codepoint.
U+0065 + U+0301
UTF-8: 65 CC 81
Twee codepoints: een e plus een gecombineerd accent aigu.
Een correct lettertype toont beide reeksen hetzelfde. De onderliggende bytes en het aantal codepoints verschillen echter. Een lettertelling kan daardoor 1 of 2 opleveren, terwijl de gebruiker in beide gevallen maar één letter ziet.
Gelijkwaardig is niet hetzelfde als identiek
Unicode noemt deze twee representaties canoniek gelijkwaardig. Ze stellen hetzelfde abstracte teken voor en horen dezelfde visuele betekenis en werking te hebben. Canoniek gelijkwaardig betekent echter niet dat de oorspronkelijke tekenreeksen binair identiek zijn.
Of een systeem de waarden als gelijk behandelt, hangt af van de gebruikte software, vergelijkingsregels en collatie. Wie alleen op de oorspronkelijke bytes vergelijkt, krijgt een verschil. Daardoor kan hetzelfde gegevenspaar zich in het ene systeem als gelijk gedragen en na overdracht naar een ander systeem ineens als verschillend.
Waar dit in de praktijk mis kan gaan
- Zoeken en koppelen: een zoekopdracht, join of lookup vindt niet alle visueel gelijke waarden.
- Ontdubbelen: twee records blijven bestaan omdat de sleutelwaarden technisch verschillen.
- Unieke waarden: een unieke index kan beide representaties toelaten wanneer de gebruikte vergelijking ze niet gelijk behandelt.
- Lengtecontroles: een veld bevat visueel het juiste aantal letters, maar een controle op codepoints of bytes geeft een andere lengte.
- Hashes en handtekeningen: verschillende bytepatronen produceren verschillende hashes, ook al ziet de invoer er gelijk uit.
- Gegevensuitwisseling: invoer uit toetsenborden, documenten, API’s en andere systemen kan in uiteenlopende normalisatievormen binnenkomen.
Het verraderlijke is dat een medewerker het probleem meestal niet kan zien. Kopiëren en plakken neemt de verborgen representatie gewoon mee.
Bestandsnamen tussen macOS, Windows en Linux
Bij bestandsnamen wordt het verschil tussen samengestelde en ontlede tekens extra zichtbaar. Vaak wordt dit samengevat als: “macOS gebruikt NFD en Windows en Linux gebruiken NFC.” Dat is begrijpelijk, maar technisch te stellig.
Het oudere bestandssysteem HFS+ van Apple ontleedde Unicode-tekens volgens eigen, op Unicode 3.2 gebaseerde regels. Dat lijkt sterk op NFD, maar is er niet volledig aan gelijk. Het modernere APFS is normalisatiebewust: het kan canoniek gelijkwaardige bestandsnamen bij vergelijking als gelijk behandelen. Windows met NTFS en Linux met bijvoorbeeld ext4 leggen de aangeleverde tekenreeks in beginsel vast zonder alle bestandsnamen standaard naar NFC om te zetten.
Het probleem ontstaat dus niet simpelweg doordat drie besturingssystemen elk één vaste vorm gebruiken. Het ontstaat wanneer een bestandsnaam via een applicatie, synchronisatiedienst, archief of versiebeheersysteem van de ene omgeving naar de andere gaat en ergens op codepoints of bytes wordt vergeleken. Twee namen kunnen dan visueel gelijk zijn, maar toch een andere hash opleveren, als een naamswijziging worden gezien of naast elkaar terechtkomen. Git kent voor macOS daarom bijvoorbeeld de instelling core.precomposeUnicode.
Wie bestanden tussen platformen uitwisselt, doet er goed aan de naamgeving in de hele keten te testen en op de systeemgrenzen één gedocumenteerde normalisatiestrategie toe te passen. Alleen vertrouwen op wat Verkenner, Finder of een terminal toont, is niet voldoende.
Unicode-normalisatie brengt orde aan
Unicode-normalisatie zet gelijkwaardige tekenreeksen om naar een afgesproken standaardvorm. Daardoor krijgen canoniek gelijkwaardige teksten na normalisatie dezelfde binaire representatie.
| Vorm | Werking | Aandachtspunt |
|---|---|---|
| NFC | Canonieke compositie: combineert tekens waar mogelijk. | Veelgebruikt als compacte standaardvorm voor tekst. |
| NFD | Canonieke decompositie: splitst samengestelde tekens op. | Canoniek gelijkwaardig aan NFC, maar doorgaans langer. |
| NFKC | Compatibiliteitscompositie: maakt ook bepaalde typografische varianten gelijk. | Kan betekenisvolle vormverschillen verwijderen; niet blind toepassen. |
| NFKD | Compatibiliteitsdecompositie. | Heeft hetzelfde betekenisrisico als NFKC en levert ontlede tekst op. |
Voor algemene tekstvelden is NFC vaak een logisch uitgangspunt. Welke vorm geschikt is, blijft echter een inhoudelijke keuze per gegevenssoort. Vooral de compatibiliteitsvormen NFKC en NFKD kunnen onderscheid verwijderen dat de invoerder bewust heeft aangebracht.
Wanneer NFD wél een goede keuze kan zijn
NFC is vaak praktisch voor opslag en uitwisseling, maar NFD heeft wel degelijk nuttige toepassingen. De ontlede vorm maakt de opbouw uit een basisteken en één of meer gecombineerde tekens expliciet. Dat kan handig zijn wanneer juist die opbouw moet worden onderzocht of verwerkt.
- Taalkundige analyse: software kan basistekens en diakritische tekens afzonderlijk onderzoeken, vergelijken of transformeren.
- Zoek- en transliteratieprocessen: NFD kan een bruikbare tussenstap zijn bij bewust accentongevoelig zoeken of translitereren, mits daarna expliciete en taalbewuste regels worden toegepast.
- Compatibiliteit: een bestaande applicatie, gegevensuitwisseling of historisch HFS+-proces kan een ontlede representatie voorschrijven of verwachten.
- Diagnose en kwaliteitscontrole: decompositie maakt verborgen verschillen in de samenstelling en volgorde van gecombineerde tekens beter analyseerbaar.
NFD is daarmee vooral zinvol als bewuste verwerkingsvorm of wanneer een specificatie haar vereist. Zij is niet automatisch de beste standaard voor bestandsnamen of algemene gegevensopslag: ontlede tekst gebruikt vaak meer codepoints en kan daardoor andere lengtecontroles of limieten raken. Bovendien verwijdert NFD geen accenten; het afzonderlijk verwijderen daarvan is een aanvullende, potentieel betekenisveranderende bewerking.
Een klein PostgreSQL-experiment
PostgreSQL beschikt over functies om Unicode-tekst te controleren en te normaliseren. Onderstaand voorbeeld laat zien dat de twee schrijfwijzen een verschillende lengte hebben, maar na normalisatie naar NFC gelijk worden.
SELECT
char_length(U&'\00E9') AS samengesteld,
char_length(U&'\0065\0301') AS ontleed,
normalize(U&'\0065\0301', NFC) = U&'\00E9'
AS gelijk_na_normalisatie;
Het resultaat is respectievelijk 1, 2 en true. PostgreSQL ondersteunt daarnaast IS NORMALIZED om te controleren of tekst al aan een gekozen normalisatievorm voldoet.
NFC afdwingen met een CHECK-constraint
Wie uitsluitend NFC-genormaliseerde waarden wil opslaan, kan dat rechtstreeks bij de kolomdefinitie afdwingen met een CHECK-constraint:
CREATE TABLE persoon
(
persoon_id bigint GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
naam text
CONSTRAINT persoon_naam_nfc_ck
CHECK (naam IS NFC NORMALIZED)
);
Deze constraint normaliseert de invoer niet zelf. Een waarde die niet in NFC staat, wordt geweigerd. Daarmee vormt de database een laatste vangnet wanneer normalisatie in een applicatie, interface of importproces is overgeslagen.
De herbruikbare oplossing: een DOMAIN
Wanneer dezelfde regel voor meerdere kolommen geldt, is een PostgreSQL-DOMAIN eleganter. Het domein koppelt de semantische regel één keer aan een herbruikbaar datatype:
CREATE DOMAIN tekst_nfc AS text
CHECK (
VALUE IS NULL
OR VALUE IS NFC NORMALIZED
);
CREATE TABLE persoon
(
persoon_id bigint GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
naam tekst_nfc NOT NULL,
woonplaats tekst_nfc
);
Dit is Single Point of Maintenance in optima forma: de normalisatieregel wordt één keer gedefinieerd en geldt vervolgens automatisch voor iedere kolom van het type tekst_nfc. Een wijziging of uitbreiding van de centrale definitie hoeft daardoor niet afzonderlijk in iedere tabel te worden ontworpen en onderhouden. Of een waarde verplicht is, blijft bewust een eigenschap van de kolom; daarom staat NOT NULL bij naam en niet in het domein zelf.
Normaliseren is niet hetzelfde als accenten verwijderen
Unicode-normalisatie maakt de twee representaties van é consistent. Zij verandert een é niet automatisch in een gewone e. Ook hoofdletters, kleine letters en gelijkende tekens uit verschillende schriften worden niet zonder meer gelijkgemaakt.
Dat onderscheid is belangrijk. Normalisatie zorgt voor een consistente technische representatie. Accentongevoelig zoeken, casefolding en detectie van visueel gelijkende tekens zijn andere vraagstukken, met eigen regels en risico’s.
Wat goed datamanagement hier vraagt
De oplossing is niet om op willekeurige plaatsen in applicaties een functie aan te roepen. Een organisatie moet centraal bepalen welke normalisatie voor welke gegevenssoort geldt en op welk moment zij wordt toegepast.
- Leg de regel vast per data class. Bepaal voor namen, adressen, zoekvelden en identificerende waarden expliciet welke normalisatievorm geldt.
- Normaliseer aan de grens. Pas de regel toe bij invoer of gegevensontvangst, voordat wordt vergeleken, ontdubbeld, geïndexeerd of gehasht.
- Gebruik één implementatie. Voorkom dat iedere applicatie, interface of migratie haar eigen interpretatie krijgt.
- Onderzoek bestaande gegevens. Een nieuwe invoerregel corrigeert historische varianten niet automatisch.
- Test de hele keten. Controleer niet alleen opslag, maar ook zoeken, export, import, rapportage en gegevensuitwisseling.
Dit is voor mij een schoolvoorbeeld van het Single Point of Maintenance-principe: definieer de regel één keer, implementeer haar centraal en gebruik haar overal op dezelfde manier.
De overeenkomst met Punycode
Bij Punycode kan een domeinnaam er voor de gebruiker anders uitzien dan de technische waarde die in een systeem wordt opgeslagen. Bij Unicode-normalisatie gebeurt bijna het omgekeerde: twee waarden zien er voor de gebruiker hetzelfde uit, terwijl de technische representatie verschilt.
In beide gevallen geldt dezelfde les: datakwaliteit begint niet bij wat u op het scherm ziet, maar bij inzicht in betekenis, representatie en gebruik door de hele gegevensketen.
Bronnen en verdere verdieping
- Unicode Consortium – Normalization FAQ
- Unicode Standard Annex #15 – Unicode Normalization Forms
- W3C – Character Model for the World Wide Web: String Matching
- PostgreSQL – String Functions and Operators
- PostgreSQL – Collation Support
- Apple – HFS Plus Volume Format
- Apple – Apple File System FAQ
- Microsoft – Maximum Path Length Limitation
- Linux Kernel – ext4 General Information
- Git – Configuration documentation
Wat u ziet, is niet altijd wat u opslaat
Wilt u weten waar verborgen verschillen, onduidelijke definities of inconsistente invoerregels de datakwaliteit binnen uw organisatie beïnvloeden? Met de datamanagement quickscan brengt u sterke punten, risico’s en concrete verbeterkansen in beeld.