🏗️ Hurtownie Danych

Schemat gwiazdy i płatka śniegu, SH schema, projektowanie

⚡ Kluczowe fakty do zapamiętania
  • 4 cechy Inmona (MUST KNOW): (1) Zorientowana tematycznie — dane wokół tematu (sprzedaż), nie procesu (fakturowanie). (2) Zintegrowana — dane z różnych źródeł ujednolicone. (3) Nieulotna — raz załadowane dane nie są modyfikowane, tylko dołączane. (4) Zmienna w czasie — każdy rekord ma timestamp, opisuje historię.
  • Schemat gwiazdy = tabela faktów w centrum + wymiary bezpośrednio podłączone (zdenormalizowane). Mało JOINów → szybkie zapytania OLAP. Płatek śniegu = wymiary znormalizowane (hierarchie rozbite na osobne tabele). Więcej JOINów, mniej redundancji.
  • Tabela faktów = centrum schematu, zawiera klucze obce do wymiarów + miary (liczby które chcemy analizować: kwota, ilość, koszt). Tabela wymiarów = 'po czym filtrujemy/grupujemy' (czas, produkt, klient, region).
  • Ziarnistość (granularity) = poziom szczegółowości jednego wiersza w tabeli faktów. Jedna transakcja = najniższa ziarnistość = maksymalna elastyczność + największa tabela. Miesiąc = wyższa ziarnistość = mniejsza tabela ale mniej szczegółów.
  • Operacje OLAP: Roll-up = od szczegółu do ogółu (sklep → miasto → kraj). Drill-down = od ogółu do szczegółu (rok → kwartał → miesiąc). Slice = wybierz jeden plaster (tylko rok 2023). Dice = wybierz kilka wymiarów (rok 2022-2023 I region PL). Pivot = obróć osie.
  • SH Schema (Oracle Sales History): sh.sales (fakty: prod_id, cust_id, time_id, channel_id, amount_sold). Dołącz przez: JOIN sh.products ON prod_id, JOIN sh.times ON time_id, JOIN sh.channels ON channel_id, JOIN sh.customers ON cust_id.

Definicje hurtowni danych

AutorDefinicja
W. H. InmonKolekcja danych zorientowana tematycznie, zintegrowana, nieulotna i zmienna w czasie, wspomagająca podejmowanie decyzji
R. KimballSystem pobierający dane ze źródeł, transformujący je i ładujący do struktur wielowymiarowych, wspierających zapytania analityczne
ℹ️
4 cechy Inmona — musisz znać!
Zorientowana tematycznie — dane zorganizowane wokół tematów (klienci, produkty, sprzedaż), nie procesów aplikacji.
Zintegrowana — dane z różnych źródeł ujednolicone (jeden format dat, jedno kodowanie płci).
Nieulotna (non-volatile) — dane raz załadowane nie są modyfikowane; nowe dane są dołączane.
Zmienna w czasie (time-variant) — każdy rekord ma znacznik czasu; dane opisują historię, nie tylko bieżący stan.

Hurtownia danych vs OLTP

CechaOLTP (np. HR)Hurtownia / OLAP
CelOperacje bieżące (INSERT, UPDATE, DELETE)Analiza historyczna (SELECT)
Schemat3NF — znormalizowanyGwiazda / płatek śniegu — zdenormalizowany
DaneAktualneHistoryczne (lata)
ZapytaniaProste, szybkie (klucze)Złożone agregacje, JOIN wielu tabel
WydajnośćDużo małych transakcjiMało dużych zapytań analitycznych

Architektura hurtowni — modele

ModelOpisWady
Bez stagingDane ładowane wprost ze źródeł do hurtowni; transformacje "w locie"Obciążenie źródeł; trudna kontrola jakości
Ze staging areaDane trafiają najpierw do strefy przejściowej (surowe dane, czyszczenie, ujednolicanie)Bardziej złożona architektura
Ze staging + data martsDodatkowo tematyczne podmartownia (sprzedaż, finanse, marketing) dla konkretnych działówNajdroższe; wymaga starannego zarządzania

ROLAP / MOLAP / HOLAP — technologie OLAP

TechnologiaPrzechowywanieZaletyWady
ROLAP (Relational OLAP)Relacyjna baza danych — tabele gwiazdy/płatka, SQLBrak nowej infrastruktury, skaluje do dużych zbiorówWolniejszy — każde zapytanie OLAP = wiele JOINów
MOLAP (Multidimensional OLAP)Wielowymiarowe kostki danych (cube), specjalny formatBardzo szybkie zapytania — dane preaggregowaneSpecjalna infrastruktura, słaba skalowalność przy wielu wymiarach
HOLAP (Hybrid OLAP)Hybrydowo: szczegóły w ROLAP, agregaty w MOLAPKompromis prędkość + skalowalnośćZłożona architektura, trudna administracja
ℹ️
Kiedy co wybrać?
ROLAP — gdy masz już relacyjną bazę i zależy Ci na elastyczności SQL.
MOLAP — gdy masz ograniczoną liczbę wymiarów i potrzebujesz maksymalnej szybkości zapytań.
HOLAP — kompromis: szczegółowe dane w ROLAP (duże tabele faktów), prekalkulowane agregaty w MOLAP.

Operacje OLAP

OperacjaKierunekPrzykład
Roll-up (drill-up)Szczegół → ogółSklep → Miasto → Województwo
Drill-downOgół → szczegółRok → Kwartał → Miesiąc → Dzień
SliceWybór jednej wartości wymiaruTylko rok 2023 (cały plaster)
DiceWybór zakresu kilku wymiarówRok 2022–2023 AND Region = PL
Pivot / RotateZmiana kolejności wymiarówZamień osie: produkt ↔ region

Ziarnistość (Grain) — poziom szczegółowości

ZiarnistośćPrzykład wiersza faktówUwagi
Transakcja (najniższa)Jedna sprzedaż w sklepieNajwiększa elastyczność, ogromna tabela
Dzienny snapshotSprzedaż dzienna na produktKompromis
Miesięczny snapshotSaldo konta na koniec miesiącaMniej miejsca, mniej zapytań

Typy miar w tabeli faktów

TypOpisPrzykładyJak agregować?
AddytywnaMożna sumować po WSZYSTKICH wymiarachamount_sold, quantity_sold, costSUM działa bez ograniczeń
Częściowo addytywna (semi-additive)Można sumować po NIEKTÓRYCH wymiarach, nie wszystkichSaldo konta na koniec miesiąca, stan magazynuSumuj po regionach: OK. Sumuj po czasie: BŁĄD. Użyj AVG lub wartości z konkretnej daty
Nieaddytywna (non-additive)NIE można sumować po żadnym wymiarzeCena jednostkowa, procent udział, wskaźniki (KPI)Zawsze obliczaj pochodnie z miar addytywnych (np. marża = przychód - koszt)
⚠️
Pułapka miar semi-addytywnych — saldo bankowe
Saldo rachunku na koniec miesiąca = miara semi-addytywna.
SUM(saldo) GROUP BY region — suma sald wszystkich klientów w regionie: poprawne.
SUM(saldo) GROUP BY rok — suma sald z 12 miesięcy ≠ saldo roczne: błędne!
Zamiast SUM po czasie użyj AVG(saldo) lub pobierz stan z ostatniego dnia okresu.

Wymiar czasu — typowe atrybuty

Wymiar czasu jest najważniejszym wymiarem — prawie każda analiza dotyczy jakiegoś okresu. Typowe kolumny:

KolumnaPrzykładUwaga
time_id (PK)20240315Klucz zastępczy lub data jako NUMBER
data (DATE)2024-03-15Konkretna data kalendarza
dzien_tygodnia51=poniedziałek, 7=niedziela (lub 0=niedziela w Oracle)
nazwa_dnia_pol / angPiątek / FridayPrzydatne w raportach
miesiac3Numer miesiąca 1–12
nazwa_miesiaca_pol / angMarzec / MarchPełna i skrócona nazwa
kwartal11–4
rok2024Rok kalendarzowy
tydzien_roku11Numer tygodnia w roku (1–52)
czy_swieto'T' / 'N'Flaga dnia wolnego (święto)
sh.times — wymiar czasu w Oracle SH
sh.times zawiera: time_id (DATE/PK), calendar_year, calendar_quarter_number, calendar_month_number, day_number_in_week, day_name.
Wymiar czasu jest zwykle prekalkulowany na kilka lat do przodu — nie generuje się go dynamicznie.

Schemat gwiazdy (Star Schema)

Centralna tabela faktów otoczona tabelami wymiarów. Wymiary są płaskie (zdenormalizowane) — nie mają własnych relacji do innych tabel.

sql
-- Tabela faktów (FACT TABLE): zawiera klucze obce + miary
CREATE TABLE fact_sales (
    sale_id       NUMBER PRIMARY KEY,
    time_id       NUMBER REFERENCES dim_time(time_id),
    product_id    NUMBER REFERENCES dim_product(product_id),
    customer_id   NUMBER REFERENCES dim_customer(customer_id),
    channel_id    NUMBER REFERENCES dim_channel(channel_id),
    amount_sold   NUMBER(10,2),   -- miara
    quantity_sold NUMBER,          -- miara
    cost          NUMBER(10,2)     -- miara
);

