Guide

Dimensjonsmodellering i praksis

Fakta, dimensjoner, granularitet, nøkler og historikk, vist med ett eksempel fra en norsk nettbutikk, og feilene som oftest gir feil tall.

En operasjonell database er laget for å registrere hendelser: en kunde legger inn en ordre, et lager trekker fra en vare. En analytisk modell skal svare på spørsmål om de samme hendelsene: hvor mye solgte vi i Bergen i mars, og hvilke produktkategorier vokser? Dimensjonsmodellering er en måte å bygge den analytiske modellen på, slik at slike spørsmål blir enkle å stille og vanskelige å besvare feil.

Begrepene i denne artikkelen kommer fra Ralph Kimball, som beskrev metoden i boken The Data Warehouse Toolkit. Eksempelet går gjennom hele artikkelen: en norsk nettbutikk som vil analysere salget sitt.

Fakta og dimensjoner

En dimensjonsmodell består av to slags tabeller.

  • Faktatabeller inneholder målbare hendelser. Hver rad er noe som skjedde, med tallene som beskriver det: antall, beløp, rabatt.
  • Dimensjonstabeller inneholder konteksten hendelsene beskrives med. Hvem kjøpte, hva ble kjøpt, og når. Det er kolonnene i dimensjonene folk filtrerer og grupperer på: fylke, produktkategori, måned.

Når faktatabellen står i midten og dimensjonene rundt, kalles mønsteret et stjerneskjema. For nettbutikken blir det fakta_ordrelinje i midten, med dim_kunde, dim_produkt og dim_dato rundt. Stjerneskjemaet ligger typisk i gullaget, bygget på den felles datamodellen i sølvlaget.

Et nyttig skille: tall du vil summere, hører hjemme i faktatabellen. Tekst og kategorier du vil filtrere på, hører hjemme i en dimensjon.

Bestem granulariteten først

Før du velger én eneste kolonne, må du bestemme hva én rad i faktatabellen betyr. Det kalles granulariteten, eller kornet. For nettbutikken er det naturlige valget:

Én rad per ordrelinje, altså ett produkt i én ordre.

Valget styrer alt annet. Med én rad per ordrelinje kan du knytte hver rad til ett produkt, og du kan regne omsetning per produkt. Hadde du valgt én rad per ordre, ville produktet ikke hatt noen plass i faktatabellen, fordi en ordre kan ha mange produkter.

Det lønner seg som regel å velge den laveste granulariteten kildedataene har. Fra ordrelinjer kan du alltid summere opp til ordre, dag eller måned. Fra månedstall kan du aldri gå tilbake til enkeltlinjene.

Skriv granulariteten ned i beskrivelsen av tabellen. Den er det første en ny analytiker trenger å vite.

Eksempelet: en stjerne for ordrelinjer

Her er faktatabellen. Den inneholder nøkler til dimensjonene, et par kolonner som identifiserer ordren, og målene.

fakta_ordrelinje: én rad per ordrelinje.

Kolonne Type Beskrivelse
kunde_nokkel bigint Surrogatnøkkel til dim_kunde, den versjonen av kunden som gjaldt da ordren ble lagt inn.
produkt_nokkel bigint Surrogatnøkkel til dim_produkt.
dato_nokkel integer Nøkkel til dim_dato for ordredatoen, på formen ÅÅÅÅMMDD.
ordre_nr varchar Ordrenummeret fra nettbutikken. Ingen egen dimensjon, men nyttig for å finne igjen ordren.
linje_nr integer Linjens nummer i ordren. Sammen med ordre_nr identifiserer den raden.
antall integer Antall enheter av produktet på linjen.
enhetspris numeric(12,2) Pris per enhet i kroner, uten merverdiavgift, før rabatt.
rabatt numeric(12,2) Samlet rabatt på linjen i kroner, uten merverdiavgift.
belop numeric(12,2) antall × enhetspris − rabatt, i kroner uten merverdiavgift.

Legg merke til at beskrivelsen av belop sier både hvordan det regnes ut og om merverdiavgift er med. Det er den typen avgjørelse som ellers blir tatt på nytt i hver rapport. Mer om hva beskrivelsene bør inneholde, står i Fra forretningsbegrep til kolonne.

