← Terug naar Downloads DataPartner365 — datapartner365.nl
DataPartner365 · Handleiding · 2026

Datamodellen Ontwerpen

Van conceptueel model tot fysiek SQL schema: een complete handleiding voor het ontwerpen van datamodellen met normalisatie, ster-schema, SCD en Data Vault 2.0.

ERD SQL Kimball Data Vault Normalisatie Gratis
1

Conceptueel, logisch & fysiek model

Drie abstractieniveaus, doelgroep per niveau

Datamodelleren verloopt via drie abstractieniveaus. Elk niveau heeft een andere doelgroep en een ander detailniveau.

NiveauDoelDoelgroepBevat
ConceptueelBegrijpen wat de business nodig heeftBusiness stakeholdersEntiteiten, relaties (geen datatypes)
LogischStructuur onafhankelijk van databaseData architectAttributen, datatypes, sleutels, normalisatievorm
FysiekImplementatie in specifiek database systeemData engineer / DBADDL statements, indexen, partitionering

Stappen in het modelleringsproces

  1. Requirementsanalyse: Welke vragen moet het model kunnen beantwoorden?
  2. Conceptueel model: Entiteiten en relaties in een ERD
  3. Logisch model: Attributen, sleutels en normalisatie toevoegen
  4. Fysiek model: DDL schrijven voor het gekozen platform
  5. Review: Met business en engineering valideren
Gebruik dbdiagram.io (gratis) of ERDPlus voor het tekenen van ERD's. Voor enterprise modellering: erwin Data Modeler of PowerDesigner.
2

Entiteiten, attributen & relaties

ERD lezen, kardinaliteit, primaire en vreemde sleutels

Een Entity Relationship Diagram (ERD) is de visuele weergave van een datamodel. Begrijpen hoe je een ERD leest is de basis voor alle verdere modellering.

Kernconcepten

ConceptDefinitieVoorbeeld
EntiteitEen ding waarover je data opslaatKlant, Order, Product
AttribuutEigenschap van een entiteitklant_naam, order_datum
Primaire sleutel (PK)Uniek identificerend attribuutklant_id, order_id
Vreemde sleutel (FK)Verwijzing naar PK van andere entiteitklant_id in Order tabel
RelatieVerband tussen twee entiteitenKlant plaatst Order

Kardinaliteit

  • Een-op-een (1:1): Één klant heeft één paspoort
  • Een-op-veel (1:N): Één klant plaatst meerdere orders
  • Veel-op-veel (M:N): Een order bevat meerdere producten; een product staat in meerdere orders
sql — relaties als ddl
-- Een-op-veel: klant → orders CREATE TABLE klant ( klant_id INTEGER PRIMARY KEY, naam VARCHAR(200) NOT NULL ); CREATE TABLE order_hoofd ( order_id INTEGER PRIMARY KEY, klant_id INTEGER NOT NULL REFERENCES klant(klant_id), order_datum DATE NOT NULL ); -- Veel-op-veel: order ↔ product via koppeltabel CREATE TABLE order_regel ( order_id INTEGER REFERENCES order_hoofd(order_id), product_id INTEGER REFERENCES product(product_id), aantal INTEGER NOT NULL CHECK (aantal > 0), stukprijs DECIMAL(10,2) NOT NULL, PRIMARY KEY (order_id, product_id) );
3

Normalisatie (1NF, 2NF, 3NF)

Redundantie verwijderen, anomalieën voorkomen

Normalisatie is het systematisch organiseren van data om redundantie en update-anomalieën te voorkomen. Voor OLTP systemen streef je naar minimaal de derde normaalvorm (3NF).

1NF — Eerste Normaalvorm

Elke cel bevat één waarde (atomaire waarden). Geen herhalende groepen.

sql — schending en fix van 1nf
-- SLECHT: meerdere telefoonnummers in één kolom CREATE TABLE klant_slecht ( klant_id INTEGER, naam VARCHAR(200), telefoons VARCHAR(500) -- "0612345678, 0698765432" ← niet atomair! ); -- GOED: aparte tabel voor telefoonnummers (1NF) CREATE TABLE klant ( klant_id INTEGER PRIMARY KEY, naam VARCHAR(200) NOT NULL ); CREATE TABLE klant_telefoon ( klant_id INTEGER REFERENCES klant(klant_id), telefoon VARCHAR(20) NOT NULL, type VARCHAR(10), -- 'mobiel', 'thuis', 'werk' PRIMARY KEY (klant_id, telefoon) );

2NF — Tweede Normaalvorm

Voldoet aan 1NF + alle niet-sleutelattributen zijn volledig afhankelijk van de volledige primaire sleutel (geen partiële afhankelijkheden).

sql — schending en fix van 2nf
-- SLECHT: product_naam hangt alleen af van product_id (niet van order_id) CREATE TABLE order_regel_slecht ( order_id INTEGER, product_id INTEGER, product_naam VARCHAR(200), -- ← partiële afhankelijkheid! aantal INTEGER, PRIMARY KEY (order_id, product_id) ); -- GOED: product_naam naar eigen tabel (2NF) CREATE TABLE product ( product_id INTEGER PRIMARY KEY, product_naam VARCHAR(200) NOT NULL ); CREATE TABLE order_regel ( order_id INTEGER REFERENCES order_hoofd(order_id), product_id INTEGER REFERENCES product(product_id), aantal INTEGER NOT NULL, PRIMARY KEY (order_id, product_id) );

3NF — Derde Normaalvorm

Voldoet aan 2NF + geen transitieve afhankelijkheden (niet-sleutelattribuut mag niet afhangen van een ander niet-sleutelattribuut).

sql — schending en fix van 3nf
-- SLECHT: postcode bepaalt stad (transitieve afhankelijkheid) CREATE TABLE klant_slecht ( klant_id INTEGER PRIMARY KEY, naam VARCHAR(200), postcode VARCHAR(10), stad VARCHAR(100) -- hangt af van postcode, niet klant_id! ); -- GOED: postcode tabel (3NF) CREATE TABLE postcode ( postcode VARCHAR(10) PRIMARY KEY, stad VARCHAR(100) NOT NULL ); CREATE TABLE klant ( klant_id INTEGER PRIMARY KEY, naam VARCHAR(200) NOT NULL, postcode VARCHAR(10) REFERENCES postcode(postcode) );
4

Ster-schema (Kimball methodologie)

Feitentabellen, dimensies, surrogate keys, conformed dimensions

Het ster-schema (Kimball) is de standaard voor datawarehouse modellering. Het is bewust gedenormaliseerd voor queryperformance en begrijpelijkheid.

sql — compleet ster-schema voorbeeld
-- Datum dimensie (conformed: gedeeld door alle feiten) CREATE TABLE dim_datum ( datum_sk INTEGER PRIMARY KEY, -- surrogate key: YYYYMMDD datum DATE NOT NULL UNIQUE, dag_van_maand SMALLINT NOT NULL, dag_naam VARCHAR(20) NOT NULL, week_nummer SMALLINT NOT NULL, maand_nummer SMALLINT NOT NULL, maand_naam VARCHAR(20) NOT NULL, kwartaal SMALLINT NOT NULL, jaar SMALLINT NOT NULL, is_weekend BOOLEAN NOT NULL, is_feestdag BOOLEAN NOT NULL DEFAULT FALSE ); -- Klant dimensie CREATE TABLE dim_klant ( klant_sk INTEGER PRIMARY KEY GENERATED ALWAYS AS IDENTITY, klant_id VARCHAR(50) NOT NULL, -- business key klant_naam VARCHAR(200) NOT NULL, segment VARCHAR(50), land CHAR(2), stad VARCHAR(100), is_actief BOOLEAN DEFAULT TRUE ); -- Product dimensie CREATE TABLE dim_product ( product_sk INTEGER PRIMARY KEY GENERATED ALWAYS AS IDENTITY, product_id VARCHAR(50) NOT NULL, product_naam VARCHAR(200) NOT NULL, categorie VARCHAR(100), subcategorie VARCHAR(100), merk VARCHAR(100), kostprijs DECIMAL(18,2) ); -- Omzet feitentabel CREATE TABLE fct_omzet ( datum_sk INTEGER NOT NULL REFERENCES dim_datum(datum_sk), klant_sk INTEGER NOT NULL REFERENCES dim_klant(klant_sk), product_sk INTEGER NOT NULL REFERENCES dim_product(product_sk), -- Business key voor traceerbaarheid order_id VARCHAR(50) NOT NULL, -- Feiten (meetbare waarden) aantal INTEGER NOT NULL, verkoopprijs DECIMAL(18,2) NOT NULL, korting DECIMAL(18,2) DEFAULT 0, omzet DECIMAL(18,2) NOT NULL, marge DECIMAL(18,2), CONSTRAINT pk_fct_omzet PRIMARY KEY (order_id, product_sk) );
Surrogate keys: Gebruik altijd integer surrogate keys (gegenereerd door de database) als primaire sleutel in dimensietabellen, nooit de business key. Dit maakt SCD Type 2 mogelijk en isoleert het warehouse van bronwijzigingen.
5

Sneeuwvlokschema

Genormaliseerde dimensies, voor- en nadelen

Het sneeuwvlokschema is een variant van het ster-schema waarbij dimensies verder zijn genormaliseerd. Een productdimensie kan bijvoorbeeld worden opgesplitst in product, categorie en merk.

sql — sneeuwvlok: product opgesplitst
-- Categorie als aparte tabel CREATE TABLE dim_categorie ( categorie_sk INTEGER PRIMARY KEY, categorie_naam VARCHAR(100) NOT NULL, afdeling VARCHAR(100) ); -- Product verwijst naar categorie CREATE TABLE dim_product_sneeuwvlok ( product_sk INTEGER PRIMARY KEY, product_id VARCHAR(50), product_naam VARCHAR(200), categorie_sk INTEGER REFERENCES dim_categorie(categorie_sk), merk VARCHAR(100), kostprijs DECIMAL(18,2) ); -- Query vereist nu extra join SELECT c.categorie_naam, SUM(f.omzet) FROM fct_omzet f JOIN dim_product_sneeuwvlok p ON f.product_sk = p.product_sk JOIN dim_categorie c ON p.categorie_sk = c.categorie_sk GROUP BY c.categorie_naam;
⚠️ Gebruik sneeuwvlokschema alleen als dimensies erg groot zijn (miljoenen rijen) of als categoriehiërachieën diep zijn (meer dan 3 niveaus). De extra joins vertragen queries. Voor BI-rapportage is het ster-schema vrijwel altijd beter.
6

Slowly Changing Dimensions (SCD)

Type 1 (overschrijven), Type 2 (historiek), Type 3 (kolom)

Dimensiedata verandert over tijd (klant verhuist, product krijgt nieuwe categorie). SCD types bepalen hoe je omgaat met deze wijzigingen.

SCD Type 1 — Overschrijven (geen historiek)

sql — scd type 1: update
-- Eenvoudig: update de bestaande rij, geen historiek bewaard UPDATE dim_klant SET stad = 'Amsterdam', postcode = '1012AB', updated_at = CURRENT_TIMESTAMP WHERE klant_id = 'K001';

SCD Type 2 — Historiek bewaren (nieuwe rij)

sql — scd type 2: structuur en merge
-- Type 2 kolommen toevoegen aan dim_klant ALTER TABLE dim_klant ADD COLUMN valid_from DATE NOT NULL DEFAULT CURRENT_DATE, ADD COLUMN valid_to DATE, -- NULL = huidig actief record ADD COLUMN is_current BOOLEAN DEFAULT TRUE; -- Type 2 update: sluit oud record + voeg nieuw toe BEGIN; -- 1. Sluit het huidige record UPDATE dim_klant SET valid_to = CURRENT_DATE - 1, is_current = FALSE WHERE klant_id = 'K001' AND is_current = TRUE; -- 2. Voeg nieuwe versie toe INSERT INTO dim_klant (klant_id, klant_naam, stad, segment, valid_from, valid_to, is_current) VALUES ('K001', 'Jan de Vries', 'Amsterdam', 'Gold', CURRENT_DATE, NULL, TRUE); COMMIT; -- Actieve records opvragen: SELECT * FROM dim_klant WHERE is_current = TRUE;

SCD Type 3 — Vorige waarde bewaren (extra kolom)

sql — scd type 3
-- Vorige waarde als aparte kolom opslaan CREATE TABLE dim_klant_type3 ( klant_sk INTEGER PRIMARY KEY, klant_id VARCHAR(50), huidig_segment VARCHAR(50), vorig_segment VARCHAR(50), -- vorige waarde segment_gewijzigd DATE -- datum laatste wijziging ); -- Update Type 3: UPDATE dim_klant_type3 SET vorig_segment = huidig_segment, huidig_segment = 'Gold', segment_gewijzigd = CURRENT_DATE WHERE klant_id = 'K001';
ℹ️ Keuze: Type 1 voor correcties van fouten (geen historiek nodig). Type 2 voor business-relevante wijzigingen (segment, adres) waar historische analyse vereist is. Type 3 alleen als je exact één vorige versie nodig hebt.
7

Data Vault 2.0

Hubs, Links, Satellites — agiel en auditeerbaar

Data Vault 2.0 is een alternatief modelleringsparadigma voor de Raw Vault (historische opslag). Het is zeer flexibel bij bronwijzigingen en volledig auditeerbaar.