-- Tabela wymiaru (DIMENSION TABLE): płaska, zdenormalizowana
CREATE TABLE dim_product (
    product_id    NUMBER PRIMARY KEY,
    product_name  VARCHAR2(100),
    category      VARCHAR2(50),    -- denormalizacja (w 3NF byłaby osobna tabela)
    subcategory   VARCHAR2(50),
    unit_price    NUMBER(8,2)
);

Schemat płatka śniegu (Snowflake Schema)

Wymiary są znormalizowane — tabela wymiaru odwołuje się do innych tabel (jak w 3NF). Mniej redundancji, ale więcej JOINów.

sql
-- Płatek śniegu: product → subcategory → category (3 tabele)
CREATE TABLE dim_category (
    category_id   NUMBER PRIMARY KEY,
    category_name VARCHAR2(50)
);
CREATE TABLE dim_subcategory (
    subcategory_id   NUMBER PRIMARY KEY,
    subcategory_name VARCHAR2(50),
    category_id      NUMBER REFERENCES dim_category(category_id)
);
CREATE TABLE dim_product_sf (
    product_id     NUMBER PRIMARY KEY,
    product_name   VARCHAR2(100),
    subcategory_id NUMBER REFERENCES dim_subcategory(subcategory_id)
);
-- Zapytanie wymaga 3 JOINów zamiast 1
CechaGwiazdaPłatek śniegu
Normalizacja wymiarówNie (zdenormalizowane)Tak (hierarchie osobne tabele)
Liczba JOINówMałaDuża
Redundancja danychTakNie
Złożoność zapytańProstaZłożona
Wydajność OLAPLepszaGorsza (więcej JOINów)

Konstelacja faktów (Fact Constellation)

Kilka tabel faktów współdzieli te same tabele wymiarów. Np. sh.sales i sh.costs obie korzystają z wymiarów produktów, czasu i kanałów.

sql
-- Konstelacja: SALES + COSTS współdzielą wymiary
-- fact_sales(prod_id FK, time_id FK, channel_id FK, amount_sold, quantity_sold)
-- fact_costs(prod_id FK, time_id FK, channel_id FK, unit_cost, unit_price)
-- dim_products(prod_id PK, prod_name, prod_category)
-- dim_times(time_id PK, calendar_year, calendar_month_number)
-- dim_channels(channel_id PK, channel_desc)

-- Zapytanie na konstelacji: marża = sprzedaż - koszty:
SELECT p.prod_name,
       SUM(s.amount_sold) AS przychod,
       SUM(c.unit_cost * s.quantity_sold) AS koszt,
       SUM(s.amount_sold - c.unit_cost * s.quantity_sold) AS marza
FROM sh.sales s
JOIN sh.costs c    ON s.prod_id = c.prod_id AND s.time_id = c.time_id
                  AND s.channel_id = c.channel_id
JOIN sh.products p ON s.prod_id = p.prod_id
GROUP BY p.prod_name
ORDER BY marza DESC;

Tabela faktów bez miar (Factless Fact Table)

Tabela faktów zawierająca tylko klucze obce — bez kolumn miar. Używana do rejestrowania zdarzeń (obecność, przypisanie).

sql
-- Przykład: które produkty były na promocji w danym dniu (bez kwot)?
CREATE TABLE fact_promo_coverage (
    time_id     NUMBER REFERENCES dim_times(time_id),
    prod_id     NUMBER REFERENCES dim_products(prod_id),
    promo_id    NUMBER REFERENCES dim_promotions(promo_id),
    store_id    NUMBER REFERENCES dim_stores(store_id)
    -- brak kolumn miar!
);

-- Pytanie: ile produktów objęto promocją 2023-01-15?
SELECT COUNT(*) FROM fact_promo_coverage
WHERE time_id = (SELECT time_id FROM dim_times WHERE calendar_date = DATE '2023-01-15');

-- Typowe użycia: frekwencja pracownicza, dostępność produktów,
-- uczestnictwo klientów w szkoleniach
⚠️
Tabela faktów vs tabela faktów bez miar
Zwykła tabela faktów: klucze obce + miary liczbowe (kwoty, ilości).
Factless fact table: tylko klucze obce — miarą jest COUNT(*) (fakt zaistnienia zdarzenia).
Jeśli chcesz dodać miarę "sztuczną", dodaj kolumnę z wartością 1 (np. zdarzenie NUMBER DEFAULT 1).

SH Schema — Oracle Sales History