ordre_nr står direkte i faktatabellen uten en egen dimensjon. Det kalles en degenerert dimensjon: en identifikator som er nyttig å filtrere på, men som ikke har noen egne egenskaper å beskrive.

dim_kunde: én rad per versjon av en kunde.

Kolonne Type Beskrivelse
kunde_nokkel bigint Surrogatnøkkel, laget i dataplattformen. Ny verdi for hver versjon av kunden.
kunde_id varchar Kundenummeret fra nettbutikken (den naturlige nøkkelen).
navn varchar Kundens navn.
postnummer char(4) Postnummeret i leveringsadressen.
kommune varchar Kommunen postnummeret hører til.
fylke varchar Fylket kommunen hører til.
gyldig_fra date Første dag denne versjonen gjaldt.
gyldig_til date Siste dag denne versjonen gjaldt. 9999-12-31 for gjeldende versjon.
er_gjeldende boolean Sann for den versjonen som gjelder nå.

dim_produkt: én rad per produkt.

Kolonne Type Beskrivelse
produkt_nokkel bigint Surrogatnøkkel, laget i dataplattformen.
produkt_id varchar Varenummeret fra nettbutikken.
navn varchar Produktnavnet slik det vises i butikken.
kategori varchar Hovedkategori, for eksempel «Kjøkken».
underkategori varchar Underkategori, for eksempel «Kaffetraktere».
merke varchar Produsentens merkenavn.

dim_dato: én rad per kalenderdag.

Kolonne Type Beskrivelse
dato_nokkel integer Datoen på formen ÅÅÅÅMMDD, for eksempel 20261021.
dato date Selve datoen.
ukedag varchar Ukedagen på norsk, for eksempel «onsdag».
uke integer Ukenummer etter ISO 8601.
maned integer Månedsnummer, 1–12.
kvartal integer Kvartal, 1–4.
ar integer Årstall.
er_helligdag boolean Sann for norske offentlige helligdager.

Med disse fire tabellene kan analytikeren svare på «omsetning per fylke og kvartal for kategorien Kjøkken» med én spørring: faktatabellen koblet til tre dimensjoner, filtrert og gruppert på kolonner i dimensjonene, og summert over belop.

Naturlige nøkler og surrogatnøkler

Kildesystemet har allerede nøkler: kundenummer, varenummer, ordrenummer. Det er naturlige nøkler. Dimensjonene tar vare på dem, i kunde_id og produkt_id, slik at det går an å spore en rad tilbake til kilden.

Likevel peker faktatabellen på dimensjonene med surrogatnøkler, tall som dataplattformen lager selv. Det er tre grunner til det:

  • Historikk. Hvis en kunde har flere versjoner i dim_kunde, er ikke kunde_id lenger unik. Surrogatnøkkelen skiller versjonene, og faktaraden peker på akkurat den versjonen som gjaldt da ordren ble lagt inn.
  • Uavhengighet fra kilden. Hvis nettbutikken bytter system og kundenumrene endres, eller to kilder har overlappende numre, påvirker det bare dimensjonen, ikke alle radene i faktatabellen.
  • Ukjente verdier. En ordrelinje uten kjent kunde kan peke på en egen rad i dim_kunde for «ukjent kunde», i stedet for å ha en tom nøkkel som forsvinner i en vanlig join.

Datodimensjonen er et vanlig unntak: der brukes ofte selve datoen som nøkkel, fordi en kalenderdag ikke endrer seg.

Historikk: når kunden flytter

Kunde 10427 bor i Bergen og har handlet der i to år. I august flytter kunden til Trondheim og oppdaterer adressen i nettbutikken. Hva skal skje med dim_kunde? Det er spørsmålet SCD, slowly changing dimensions, handler om. De tre vanligste typene svarer forskjellig.

Type 1 overskriver. Raden oppdateres med den nye adressen. Det er enkelt, men all historikk forsvinner: alle ordrene fra Bergen ser nå ut som om de kom fra Trondheim, og omsetningen per fylke for i fjor endrer seg i etterkant. Type 1 passer for rettelser, som et feilstavet navn, der den gamle verdien aldri var riktig.

