Surrogaattiavain on tietovaraston oma, järjestelmän generoima kokonaislukutunniste joka korvaa lähdejärjestelmän luonnollisen avaimen dimension pääavaimena. Dimensioiden mallinnuksen perusteissa käydään läpi mitä surrogaattiavain on ja miksi se on aina kokonaisluku. Tällä sivulla käsitellään kolme asiaa jotka jäävät perusteista usein pois: missä medallion-arkkitehtuurin kerroksessa surrogaatit luodaan, miten suurten dimensioiden avainten hallinta kannattaa rakentaa, ja miten inkrementaalinen lataus toimii käytännössä.
Luonnollinen avain tulee lähdejärjestelmästä ja se on lähdejärjestelmän omistama. Tietomalli, joka rakentaa relaatiot luonnollisten avainten varaan, on riippuvainen lähdejärjestelmän päätöksistä. Kun lähdejärjestelmä uudelleennumeroi asiakkaat järjestelmänvaihdon yhteydessä, kaikki historialliset faktataulun rivit viittaavat väärään asiakkaaseen. Surrogaattiavain katkaisee tämän riippuvuuden.
| Ominaisuus | Luonnollinen avain (NK) | Surrogaattiavain (SK) |
|---|---|---|
| Omistaja | Lähdejärjestelmä — arvo voi muuttua ulkoisesta päätöksestä | Tietovarasto — arvo pysyy muuttumattomana |
| Tietotyyppi | Usein VARCHAR tai yhdistetty avain (useampi sarake) | Aina INT — pakkautuu VertiPaqissa optimaalisesti |
| Uudelleenkäyttö | Lähdejärjestelmä voi antaa saman tunnisteen uudelle entiteetille | Ei koskaan uudelleenkäytetä — jokainen arvo on ainutkertainen |
| Historian säilytys | Sama NK kaikissa versioissa → historia hajoaa tai ylikirjoittuu | Jokainen SCD Type 2 -versio saa oman SK:n → historia säilyy täydellisenä |
| Usean lähteen yhdistäminen | NK:t voivat törmätä: sama asiakastunnus eri järjestelmissä eri asiakkaalle | SK on globaali — ei törmäyksiä lähteiden välillä |
Surrogaattiavaimet kannattaa luoda ennen esitystasoa, eli Silver-kerroksessa. Yleisin virhe on luoda ne liian aikaisin (Bronze) tai liian myöhään (Gold). Silver on se kerros jossa data puhdistetaan, yhdistetään useista lähteistä ja yhdenmukaistetaan yhteiseen muotoon — vasta silloin tiedetään varmuudella mitä entiteettiä NK edustaa.
Yksi ehto kuitenkin: Silver-kerroksen on oltava materialisoitu — fyysisiä tauluja, ei näkymiä.
Surrogaattiavaimet siis kirjoitetaan levylle Silver-tasolla ennen kuin
faktataulu ladataan, jotta faktan latauksessa on olemassa valmis avain johon
viitata. Jos mapping-taulu ja silver.DimAsiakas ovat näkymiä, surrogaattiavainta ei kirjoiteta
mihinkään: se lasketaan uudelleen joka kerta kun joku kysyy. Silloin faktataulun
liitos vertaa yhä lähdejärjestelmän VARCHAR-tunnistetta, ja koko INT-avaimen hyöty jää saamatta. Materialisointi on
tässä myös tekninen pakko eikä pelkkä suositus: IDENTITY on
perustaulun sarakeominaisuus, joten sitä ei voi määritellä näkymässä. Tämän sivun mapping-taulut ovat väistämättä
CREATE TABLE:ja.
| Kerros | Rooli avainten kannalta | Mitä tehdään |
|---|---|---|
| Bronze | Raakadata sellaisenaan | Tallennetaan NK lähdejärjestelmästä koskemattomana. Ei surrogaatteja. Data voi olla epätäydellistä, duplikoitua tai virheellistä — SK:ta ei luoda ennen kuin tiedetään mitä data tarkoittaa. |
| Silver | Avainten luontipaikka — materialisoituna | Rakennetaan mapping-taulu (NK → SK). Jokainen uniikki NK saa oman SK:n. Jos sama entiteetti tulee useasta lähdejärjestelmästä, NK:t yhdistetään yhdeksi SK:ksi. Silver-dimensiotaulussa on sekä SK (PK) että NK (attribuuttina, debug-käyttöön). |
| Gold | Käyttää vain SK:ta | Faktataulut ja raportointimallit viittaavat yksinomaan SK:iin. Gold-kerroksessa ei enää viitata NK:iin relaatioissa. NK voi silti olla dimensiossa attribuuttina loppukäyttäjää varten, mutta se ei ole avain. |
Bronze-data on raakaa: siellä voi olla duplikaattirivejä, puuttuvia arvoja ja saman entiteetin useita versioita peräkkäin. Jos surrogaatti luodaan Bronze-kerroksessa, sama asiakas saa useamman SK:n ennen kuin data on puhdistettu. Tämä tarkoittaa, että Silver-kerroksessa pitää joka tapauksessa yhdistää nämä avaimet uudelleen — eli Bronze-tason SK oli turha välivaihe. Luodaan avain kerran, oikeassa paikassa.
Miksi ei Gold-kerroksessa?Gold-kerroksessa voi olla useita erillisiä tauluja tai aggregointeja, jotka kaikki tarvitsevat saman asiakasdimension. Jos SK luodaan vasta Gold-tasolla, jokainen Gold-taulu luo omat avaimensa — ja ne eivät täsmää toisiinsa. Silver-tasolla luotu SK on se yhteinen nimittäjä, johon kaikki Gold-tasot voivat luottaa.
Kun dimensiossa on miljoonia rivejä — esimerkiksi teleoperaattorin kaikki tilaajat tai verkkokaupan rekisteröityneet käyttäjät — avainten hallintaan tarvitaan erillinen mapping-taulu. Mapping-taulu on yksinkertainen viitetaulu joka pitää kirjaa siitä, mikä NK vastaa mitäkin SK:ta. Se on koko inkrementaalisen latauksen ydin.
| Sarake | Tietotyyppi | Rooli |
|---|---|---|
| AsiakasTunnus | VARCHAR | Luonnollinen avain lähdejärjestelmästä. Indeksoitu — AsiakasTunnukselle lisätään surrogaattiavain ja luontiaika jokaisessa latauksessa |
| AsiakasAvain | INT IDENTITY | Surrogaattiavain. Tietokanta generoi automaattisesti nousevan kokonaisluvun. Ei koskaan uudelleenkäytetä. |
| LuontiAika | DATETIME2 | Milloin rivi lisättiin. Hyödyllinen debug-työssä ja latausprosessin seurannassa. |
Lähdejärjestelmä käyttää asiakasnumerona merkkijonoa FI-VVVV-NNNNN. Viiden latauserän jälkeen
silver.LookupAsiakasAvain näyttää tältä — IDENTITY on generoinut SK:t automaattisesti lisäysjärjestyksessä:
| AsiakasAvain (SK) | AsiakasTunnus (NK) | LuontiAika |
|---|---|---|
| 1 | FI-2024-00001 | 2024-01-15 08:32:04 |
| 2 | FI-2024-00002 | 2024-01-15 08:32:04 |
| 3 | FI-2024-05821 | 2024-03-22 14:05:31 |
| 4 | FI-2025-00134 | 2025-02-10 09:18:47 |
| 5 | FI-2026-00891 | 2026-06-15 14:30:02 |
Mapping-tauluun liitytään dimensiolatauksessa NK:n perusteella. Tuloksena silver.DimAsiakas
käyttää ainoastaan INT-surrogaattiavainta — VARCHAR-tunnus jää attribuutiksi debug-käyttöön:
| AsiakasAvain (PK) | AsiakasTunnus | AsiakasNimi | Segmentti | Maa |
|---|---|---|---|---|
| 1 | FI-2024-00001 | Matti Meikäläinen | Pienasiakas | Suomi |
| 2 | FI-2024-00002 | Yritys Oy | B2B | Suomi |
| 3 | FI-2024-05821 | Tiina Testaaja | Avainasiakas | Suomi |
| 4 | FI-2025-00134 | Nordic Corp AS | B2B | Norja |
| 5 | FI-2026-00891 | Uusi Asiakas Ab | Pienasiakas | Ruotsi |
SQL-koodi joka toteuttaa tämän inkrementaalisesti — ensin mapping-taulun luonti, sitten kaksi vaihetta jokaisessa latauserässä. IDENTITY(1,1) CREATE TABLE:ssa tarkoittaa: ensimmäinen luku on lähtöarvo (1 = aloitetaan ykkösestä), toinen luku on askel (1 = kasvatetaan yhdellä aina kun rivi lisätään). Tietokanta generoi AsiakasAvain-arvon automaattisesti — INSERT-lauseessa ei tarvitse mainita AsiakasAvain-saraketta lainkaan. Ensimmäinen asiakas saa arvon 1, toinen 2, kolmas 3, ja niin edelleen ikuisesti kasvavana sarjana.
-- 1. Mapping-taulun luonti (kerran)
CREATE TABLE silver.LookupAsiakasAvain (
AsiakasAvain INT NOT NULL IDENTITY(1,1),
AsiakasTunnus VARCHAR(50) NOT NULL,
LuontiAika DATETIME2 NOT NULL DEFAULT SYSDATETIME(),
CONSTRAINT PK_LookupAsiakas PRIMARY KEY (AsiakasAvain),
CONSTRAINT UQ_LookupAsiakas_NK UNIQUE (AsiakasTunnus)
);
-- 2. Lisää uudet NK:t — IDENTITY generoi SK:n automaattisesti
-- (ajetaan joka latauserässä ennen dimension päivitystä)
INSERT INTO silver.LookupAsiakasAvain (AsiakasTunnus)
SELECT DISTINCT s.AsiakasTunnus
FROM bronze.Staging_Asiakas s
WHERE NOT EXISTS (
SELECT 1
FROM silver.LookupAsiakasAvain l
WHERE l.AsiakasTunnus = s.AsiakasTunnus
);
-- 3. Päivitä Silver-dimensio (SCD Type 1: ylikirjoita muuttunut data)
MERGE silver.DimAsiakas AS target
USING (
SELECT l.AsiakasAvain,
s.AsiakasTunnus,
s.AsiakasNimi,
s.Segmentti,
s.Maa
FROM bronze.Staging_Asiakas s
JOIN silver.LookupAsiakasAvain l
ON l.AsiakasTunnus = s.AsiakasTunnus
) AS source ON target.AsiakasAvain = source.AsiakasAvain
WHEN MATCHED THEN
UPDATE SET
AsiakasNimi = source.AsiakasNimi,
Segmentti = source.Segmentti,
Maa = source.Maa
WHEN NOT MATCHED THEN
INSERT (AsiakasAvain, AsiakasTunnus, AsiakasNimi, Segmentti, Maa)
VALUES (source.AsiakasAvain, source.AsiakasTunnus,
source.AsiakasNimi, source.Segmentti, source.Maa);
Vaihe 2 lisää mapping-tauluun vain ne rivit joiden NK ei siellä vielä ole — olemassa olevat asiakkaat saavat saman SK:n kuin aiemmassa erässä. Vaihe 3 päivittää dimension: MATCHED-haara ylikirjoittaa muuttuneet attribuutit, NOT MATCHED -haara lisää kokonaan uudet rivit.
SCD Type 2 -mallinnuksessa sama NK voi esiintyä mapping-taulussa useaan kertaan — jokainen versio saa oman SK:n. Tällöin mapping-tauluun lisätään voimassaoloaika ja nykyisyyslippu, jotta tiedetään mikä SK on tällä hetkellä voimassa.
Matti Meikäläinen (NK: FI-2024-00001, nykyinen SK: 1) siirtyy segmentistä
Pienasiakas segmenttiin Avainasiakas 15.6.2026. SCD Type 2 -mallinnuksessa tämä ei ylikirjoita
vanhaa riviä — se sulkee sen ja luo uuden rivin uudella SK:lla.
Ennen muutosta:
| AsiakasAvain | AsiakasTunnus | Segmentti | AlkuPvm | LoppuPvm | OnkoNykyinen |
|---|---|---|---|---|---|
| 1 | FI-2024-00001 | Pienasiakas | 2024-01-15 | 9999-12-31 | 1 |
Latauserän jälkeen (15.6.2026): Vanha rivi suljetaan, uusi SK (7) generoidaan IDENTITY:llä ja uusi dimensiorivi lisätään uudella segmentillä.
| AsiakasAvain | AsiakasTunnus | Segmentti | AlkuPvm | LoppuPvm | OnkoNykyinen |
|---|---|---|---|---|---|
| 1 | FI-2024-00001 | Pienasiakas | 2024-01-15 | 2026-06-14 | 0 |
| 7 | FI-2024-00001 | Avainasiakas | 2026-06-15 | 9999-12-31 | 1 |
FactMyynti-rivit ennen 15.6.2026 viittaavat AsiakasAvaimeen 1 → segmentti on edelleen Pienasiakas. Myyntirivit 15.6.2026 alkaen viittaavat AsiakasAvaimeen 7 → segmentti on Avainasiakas. Sama asiakas, sama NK — mutta kaksi erillistä historiaa.
SCD Type 2 -mapping-taulussa ei voi käyttää UNIQUE-rajoitetta AsiakasTunnukselle, koska sama NK esiintyy useita kertoja. CREATE TABLE eroaa SCD Type 1 -versiosta juuri tässä:
-- SCD Type 2 -mapping-taulu: sama NK voi esiintyä useasti
CREATE TABLE silver.LookupAsiakasAvain (
AsiakasAvain INT NOT NULL IDENTITY(1,1),
AsiakasTunnus VARCHAR(50) NOT NULL,
AlkuPvm DATE NOT NULL,
LoppuPvm DATE NOT NULL DEFAULT '9999-12-31',
OnkoNykyinen BIT NOT NULL DEFAULT 1,
CONSTRAINT PK_LookupAsiakas PRIMARY KEY (AsiakasAvain)
-- Ei UNIQUE(AsiakasTunnus) — sama NK saa useamman SK:n eri versioille
);
-- Vaihe 1: Sulje vanha rivi mapping-taulusta
-- (ennen uuden SK:n luontia, jotta tuore rivi ei sulkeudu heti)
UPDATE l
SET LoppuPvm = DATEADD(day, -1, CAST(GETDATE() AS DATE)),
OnkoNykyinen = 0
FROM silver.LookupAsiakasAvain l
JOIN silver.DimAsiakas d ON d.AsiakasTunnus = l.AsiakasTunnus
AND d.OnkoNykyinen = 1
JOIN bronze.Staging_Asiakas s ON s.AsiakasTunnus = l.AsiakasTunnus
WHERE d.Segmentti <> s.Segmentti
AND l.OnkoNykyinen = 1;
-- Vaihe 2: Luo uusi SK muuttuneelle riville
INSERT INTO silver.LookupAsiakasAvain (AsiakasTunnus, AlkuPvm)
SELECT DISTINCT s.AsiakasTunnus, CAST(GETDATE() AS DATE)
FROM bronze.Staging_Asiakas s
JOIN silver.DimAsiakas d
ON d.AsiakasTunnus = s.AsiakasTunnus
AND d.OnkoNykyinen = 1
WHERE d.Segmentti <> s.Segmentti; -- seurattava attribuutti muuttui
-- Vaihe 3: Sulje vanha dimensiorivi
UPDATE d
SET LoppuPvm = DATEADD(day, -1, CAST(GETDATE() AS DATE)),
OnkoNykyinen = 0
FROM silver.DimAsiakas d
JOIN bronze.Staging_Asiakas s ON s.AsiakasTunnus = d.AsiakasTunnus
WHERE d.Segmentti <> s.Segmentti
AND d.OnkoNykyinen = 1;
-- Vaihe 4: Lisää uusi dimensiorivi uudella SK:lla
INSERT INTO silver.DimAsiakas
(AsiakasAvain, AsiakasTunnus, AsiakasNimi, Segmentti, Maa, AlkuPvm, LoppuPvm, OnkoNykyinen)
SELECT l.AsiakasAvain,
s.AsiakasTunnus, s.AsiakasNimi, s.Segmentti, s.Maa,
CAST(GETDATE() AS DATE), '9999-12-31', 1
FROM bronze.Staging_Asiakas s
JOIN silver.LookupAsiakasAvain l ON l.AsiakasTunnus = s.AsiakasTunnus
AND l.OnkoNykyinen = 1
WHERE NOT EXISTS (
SELECT 1 FROM silver.DimAsiakas d WHERE d.AsiakasAvain = l.AsiakasAvain
);
Vaiheet 1 ja 3 sulkevat vanhan version: LoppuPvm = tänään - 1 ja OnkoNykyinen = 0.
Sulkeminen tehdään ennen insertiä, jotta vaiheen 2 juuri luoma rivi ei sulkeutuisi heti syntymänsä jälkeen.
Vaihe 4 lisää uuden version — IDENTITY antaa sille
seuraavan vapaan kokonaisluvun. Faktatauluun ei kosketa yhdelläkään UPDATElla: voimassaoloaikaa luetaan vain
latauksessa, kun faktariville valitaan tapahtumahetkellä voimassa ollut SK.
Inkrementaalisessa latauksessa data päivitetään erissä: esimerkiksi puolen tunnin välein tai kerran päivässä. Jokaisessa latauksessa voi tulla sekä uusia entiteettejä (uudet asiakkaat) että muuttuneita entiteettejä (olemassa oleva asiakas vaihtanut segmenttiä). Mapping-taulu ratkaisee molemmat tapaukset yhdellä periaatteella: jos NK on jo mapping-taulussa, käytä olemassa olevaa SK:ta; jos ei ole, luo uusi.
SCD Type 2 -mallinnuksessa vaihe 3 muuttuu: kun dimensioattribuutti muuttuu (esim. segmentti vaihtuu), vanhalle riville kirjoitetaan sulkemispäivä ja uudelle versiolle luodaan uusi SK mapping-tauluun. Faktataulun seuraavat rivit viittaavat uuteen SK:hon — historialliset rivit jäävät viittaamaan vanhaan.
| 30 minuutin lataus | Päivittäinen lataus | |
|---|---|---|
| Eräkoko | Pieni — tyypillisesti muutamia satoja tai tuhansia rivejä | Suuri — koko päivän muutokset kerralla |
| Mapping-taulun käyttö | Sama logiikka — NK haetaan mapping-taulusta jokaisessa erässä | Sama logiikka — NK haetaan mapping-taulusta kerran päivässä |
| Erityishuomio | Sama NK voi tulla kahdessa peräkkäisessä erässä (asiakas päivitetty kahdesti tunnissa). Mapping-taulu estää uuden SK:n luomisen — toinen erä löytää NK:n jo mapping-taulusta ja käyttää samaa SK:ta. | Lataus voidaan ajaa hiljaisena aikana (esim. yöllä), eräkoko voi olla suurempi ilman suorituskykyhuolia. SCD Type 2 -sulkemiset ja avaukset tehdään kerralla koko päivän muutoksille. |
| Tuoreusviive | Data on korkeintaan 30 minuuttia vanhaa — sopii operatiiviseen raportointiin | Data on korkeintaan 24 tuntia vanhaa — riittää useimpiin liiketoimintaraportteihin |
| Faktataulun lataus | Faktat ladataan samassa tai seuraavassa erässä — dimension SK:n on oltava olemassa ennen faktan latausta | Dimensiot ladataan ensin, faktat perässä — sama järjestys kuin puolen tunnin mallissa |
Kello 14:00 Bronze-tauluun saapuu uusi asiakas AsiakasTunnus = "FI-2026-00892". Silver-latausprosessi käynnistyy 14:30: