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

20 min leestijd Beginner-Gevorderd

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:

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:

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:

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:

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:

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:

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:

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:

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.