Wanneer een spatie geen spatie is
Twee namen kunnen er exact hetzelfde uitzien en toch andere tekens bevatten. Voor een mens is er geen verschil. Voor een database soms wel.
De Van den Bergs
Een paar jaar geleden kreeg ik de vraag waarom de data op een overzicht niet oplopend gesorteerd was, terwijl dat wel zo geïmplementeerd was in de programmatuur. Zo stond “Van den Berg, R.” in het overzicht vóór “Van den Berg, J.”. Wat was er aan de hand?
Ook al zagen de namen er voor een mens exact hetzelfde uit, in de database was er wel degelijk een verschil te bespeuren: de ene spatie bleek de andere niet te zijn. In de ene naam stond een gewone spatie, in de andere een non-breaking space.
Beide tekens nemen op het scherm ruimte in, maar hebben een ander codepoint en een andere UTF-8-representatie. Dat verschil kan de volgorde waarin waarden worden gesorteerd beïnvloeden. De programmatuur sorteerde dus wel degelijk; alleen niet op de tekens die de gebruiker dacht te zien.
Drie ogenschijnlijk lege tekens
Unicode kent verschillende tekens die als een spatie ogen of zelfs helemaal niet zichtbaar zijn. Deze drie komen in gegevensverwerking geregeld voor:
U+0020 SPACE
UTF-8: 20
De gebruikelijke spatie tussen woorden.
U+00A0 NO-BREAK SPACE
UTF-8: C2 A0
Voorkomt dat de tekst op deze plaats over twee regels wordt verdeeld.
U+200B ZERO WIDTH SPACE
UTF-8: E2 80 8B
Is onzichtbaar, maar markeert een mogelijke positie voor een regelafbreking.
De naam zero-width space is enigszins misleidend. U+200B is volgens Unicode geen witruimteteken in de algemene categorie Separator, maar een onzichtbaar opmaakteken in de categorie Format. Voor databases en controles maakt dat onderscheid uit: een functie die “alle whitespace” beweert te verwerken, hoeft U+200B niet mee te nemen.
Er zijn er nog meer
Behalve deze drie kent Unicode onder meer smalle, vaste en cijferbrede spaties. Niet ieder teken is fout: typografie en bepaalde talen hebben er legitieme toepassingen voor. Het risico ontstaat wanneer een systeem impliciet aanneemt dat ieder leeg ogend teken U+0020 is.
| Codepoint | Naam | Voorbeeld van gebruik |
|---|---|---|
U+0020 |
SPACE | Gewone scheiding tussen woorden. |
U+00A0 |
NO-BREAK SPACE | Woorden of eenheden bij elkaar houden. |
U+2007 |
FIGURE SPACE | Een spatie met de breedte van een cijfer. |
U+2009 |
THIN SPACE | Typografische, smalle tussenruimte. |
U+200B |
ZERO WIDTH SPACE | Onzichtbare mogelijkheid voor een regelafbreking. |
U+202F |
NARROW NO-BREAK SPACE | Smalle, niet-afbrekende tussenruimte. |
U+2060 |
WORD JOINER | Onzichtbaar teken dat juist een regelafbreking verhindert. |
U+FEFF |
ZERO WIDTH NO-BREAK SPACE / BOM | Byte Order Mark aan het begin van tekst; midden in tekst een onzichtbaar teken. |
Hoe komen deze tekens in uw database?
Meestal voert niemand bewust U+00A0 of U+200B in. De tekens komen ongemerkt mee bij het kopiëren uit een website, tekstverwerker, spreadsheet of pdf. Ook exports, imports en koppelingen kunnen typografische tekens doorgeven zonder dat de ontvangende applicatie ze zichtbaar maakt.
Dat is op zichzelf correct gedrag: de gegevens worden behouden. Het wordt een datakwaliteitsprobleem wanneer de bestemming een andere betekenis aan de tekens geeft dan de bron, of wanneer voor de betrokken gegevenssoort nooit is vastgelegd wat is toegestaan.
Waar dit in de praktijk mis kan gaan
- Sorteren: ogenschijnlijk gelijke namen komen onverwacht in een andere volgorde terecht.
- 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 ogenschijnlijk gelijke waarden toelaten.
- Validatie: een correct uitziend e-mailadres, rekeningnummer of identificatienummer wordt afgekeurd – of ten onrechte geaccepteerd.
- Lengtecontroles: een onzichtbaar teken telt wel mee als codepoint of byte.
- Hashes en handtekeningen: verschillende bytepatronen produceren verschillende hashes.
Het verraderlijke is dat een medewerker de oorzaak meestal niet kan zien. Kopiëren en plakken neemt het teken bovendien gewoon mee.
Waarom TRIM() niet voldoende is
Wie het probleem met TRIM() denkt op te lossen, kan opnieuw voor een verrassing komen te staan. In PostgreSQL verwijdert trim() standaard gewone spaties aan het begin en einde van een tekst. Een non-breaking space of zero-width space blijft daarbij staan.
SELECT
btrim(U&'\00A0waarde\00A0') = 'waarde'
AS standaard_trim,
btrim(U&'\00A0waarde\00A0', U&'\0020\00A0') = 'waarde'
AS expliciete_trim;
Het resultaat is respectievelijk false en true. In het tweede geval is expliciet opgegeven dat zowel U+0020 als U+00A0 aan de randen mag worden verwijderd. Dat lost uitsluitend dit specifieke randgeval op: tekens midden in de tekst en andere onzichtbare tekens vragen nog steeds om een inhoudelijke regel.
Een klein PostgreSQL-experiment
Onderstaand voorbeeld maakt het verschil zichtbaar. De twee namen ogen gelijk, maar hun UTF-8-bytes en de directe vergelijking verschillen.
SELECT
'Van den Berg' = U&'Van den\00A0Berg' AS gelijk,
encode(convert_to('Van den Berg', 'UTF8'), 'hex')
AS gewone_bytes,
encode(convert_to(U&'Van den\00A0Berg', 'UTF8'), 'hex')
AS nbsp_bytes;
De vergelijking levert false op. In de hexadecimale weergave staat bij de gewone spatie 20 en bij de non-breaking space c2a0.
Ook een onzichtbaar teken telt gewoon mee:
SELECT
char_length('AB') AS zonder_teken,
char_length(U&'A\200BB') AS met_zero_width_space;
Beide waarden zien eruit als AB, maar de lengtes zijn respectievelijk 2 en 3 codepoints.
De juiste regel hangt af van de gegevenssoort
De oplossing is niet om overal alle afwijkende tekens te verwijderen. Daarmee kan betekenis verloren gaan. Bepaal per gegevenssoort welke tekens toegestaan zijn en of een afwijking moet worden geconverteerd, geweigerd of behouden.
| Gegevenssoort | Mogelijke regel | Waarom |
|---|---|---|
| Persoonsnaam | Zet U+00A0 waar passend om naar U+0020; weiger onverwachte onzichtbare opmaaktekens. | Zoeken, sorteren en ontdubbelen vragen om een consistente representatie. |
| E-mailadres of technische identificatie | Weiger onverwachte whitespace en onzichtbare tekens. | Stil verwijderen kan een invoerfout of misleiding verhullen. |
| Getal of bedrag | Parseer volgens een expliciet formaat en sla de waarde op in een numeriek datatype. | Een typografische spatie hoort niet de opgeslagen numerieke waarde te bepalen. |
| Vrije tekst | Behoud legitieme typografie; controleer gericht op ongewenste besturings- en opmaaktekens. | Een brede vervanging kan taal en vormgeving beschadigen. |
De database als laatste vangnet
Normalisatie vindt bij voorkeur plaats wanneer gegevens de organisatie of applicatie binnenkomen. De database kan daarnaast afdwingen dat alleen de afgesproken vorm wordt opgeslagen. Voor gegevenssoorten waarin U+00A0 en U+200B niet zijn toegestaan, kan bijvoorbeeld een herbruikbaar PostgreSQL-DOMAIN worden gebruikt:
CREATE DOMAIN tekst_zonder_verborgen_spaties AS text
CHECK (
VALUE IS NULL
OR (
position(U&'\00A0' in VALUE) = 0
AND position(U&'\200B' in VALUE) = 0
AND position(U&'\2060' in VALUE) = 0
AND position(U&'\FEFF' in VALUE) = 0
)
);
CREATE TABLE persoon
(
persoon_id bigint GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
naam tekst_zonder_verborgen_spaties NOT NULL
);
Deze definitie is nadrukkelijk geen universele Unicode-regel. Zij is een voorbeeld van een beleidskeuze voor gegevenssoorten waarin deze tekens niet thuishoren. De invoerlaag kan een non-breaking space eerst gecontroleerd naar een gewone spatie converteren; de database weigert waarden die desondanks niet aan de afspraak voldoen.
Het DOMAIN maakt de regel herbruikbaar. Dat is Single Point of Maintenance in optima forma: definieer de regel één keer voor de betrokken gegevenssoorten, implementeer haar centraal en pas haar overal hetzelfde toe.
Wat goed datamanagement hier vraagt
- Leg regels vast per data class. Beschrijf toegestane tekens, conversies, validaties en uitzonderingen.
- Controleer aan de grens. Valideer invoer en ontvangen gegevens voordat ermee wordt gezocht, gekoppeld of gehasht.
- Maak afwijkingen zichtbaar. Toon codepoints of hexadecimale bytes in diagnose- en kwaliteitsrapportages.
- Onderzoek bestaande gegevens. Een nieuwe invoerregel corrigeert historische waarden niet automatisch.
- Test de hele keten. Neem ook import, export, API’s, spreadsheets, rapportages en kopieer-en-plakgedrag mee.
Datakwaliteit wordt soms bepaald door tekens die niemand kan zien. Juist daarom moeten de regels wel zichtbaar, expliciet en centraal beheerd zijn.
Bronnen en verdere verdieping
Wat u niet ziet, kan uw data wel beïnvloeden
Wilt u weten waar impliciete invoerregels, verborgen tekens of inconsistente definities de datakwaliteit binnen uw organisatie beïnvloeden? Met de datamanagement quickscan brengt u sterke punten, risico’s en concrete verbeterkansen in beeld.