ComponentDoelBevat
HubBusiness sleutels opslaanhash_key, business_key, load_date, record_source
LinkRelaties tussen Hubshash_key, hub_key_A, hub_key_B, load_date, record_source
SatelliteBeschrijvende attributen + historiekhub_key, load_date, attributes, hash_diff
sql — data vault 2.0 structuur
-- Hub: unieke business sleutels voor Klant CREATE TABLE hub_klant ( klant_hk CHAR(32) PRIMARY KEY, -- MD5/SHA1 hash van business key klant_bk VARCHAR(50) NOT NULL, -- business key load_date TIMESTAMP NOT NULL, record_source VARCHAR(100) NOT NULL ); -- Hub: unieke business sleutels voor Order CREATE TABLE hub_order ( order_hk CHAR(32) PRIMARY KEY, order_bk VARCHAR(50) NOT NULL, load_date TIMESTAMP NOT NULL, record_source VARCHAR(100) NOT NULL ); -- Link: relatie klant ↔ order CREATE TABLE lnk_klant_order ( klant_order_hk CHAR(32) PRIMARY KEY, -- hash van klant_hk + order_hk klant_hk CHAR(32) REFERENCES hub_klant(klant_hk), order_hk CHAR(32) REFERENCES hub_order(order_hk), load_date TIMESTAMP NOT NULL, record_source VARCHAR(100) NOT NULL ); -- Satellite: attributen van Klant (met historiek) CREATE TABLE sat_klant ( klant_hk CHAR(32) REFERENCES hub_klant(klant_hk), load_date TIMESTAMP NOT NULL, load_end_date TIMESTAMP, -- NULL = huidig hash_diff CHAR(32) NOT NULL, -- hash van alle attributen record_source VARCHAR(100) NOT NULL, klant_naam VARCHAR(200), email VARCHAR(254), segment VARCHAR(50), stad VARCHAR(100), PRIMARY KEY (klant_hk, load_date) );
Data Vault is het meest waardevol in grote enterprise omgevingen met veel bronnen, frequente bronwijzigingen en strikte auditverplichtingen. Voor kleinere omgevingen is het Kimball ster-schema eenvoudiger en snel genoeg.
8

Naming conventions & best practices

Consistentie, leesbaarheid, documentatie

Consistente naamgeving is essentieel voor een onderhoudbaar datamodel. Stel afspraken vast voordat het project begint en leg ze vast in een Data Dictionary.

ElementConventieVoorbeeld
Tabelnaamsnake_case, enkelvouddim_klant, fct_omzet
Kolomnaamsnake_case, beschrijvendklant_naam, order_datum
Primary Key[tabel]_sk (surrogate) of [tabel]_id (business)klant_sk, order_id
Foreign Key[referentietabel]_skklant_sk, product_sk
Booleanis_ of has_ prefixis_actief, has_korting
Datum (date)_datum of _date suffixorder_datum, geboorte_datum
Timestamp_at suffixcreated_at, updated_at
Bedrag_bedrag of _amount suffixomzet_bedrag, korting_amount

Best practices samengevat

  • Documenteer elk model in een Data Dictionary (tabel, kolom, beschrijving, eigenaar)
  • Gebruik constraint namen (pk_, fk_, uq_, chk_)
  • Voeg altijd created_at en updated_at toe voor auditing
  • Voer een peer review uit op elk nieuw datamodel met zowel business als engineering
  • Versiebeheer: sla DDL scripts op in Git, gebruik database migratie tools (Flyway, Liquibase)
ℹ️ Gebruik Flyway of Liquibase voor schema migraties. Zo is elke schemawijziging traceerbaar, herhaalbaar en kan worden teruggedraaid. Sla migratiescripts op in dezelfde Git repository als je applicatiecode.
9

Checklist

Controleer je datamodel voor implementatie
Model Ontwerp
  • Conceptueel model goedgekeurd door business stakeholders
  • Logisch model doorloopt minimaal 3NF (OLTP) of ster-schema (OLAP)
  • Kardinaliteit van alle relaties gedocumenteerd
  • SCD strategie bepaald per dimensietabel
SQL & DDL
  • Alle tabellen hebben een primaire sleutel
  • Foreign key constraints aangemaakt (of gedocumenteerd als logisch)
  • Geen FLOAT voor geldbedragen (gebruik DECIMAL)
  • created_at en updated_at aanwezig op alle tabellen
Naamgeving
  • Consistent snake_case voor alle namen
  • Prefix conventies gevolgd (fct_, dim_, stg_)
  • Constraint namen zijn beschrijvend (pk_, fk_, uq_)
  • Data Dictionary bijgewerkt voor alle nieuwe tabellen
Versiebeheer
  • DDL scripts opgeslagen in Git
  • Migratie tool geconfigureerd (Flyway/Liquibase)
  • Model gereviewed door minstens één collega
  • Rollback plan klaar voor elke schemawijziging