TabelaTypKluczowe kolumny
sh.salesFaktyprod_id, cust_id, time_id, channel_id, amount_sold, quantity_sold
sh.costsFaktyprod_id, time_id, channel_id, unit_cost, unit_price
sh.productsWymiarprod_id, prod_name, prod_category, prod_subcategory, prod_list_price
sh.customersWymiarcust_id, cust_first_name, cust_last_name, cust_city, country_id
sh.timesWymiar czasutime_id, calendar_year, calendar_quarter_number, calendar_month_number
sh.channelsWymiarchannel_id, channel_desc, channel_class
sh.promotionsWymiarpromo_id, promo_name, promo_category
sh.countriesWymiarcountry_id, country_name, country_region

Przykładowe zapytania analityczne SH

sql
-- Sprzedaż wg roku i kategorii produktu:
SELECT t.calendar_year,
       p.prod_category,
       SUM(s.amount_sold)   AS sprzedaz,
       SUM(s.quantity_sold) AS ilosc
FROM sh.sales s
JOIN sh.times    t ON s.time_id   = t.time_id
JOIN sh.products p ON s.prod_id   = p.prod_id
GROUP BY t.calendar_year, p.prod_category
ORDER BY t.calendar_year, sprzedaz DESC;

-- TOP 5 klientów wg wartości zakupów:
SELECT cust_id, SUM(amount_sold) AS total
FROM sh.sales
GROUP BY cust_id
ORDER BY total DESC
FETCH FIRST 5 ROWS ONLY;

-- Marża: sprzedaż minus koszty (JOIN między dwiema tabelami faktów):
SELECT s.prod_id,
       SUM(s.amount_sold) AS przychod,
       SUM(c.unit_cost * s.quantity_sold) AS koszt,
       SUM(s.amount_sold) - SUM(c.unit_cost * s.quantity_sold) AS marza
FROM sh.sales s
JOIN sh.costs c ON s.prod_id = c.prod_id AND s.time_id = c.time_id
GROUP BY s.prod_id;

ROLLUP na SH — raporty wielopoziomowe

sql
-- Sprzedaż: rok → kwartał → miesiąc → total
SELECT t.calendar_year,
       t.calendar_quarter_number,
       t.calendar_month_number,
       SUM(s.amount_sold) AS sprzedaz
FROM sh.sales s
JOIN sh.times t ON s.time_id = t.time_id
GROUP BY ROLLUP(t.calendar_year, t.calendar_quarter_number, t.calendar_month_number)
ORDER BY t.calendar_year NULLS LAST,
         t.calendar_quarter_number NULLS LAST,
         t.calendar_month_number NULLS LAST;
Granularity — ziarnistość tabeli faktów
Ziarnistość to poziom szczegółowości jednego wiersza w tabeli faktów. Np. sh.sales: jeden wiersz = jedna transakcja sprzedaży (najniższa ziarnistość). Im niższa ziarnistość, tym więcej wierszy i większa elastyczność analiz. Ziarnistość definiuje się PRZED projektowaniem schematu.

Pytania egzaminacyjne

🔤
Pytanie ABCD

Która z 4 cech Inmona opisuje, że dane hurtowni są uporządkowane wg tematów biznesowych (klienci, sprzedaż), a nie procesów aplikacji?

🔤
Pytanie ABCD

Jaka jest główna różnica między schematem gwiazdy a płatkiem śniegu?

🔤
Pytanie ABCD

Co to jest 'grain' (ziarnistość) w kontekście tabeli faktów?

🔤
Pytanie ABCD

Operacja OLAP 'Drill-down' to...

🔤
Pytanie ABCD

Czym jest 'Factless Fact Table' (tabela faktów bez miar)?

🔤
Pytanie ABCD

Które zestawienie technologii OLAP jest POPRAWNE?

🔤
Pytanie ABCD

Saldo bankowe na koniec miesiąca to miara:

🔤
Pytanie ABCD

Który wymiar jest ZAWSZE obecny w każdej hurtowni danych?

Ćwiczenia

💪

Ćwiczenie 1 — Sprzedaż wg regionu i kanału

Wyświetl łączną sprzedaż (amount_sold) z SH schema grupując po kraju (country_name z sh.countries) i kanale (channel_desc z sh.channels). Posortuj malejąco wg sprzedaży.

💪

Ćwiczenie 2 — ROLLUP: sprzedaż rok/kwartał/total

Użyj ROLLUP na sh.sales z sh.times, aby wygenerować sumy sprzedaży na poziomie: (rok, kwartał), (rok) i () — grand total. Użyj GROUPING() do oznaczenia wierszy sumarycznych.

💪

Ćwiczenie 3 — TOP produkty wg kategorii (RANK)

Wyświetl TOP 3 produkty wg sprzedaży w każdej kategorii produktu. Użyj RANK() OVER (PARTITION BY prod_category ORDER BY suma_sprzedazy DESC).