Type 2 legger til en ny rad. Den gamle raden får gyldig_til satt til dagen før flyttingen og er_gjeldende satt til usann. En ny rad med ny surrogatnøkkel får den nye adressen:

kunde_nokkel kunde_id kommune gyldig_fra gyldig_til er_gjeldende
5810 10427 Bergen 2024-06-03 2026-08-14 usann
9132 10427 Trondheim 2026-08-15 9999-12-31 sann

Ordrene fra før flyttingen peker på 5810, de nye på 9132. Omsetningen for Bergen i fjor står fast, og du kan fortsatt telle kunden én gang ved å gruppere på kunde_id. Det er derfor surrogatnøkkelen trengs.

Type 3 tar vare på forrige verdi i en egen kolonne. dim_kunde får en kolonne forrige_kommune ved siden av kommune. Det gir mulighet til å sammenligne «før» og «etter» for én endring, men ikke mer: flytter kunden igjen, er Bergen borte. Type 3 brukes mest der en endring skjer på én gang for mange rader, for eksempel når en salgsregion deles opp.

Du velger type per kolonne, ikke per tabell. I dim_kunde kan adressen være type 2 mens navnet er type 1. Skriv valget inn i beskrivelsen av kolonnen.

Vanlige feil

Blandet granularitet i én faktatabell. Noen legger fraktkostnaden, som gjelder hele ordren, inn i fakta_ordrelinje. Da står den på hver linje, og summen av frakt blir like mange ganger for stor som ordren har linjer. Mål på ordrenivå hører hjemme i en egen faktatabell med én rad per ordre, eller må fordeles på linjene etter en regel som står i beskrivelsen.

Fakta lagret i dimensjoner. En kolonne total_omsetning i dim_kunde ser praktisk ut, men den er et aggregat som er riktig bare på det tidspunktet den ble regnet ut, og den kan ikke filtreres på periode. Tall som skal summeres, hører hjemme i faktatabellen.

Ingen datodimensjon. Uten dim_dato må hver rapport regne ut uke, kvartal og helligdager fra en dato, og ulike rapporter gjør det ulikt. Ukenummer rundt nyttår er et kjent eksempel. En datodimensjon gjør det én gang.

Dobbelttelling når to faktatabeller kobles. Nettbutikken har også fakta_retur med én rad per returnert vare. Kobler du fakta_ordrelinje og fakta_retur direkte på kunde, blir hver ordrelinje gjentatt for hver retur kunden har, og begge summene blir for store. Summer hver faktatabell for seg på den felles granulariteten, for eksempel kunde og måned, og koble resultatene etterpå. Det fungerer fordi begge bruker de samme dimensjonene.

Unødvendig snøfnugging. Snøfnugging betyr å dele en dimensjon opp i flere normaliserte tabeller, for eksempel dim_produkt → dim_underkategori → dim_kategori. Det sparer litt lagringsplass, men hver spørring trenger flere joins, og modellen blir vanskeligere å lese. I de fleste tilfeller er det enklere å ha kategori og underkategori som kolonner i dim_produkt, slik eksempelet over gjør.

Hvor du kan begynne

Du trenger ikke modellere hele virksomheten på én gang. Kimball beskriver fire steg for hver stjerne:

  1. Velg forretningsprosessen. Ta en prosess som noen allerede stiller spørsmål om, for nettbutikken salg.
  2. Bestem granulariteten. Skriv den ned: én rad per ordrelinje.
  3. Finn dimensjonene. Velg de få dimensjonene spørsmålene faktisk bruker, typisk hvem, hva og når. Bestem for hver kolonne i dimensjonene om endringer skal overskrives eller tas vare på.
  4. Finn faktaene. Ta med målene som hører til granulariteten: antall, enhetspris, rabatt og beløp.

Når den første stjernen er i bruk, kan neste prosess, for eksempel returer, gjenbruke dim_kunde, dim_produkt og dim_dato som felles (konforme) dimensjoner. Slik vokser modellen én faktatabell om gangen, med dimensjoner som betyr det samme overalt. Beskriver du tabellene med eier, betydning og kvalitetskrav, er du også på vei mot en datakontrakt for dem.

Kilder