🏗️ Hurtownie Danych
Schemat gwiazdy i płatka śniegu, SH schema, projektowanie
- →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
| Autor | Definicja |
|---|---|
| W. H. Inmon | Kolekcja danych zorientowana tematycznie, zintegrowana, nieulotna i zmienna w czasie, wspomagająca podejmowanie decyzji |
| R. Kimball | System pobierający dane ze źródeł, transformujący je i ładujący do struktur wielowymiarowych, wspierających zapytania analityczne |
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
| Cecha | OLTP (np. HR) | Hurtownia / OLAP |
|---|---|---|
| Cel | Operacje bieżące (INSERT, UPDATE, DELETE) | Analiza historyczna (SELECT) |
| Schemat | 3NF — znormalizowany | Gwiazda / płatek śniegu — zdenormalizowany |
| Dane | Aktualne | Historyczne (lata) |
| Zapytania | Proste, szybkie (klucze) | Złożone agregacje, JOIN wielu tabel |
| Wydajność | Dużo małych transakcji | Mało dużych zapytań analitycznych |
Architektura hurtowni — modele
| Model | Opis | Wady |
|---|---|---|
| Bez staging | Dane ładowane wprost ze źródeł do hurtowni; transformacje "w locie" | Obciążenie źródeł; trudna kontrola jakości |
| Ze staging area | Dane trafiają najpierw do strefy przejściowej (surowe dane, czyszczenie, ujednolicanie) | Bardziej złożona architektura |
| Ze staging + data marts | Dodatkowo tematyczne podmartownia (sprzedaż, finanse, marketing) dla konkretnych działów | Najdroższe; wymaga starannego zarządzania |
ROLAP / MOLAP / HOLAP — technologie OLAP
| Technologia | Przechowywanie | Zalety | Wady |
|---|---|---|---|
| ROLAP (Relational OLAP) | Relacyjna baza danych — tabele gwiazdy/płatka, SQL | Brak nowej infrastruktury, skaluje do dużych zbiorów | Wolniejszy — każde zapytanie OLAP = wiele JOINów |
| MOLAP (Multidimensional OLAP) | Wielowymiarowe kostki danych (cube), specjalny format | Bardzo szybkie zapytania — dane preaggregowane | Specjalna infrastruktura, słaba skalowalność przy wielu wymiarach |
| HOLAP (Hybrid OLAP) | Hybrydowo: szczegóły w ROLAP, agregaty w MOLAP | Kompromis prędkość + skalowalność | Złożona architektura, trudna administracja |
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
| Operacja | Kierunek | Przykład |
|---|---|---|
| Roll-up (drill-up) | Szczegół → ogół | Sklep → Miasto → Województwo |
| Drill-down | Ogół → szczegół | Rok → Kwartał → Miesiąc → Dzień |
| Slice | Wybór jednej wartości wymiaru | Tylko rok 2023 (cały plaster) |
| Dice | Wybór zakresu kilku wymiarów | Rok 2022–2023 AND Region = PL |
| Pivot / Rotate | Zmiana kolejności wymiarów | Zamień osie: produkt ↔ region |
Ziarnistość (Grain) — poziom szczegółowości
| Ziarnistość | Przykład wiersza faktów | Uwagi |
|---|---|---|
| Transakcja (najniższa) | Jedna sprzedaż w sklepie | Największa elastyczność, ogromna tabela |
| Dzienny snapshot | Sprzedaż dzienna na produkt | Kompromis |
| Miesięczny snapshot | Saldo konta na koniec miesiąca | Mniej miejsca, mniej zapytań |
Typy miar w tabeli faktów
| Typ | Opis | Przykłady | Jak agregować? |
|---|---|---|---|
| Addytywna | Można sumować po WSZYSTKICH wymiarach | amount_sold, quantity_sold, cost | SUM działa bez ograniczeń |
| Częściowo addytywna (semi-additive) | Można sumować po NIEKTÓRYCH wymiarach, nie wszystkich | Saldo konta na koniec miesiąca, stan magazynu | Sumuj 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 wymiarze | Cena jednostkowa, procent udział, wskaźniki (KPI) | Zawsze obliczaj pochodnie z miar addytywnych (np. marża = przychód - koszt) |
✅
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:
| Kolumna | Przykład | Uwaga |
|---|---|---|
time_id (PK) | 20240315 | Klucz zastępczy lub data jako NUMBER |
data (DATE) | 2024-03-15 | Konkretna data kalendarza |
dzien_tygodnia | 5 | 1=poniedziałek, 7=niedziela (lub 0=niedziela w Oracle) |
nazwa_dnia_pol / ang | Piątek / Friday | Przydatne w raportach |
miesiac | 3 | Numer miesiąca 1–12 |
nazwa_miesiaca_pol / ang | Marzec / March | Pełna i skrócona nazwa |
kwartal | 1 | 1–4 |
rok | 2024 | Rok kalendarzowy |
tydzien_roku | 11 | Numer tygodnia w roku (1–52) |
czy_swieto | 'T' / 'N' | Flaga dnia wolnego (święto) |
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.
-- 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.
-- 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| Cecha | Gwiazda | Płatek śniegu |
|---|---|---|
| Normalizacja wymiarów | Nie (zdenormalizowane) | Tak (hierarchie osobne tabele) |
| Liczba JOINów | Mała | Duża |
| Redundancja danych | Tak | Nie |
| Złożoność zapytań | Prosta | Złożona |
| Wydajność OLAP | Lepsza | Gorsza (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.
-- 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).
-- 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 szkoleniachFactless 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
| Tabela | Typ | Kluczowe kolumny |
|---|---|---|
sh.sales | Fakty | prod_id, cust_id, time_id, channel_id, amount_sold, quantity_sold |
sh.costs | Fakty | prod_id, time_id, channel_id, unit_cost, unit_price |
sh.products | Wymiar | prod_id, prod_name, prod_category, prod_subcategory, prod_list_price |
sh.customers | Wymiar | cust_id, cust_first_name, cust_last_name, cust_city, country_id |
sh.times | Wymiar czasu | time_id, calendar_year, calendar_quarter_number, calendar_month_number |
sh.channels | Wymiar | channel_id, channel_desc, channel_class |
sh.promotions | Wymiar | promo_id, promo_name, promo_category |
sh.countries | Wymiar | country_id, country_name, country_region |
Przykładowe zapytania analityczne SH
-- 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
-- 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;Pytania egzaminacyjne
Która z 4 cech Inmona opisuje, że dane hurtowni są uporządkowane wg tematów biznesowych (klienci, sprzedaż), a nie procesów aplikacji?
Jaka jest główna różnica między schematem gwiazdy a płatkiem śniegu?
Co to jest 'grain' (ziarnistość) w kontekście tabeli faktów?
Operacja OLAP 'Drill-down' to...
Czym jest 'Factless Fact Table' (tabela faktów bez miar)?
Które zestawienie technologii OLAP jest POPRAWNE?
Saldo bankowe na koniec miesiąca to miara:
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).