Ebook · Hoofdstuk 6 van 10
Data Kwaliteit en Governance
Vertrouwen wordt langzaam verdiend en in één avond verloren. Dit hoofdstuk gaat over de discipline om dat te voorkomen.
Waarom kwaliteit niet "later" kan
Een datawarehouse waarin niemand vertrouwen heeft is een dure database die niemand gebruikt. Eén verkeerd dashboard in een bestuursvergadering kost meer geloofwaardigheid dan tien correcte rapporten verdienen. Kwaliteit en governance zijn geen "luxe" of "fase 2" — ze zijn de fundering.
De ware kosten van slechte datakwaliteit
IBM publiceerde de inmiddels vaak geciteerde schatting dat slechte datakwaliteit alleen al de Amerikaanse economie 3,1 biljoen dollar per jaar kost. Voor een individuele organisatie speelt het op drie niveaus:
- Operationeel — verkeerde voorraadbeslissingen, dubbele klantmailings, foute facturen, mislukte migraties.
- Strategisch — directie neemt beslissingen op basis van fout dashboards. Een investering van miljoenen op een rapport dat 15% afwijkt is duur.
- Reputationeel — als finance en sales verschillende omzetcijfers presenteren, verliest het hele team aan vertrouwen, ongeacht wie er gelijk heeft.
De ironie: kwaliteit voorkomen kost typisch 1× de moeite, kwaliteit detecteren 10×, kwaliteit corrigeren in productie 100×. Test vroeg en automatisch — dat is geen perfectionisme, dat is economie.
De zes dimensies van data quality
Data quality is meetbaar langs zes assen. Een test bedekt vrijwel altijd één van deze:
- Completeness — geen ongewenste NULL's. (
not_null-test op verplichte velden.) - Uniqueness — geen duplicaten waar dat niet mag. (
unique-test op primary / business keys.) - Validity — waarden voldoen aan formaat / domein. (E-mailpatroon, postcode, accepted values.)
- Accuracy — komt overeen met de werkelijkheid. (Moeilijker te testen — vereist vergelijking met externe bron.)
- Consistency — over tabellen heen kloppend. (Ordertotaal = som van orderregels.)
- Timeliness — actueel genoeg. (Data niet ouder dan X uur.)
Data profiling
Voor je tests kunt schrijven, moet je weten wat er in je data zit. Profiling is het systematisch onderzoeken van een dataset op patronen, distributies en uitschieters. Een eenvoudig profile in Python:
import pandas as pd
def profile(df: pd.DataFrame) -> pd.DataFrame:
out = []
for col in df.columns:
s = df[col]
out.append({
"column": col,
"dtype": str(s.dtype),
"n_rows": len(s),
"n_null": s.isna().sum(),
"pct_null": round(s.isna().mean() * 100, 2),
"n_distinct": s.nunique(),
"min": s.min() if s.dtype.kind in "biufM" else None,
"max": s.max() if s.dtype.kind in "biufM" else None,
"sample": s.dropna().head(3).tolist(),
})
return pd.DataFrame(out)
profile(orders).to_csv("orders_profile.csv", index=False)
Voor productie zijn er volwassen tools: ydata-profiling (voorheen pandas-profiling), Great Expectations, Soda of de ingebouwde profilers in dbt en DataHub.
Tests in dbt
Voor wie met dbt werkt: tests zijn declaratief in YAML, op kolomniveau:
version: 2
models:
- name: dim_customer
columns:
- name: customer_key
tests: [unique, not_null]
- name: customer_id
tests: [unique, not_null]
- name: email
tests:
- not_null
- dbt_utils.expression_is_true:
expression: "REGEXP_LIKE(email, '^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\\.[A-Z|a-z]{2,}$')"
- name: segment
tests:
- accepted_values:
values: ['SMB', 'Mid-Market', 'Enterprise']
- name: country
tests:
- relationships:
to: ref('dim_country')
field: country_code
Plus eigen SQL-tests onder tests/ voor business rules:
-- tests/orders_total_matches_lines.sql
-- Faalt als ordertotaal <> som van regels (afwijking > 0.01 EUR)
SELECT o.order_id,
o.total_amount AS header_total,
SUM(l.net_amount) AS line_total
FROM {{ ref('fact_orders') }} o
JOIN {{ ref('fact_order_lines') }} l USING (order_id)
GROUP BY o.order_id, o.total_amount
HAVING ABS(o.total_amount - SUM(l.net_amount)) > 0.01
Great Expectations
Voor diepere validatie en statistische checks (drift in distributies, veranderend null-percentage) is Great Expectations populair:
import great_expectations as gx
context = gx.get_context()
batch = context.sources.add_or_update_pandas("dwh") \
.add_csv_asset("orders", "data/orders.csv") \
.build_batch_request()
validator = context.get_validator(batch_request=batch)
validator.expect_column_values_to_not_be_null("order_id")
validator.expect_column_values_to_be_unique("order_id")
validator.expect_column_values_to_be_between("total_amount", min_value=0, max_value=1_000_000)
validator.expect_column_values_to_match_regex("email",
r"^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,}$")
validator.expect_column_proportion_of_unique_values_to_be_between("customer_id",
min_value=0.7, max_value=1.0)
results = validator.validate()
if not results.success:
raise SystemExit(f"Quality gate gefaald: {results}")
Data contracts
Een data contract is een formele afspraak tussen producer en consumer over schema, kwaliteit, freshness en breaking changes. Verandert het bronsysteem een kolomtype? Het contract dwingt af dat dat aangekondigd en geverifieerd wordt voor het breekt.
# contracts/orders.yml
dataset: orders
owner: team-commerce
schema:
- name: order_id
type: string
constraints: [not_null, unique]
- name: customer_id
type: string
constraints: [not_null]
- name: total_amount
type: decimal(12,2)
constraints: [not_null, ge: 0]
- name: order_date
type: date
constraints: [not_null]
sla:
freshness_max_hours: 6
completeness_min_pct: 99.5
versioning:
breaking_change_notice_days: 14
Tools die dit ondersteunen: Great Expectations, Soda, Monte Carlo, Datafold, DataContract.com. Of zelf bouwen op basis van YAML + CI-checks.
Data lineage
Lineage is het antwoord op "waar komt deze kolom vandaan?". Cruciaal voor:
- Impact analysis bij schema-changes
- GDPR-verzoeken (alle plekken waar persoonsgegevens van klant X staan)
- Debugging — een fout in gold tot bron herleiden
- Documentatie
dbt genereert automatisch lineage als je {{ ref() }} gebruikt. Voor enterprise-brede lineage (dwars door tools heen) zijn DataHub, OpenLineage, Atlan en Collibra de gangbare keuzes.
Master Data Management (MDM)
Wat als "klant" in CRM een andere identiteit heeft dan in ERP? MDM is het proces om één gouden record per entiteit op te bouwen, vaak via een combinatie van:
- Deterministische matching — exacte e-mail of IBAN
- Probabilistische matching — fuzzy match op naam + adres
- Survivorship rules — bij conflict, welke bron wint?
Een eenvoudige fuzzy match in Python:
from rapidfuzz import fuzz
def match_score(a: dict, b: dict) -> float:
name = fuzz.token_sort_ratio(a["name"], b["name"])
addr = fuzz.token_sort_ratio(a["address"], b["address"])
pc = 100 if a["postal_code"] == b["postal_code"] else 0
return 0.5 * name + 0.3 * addr + 0.2 * pc
# > 90: zekere match, 70-90: review, < 70: geen match
GDPR en datawarehouses
GDPR raakt elke DWH met persoonsgegevens. Drie kernverplichtingen:
- Right to access — alle data over één persoon kunnen exporteren. Lineage helpt enorm.
- Right to be forgotten — verwijderen, ook in backups en SCD-historie. Praktisch los je dit vaak op met pseudonymisatie: vervang PII door een hash, behoud analysebare data.
- Purpose limitation — data verzameld voor X mag niet voor Y gebruikt worden zonder grondslag.
Pseudonymisatie-pattern in SQL:
-- Persoonsgegevens in aparte tabel, pseudonym in fact / dim
CREATE TABLE pii_customer (
pseudonym_id CHAR(64) PRIMARY KEY, -- SHA-256 van customer_id
customer_id VARCHAR(50), -- echte ID, encrypted at rest
full_name VARCHAR(200),
email VARCHAR(200),
phone VARCHAR(50)
);
CREATE TABLE dim_customer (
customer_key INT PRIMARY KEY,
pseudonym_id CHAR(64), -- alleen pseudoniem
segment VARCHAR(50),
country VARCHAR(50)
);
Met deze opzet kun je analytics doen zonder PII te raken. Voor "right to be forgotten" verwijder je alleen uit pii_customer — historische analyses blijven werken op het pseudoniem.
Governance: people, process, tech
Governance is geen tool maar een combinatie:
- People — data owners per dataset, data stewards per domein, een data council voor cross-domain beslissingen.
- Process — change management, schema-deprecation policy, security reviews bij nieuwe data.
- Tech — catalog (DataHub, Atlan), lineage, access control, masking.
Begin klein
Probeer geen enterprise-governance-platform op dag één. Begin met: een README per dataset, dbt-tests op kritische tabellen, één gedeelde slide met je naming conventions. Volwassenheid komt in stappen — niet via tooling-keuzes.
Data observability
Modernere stack: data observability automatiseert quality monitoring. Tools (Monte Carlo, Bigeye, Soda Cloud) detecteren via ML afwijkingen in volume, freshness en distributie zonder dat jij elke regel test schrijft. Handig op grote schaal, prijzig voor kleine teams.
Quality gates in CI/CD
Tests die niet automatisch draaien, draaien niet. Bouw quality gates in op drie momenten:
- Pull request — bij elke wijziging in dbt-modellen draait
dbt build --select state:modified+tegen een test-omgeving. Failed tests blokkeren merge. - Pre-deploy — voor elke productie-deploy draaien quality checks tegen sample-data of een snapshot uit productie.
- Post-load — na elke ELT-run draaien tests op de nieuwe data. Bij failure: alert + halt downstream consumers in plaats van garbage publiceren.
Een veelgebruikte aanpak: dbt's error-severity stopt de pipeline; warn-severity logt alleen. Reserveer error voor invarianten waar geen rapportage zin heeft als ze breken (unique keys op fact-tabellen, ordertotaal = som van regels). Zet warn op zachtere checks (volume binnen 10% van vorige run).
Documentatie als first-class citizen
Documentatie verouderd zodra hij in een aparte Confluence-pagina staat. Behandel hem als code:
- Beschrijvingen in de schema.yml naast je dbt-modellen — leeft mee met je code, gegenereerd in
dbt docs. - One-line column descriptions minimaal verplicht — een column zonder beschrijving is een failed PR.
- Business-glossarium centraal — wat is een "actieve klant", wat is "omzet", wat is een "campagne". Tools als DataHub, Atlan en Collibra koppelen termen aan kolommen.
- Voorbeelden in de docs — een rapport-vraag plus de SQL-oplossing helpt nieuwe gebruikers tien keer meer dan abstracte kolomdefinities.
Key takeaways
- Zes data-quality-dimensies: completeness, uniqueness, validity, accuracy, consistency, timeliness.
- Profile eerst, test daarna — anders test je op verkeerde aannames.
- dbt tests of Great Expectations dekken 80% van praktijktests.
- Data contracts maken schema-changes hanteerbaar.
- GDPR vereist pseudonymisatie of fysieke separatie van PII en analytics.
- Governance is people + process + tech — geen tool-aankoop.
- Quality gates in CI/CD voorkomen dat slechte data productie haalt.
Veelgestelde vragen
Wat zijn de zes dimensies van data quality?
Completeness, uniqueness, validity, accuracy, consistency en timeliness. Vrijwel elke data quality test bedekt één van deze zes dimensies.
Wat is een data contract?
Een formele afspraak tussen producer en consumer over schema, kwaliteit, freshness en breaking changes. Het dwingt aankondiging en verificatie af voor schema-wijzigingen. Tools: Great Expectations, Soda, Monte Carlo, Datafold.
Hoe ga je om met GDPR in een datawarehouse?
Right to access (lineage), right to be forgotten (pseudonymisatie of fysieke separatie van PII) en purpose limitation. Bewaar PII in een aparte tabel en koppel via een pseudoniem-hash zodat analytics blijven werken na verwijdering.
Wat is data lineage?
Het antwoord op 'waar komt deze kolom vandaan?'. Cruciaal voor impact analysis, GDPR-verzoeken en debugging. dbt genereert lineage automatisch via ref(); enterprise-breed gebruik je DataHub, OpenLineage, Atlan of Collibra.
Wat is Master Data Management?
Het proces om één gouden record per entiteit op te bouwen door deterministische matching (exacte e-mail of IBAN), probabilistische matching (fuzzy match) en survivorship rules te combineren wanneer dezelfde entiteit in meerdere bronsystemen staat.
Wat is data observability?
Geautomatiseerde quality monitoring met ML-modellen die afwijkingen in volume, freshness en distributies detecteren. Tools: Monte Carlo, Bigeye, Soda Cloud. Handig bij honderden tabellen, overkill voor kleine teams.