💾 Widoki Zmaterializowane

Tworzenie MV, tryby odświeżania, MV LOG, słowniki danych

⚡ Kluczowe fakty do zapamiętania
  • REFRESH FAST = odświeżanie 'delta' — Oracle zapisuje tylko ZMIANY w specjalnym dzienniku (MV LOG), a przy odświeżeniu stosuje tylko te zmiany zamiast przeliczać wszystko od nowa. Wymaga CREATE MATERIALIZED VIEW LOG ON tabela na każdej tabeli źródłowej. Bez logu Oracle odmówi wykonania FAST.
  • ON COMMIT wymaga REFRESH FAST. Oracle nie pozwala na kombinację REFRESH COMPLETE ON COMMIT — byłoby to zbyt kosztowne (przeliczanie całego MV po każdym COMMIT). Zapamiętaj: ON COMMIT → tylko FAST.
  • ENABLE QUERY REWRITE = transparentna optymalizacja. Oracle sam wykrywa, że zapytanie do tabel bazowych pasuje do istniejącego MV, i zamiast przeliczać — odpytuje MV. Aplikacja nic nie wie i nic nie zmienia.
  • MV LOG pełna składnia: WITH ROWID, SEQUENCE (kolumna1, kolumna2) INCLUDING NEW VALUES. ROWID = śledzenie aktualizacji, SEQUENCE = kolejność zmian, INCLUDING NEW VALUES = nowe wartości dostępne (wymagane dla SUM FAST).
  • staleness w USER_MVIEWS: FRESH = dane aktualne. STALE = tabele źródłowe zmieniły się od ostatniego refresh (MV jest nieaktualny). UNKNOWN = Oracle nie wie czy aktualne.
  • DBMS_MVIEW.REFRESH('nazwa', 'X'): 'C' = Complete (przebuduj od zera), 'F' = Fast (delta, wymaga logu), '?' = Force (F jeśli możliwe, C jeśli nie).
  • GROUP BY w MV z REFRESH FAST wymaga w zapytaniu MV: COUNT(*) i COUNT(kolumna) dla każdej funkcji agregującej (SUM, AVG, itd.). Bez tego Fast Refresh nie zadziała.
  • START WITH ... NEXT ... = harmonogram automatycznego odświeżania wbudowany w definicję MV. START WITH SYSDATE NEXT SYSDATE+1 = odświeżaj co 24h. Bez tego wymagane jest ręczne DBMS_MVIEW.REFRESH.

Widok zwykły vs zmaterializowany

CechaWidok (VIEW)Widok zmaterializowany (MV)
Dane fizyczneNie — tylko definicja SQLTak — przechowuje wyniki
WydajnośćZawsze przeliczaSzybka (dane na dysku)
OdświeżanieAutomatyczne (zawsze aktualne)Ręczne lub wg harmonogramu
UżycieAbstrakcja, bezpieczeństwoCache wyników, hurtownie

Tworzenie widoku zmaterializowanego

sql
CREATE MATERIALIZED VIEW mv_dept_summary
BUILD IMMEDIATE                  -- zbuduj od razu (lub: DEFERRED = przy pierwszym odświeżeniu)
REFRESH COMPLETE                 -- odświeżaj pełnie (lub FAST = delta)
ON DEMAND                        -- odświeżaj ręcznie (lub ON COMMIT = przy każdym COMMIT)
AS
SELECT d.department_name,
       COUNT(e.employee_id)    AS liczba_prac,
       AVG(e.salary)           AS srednia_pensja
FROM hr.employees e
JOIN hr.departments d ON e.department_id = d.department_id
GROUP BY d.department_name;

Parametry BUILD

OpcjaOpis
BUILD IMMEDIATEDane załadowane podczas CREATE (domyślne)
BUILD DEFERREDMV jest puste aż do pierwszego odświeżenia

Parametry REFRESH

OpcjaOpisWymagania
REFRESH COMPLETEPełne przepisanie danych (DELETE + INSERT)Brak — zawsze możliwe
REFRESH FASTDelta — tylko zmienione wierszeWymaga MV LOG na tabelach źródłowych
REFRESH FORCEPróbuj FAST, gdy niemożliwe — COMPLETEMV LOG jeśli dostępny
REFRESH NEVERNigdy nie odświeżaj automatycznie

Parametry ON

OpcjaKiedy odświeżaneUwaga
ON DEMANDTylko ręcznie (DBMS_MVIEW.REFRESH)Domyślne
ON COMMITPo każdym COMMIT zmieniającym tabele źródłoweWymaga REFRESH FAST + MV LOG

Harmonogram automatycznego odświeżania — START WITH / NEXT

Zamiast ręcznego DBMS_MVIEW.REFRESH możesz zdefiniować harmonogram bezpośrednio w definicji MV.

sql
-- START WITH = kiedy pierwsze odświeżenie (domyślnie: czas tworzenia MV)
-- NEXT = wyrażenie obliczające czas następnego odświeżenia
CREATE MATERIALIZED VIEW sprzedaz_mv
BUILD IMMEDIATE
REFRESH FAST
START WITH SYSDATE
NEXT SYSDATE + 1              -- co 24 godziny
AS
SELECT id_sklepu, id_produktu,
       SUM(kwota) AS suma, AVG(kwota) AS srednia
FROM sprzedaz
GROUP BY id_sklepu, id_produktu;

-- Odświeżanie raz w tygodniu (co 7 dni):
REFRESH COMPLETE
START WITH SYSDATE
NEXT SYSDATE + 7

-- Odświeżanie co godzinę:
REFRESH FAST
START WITH SYSDATE
NEXT SYSDATE + 1/24

-- Odświeżanie każdej nocy o 2:00:
REFRESH COMPLETE
START WITH TRUNC(SYSDATE) + 2/24
NEXT TRUNC(SYSDATE) + 1 + 2/24
START WITH / NEXT — praktyczne wzorce
SYSDATE + 1 = co 24 godziny.
SYSDATE + 1/24 = co godzinę.
TRUNC(SYSDATE) + 1 + 2/24 = jutro o 2:00.
Brak START WITH / NEXT = wymagane ręczne odświeżanie (ON DEMAND).
🚨
ON COMMIT wymaga REFRESH FAST
Nie można używać ON COMMIT z REFRESH COMPLETE — pełne odświeżenie przy każdym COMMIT byłoby zbyt kosztowne.ON COMMIT wymaga REFRESH FAST, który wymaga MV LOG.

ENABLE QUERY REWRITE — automatyczne przepisywanie zapytań

Oracle może automatycznie przekierować zapytanie do MV zamiast do tabel bazowych — bez zmian w kodzie aplikacji.

sql
CREATE MATERIALIZED VIEW mv_dept_summary
BUILD IMMEDIATE
REFRESH FAST ON COMMIT
ENABLE QUERY REWRITE           -- zezwól na automatyczne przepisywanie zapytań
AS
SELECT d.department_name,
       COUNT(e.employee_id) AS liczba_prac,
       SUM(e.salary)        AS suma_pensji
FROM hr.employees e
JOIN hr.departments d ON e.department_id = d.department_id
GROUP BY d.department_name;

-- Oracle automatycznie użyje mv_dept_summary dla tego zapytania:
SELECT department_name, COUNT(employee_id)
FROM hr.employees e JOIN hr.departments d
     ON e.department_id = d.department_id
GROUP BY department_name;
-- ^ Nie trzeba pisać "FROM mv_dept_summary" — Oracle przepisuje sam
ℹ️
ENABLE QUERY REWRITE — wymagania
Wymaga parametru sesji/systemu: QUERY_REWRITE_ENABLED = TRUE (domyślnie TRUE od Oracle 10g).
MV musi być FRESH (odświeżony) — przy STALE przepisywanie może być wyłączone.
Jeden MV może obsługiwać wiele różnych zapytań o podobnej strukturze.

Typy widoków zmaterializowanych

TypCharakterystykaWymagania
AgregującyZawiera GROUP BY + agregaty (COUNT, SUM, AVG)COUNT(*) wymagany przy agregatach; MV LOG musi zawierać używane kolumny
Join-onlyZłączenie tabel bez agregacjiMV LOG na każdej tabeli; ROWID w MV LOG
Zagnieżdżony (nested)MV oparty na innym MVBazowy MV musi istnieć; odświeżanie w odpowiedniej kolejności
sql
-- MV agregujący (najczęstszy):
CREATE MATERIALIZED VIEW mv_agg
BUILD IMMEDIATE REFRESH FAST ON COMMIT
AS SELECT department_id, COUNT(*) cnt, SUM(salary) suma
   FROM hr.employees GROUP BY department_id;

-- MV join-only (bez GROUP BY):
CREATE MATERIALIZED VIEW mv_join
BUILD IMMEDIATE REFRESH FAST ON DEMAND
AS SELECT e.employee_id, e.last_name, d.department_name
   FROM hr.employees e JOIN hr.departments d
   ON e.department_id = d.department_id;

-- MV zagnieżdżony (bazuje na mv_agg):
CREATE MATERIALIZED VIEW mv_nested
BUILD IMMEDIATE REFRESH COMPLETE ON DEMAND
AS SELECT department_id, suma / cnt AS sr_pensja
   FROM mv_agg;

Obiekty tworzone przez Oracle przy CREATE MATERIALIZED VIEW

ObiektNazwaOpis
Tabela konteneraTaka sama jak MVFizyczna tabela przechowująca dane MV
Definicja widokuTaka sama jak MVWidok SQL używany do odświeżania
Indeks (MV agregujący)Automatyczna nazwaIndeks na kolumnach GROUP BY dla REFRESH FAST

MV LOG — log zmian dla REFRESH FAST

MV LOG śledzi zmiany w tabeli źródłowej. Istnieją 3 warianty identyfikacji wierszy:

OpcjaCo zapisujeKiedy
WITH PRIMARY KEYWartości klucza głównegoTabela ma PK; domyślne
WITH ROWIDFizyczny adres wiersza (ROWID)Brak PK lub join-only MV
WITH ROWID, PRIMARY KEYOba: ROWID i PKDla elastyczności i wydajności
WITH SEQUENCEKolejność zmian (timestamp)Zawsze razem z PRIMARY KEY lub ROWID
sql
-- Utwórz log zmian na tabeli źródłowej:
CREATE MATERIALIZED VIEW LOG ON hr.employees
WITH ROWID, SEQUENCE (employee_id, salary, department_id)
INCLUDING NEW VALUES;

-- Dopiero teraz możesz tworzyć MV z REFRESH FAST:
CREATE MATERIALIZED VIEW mv_fast_refresh
BUILD IMMEDIATE
REFRESH FAST ON COMMIT
AS
SELECT employee_id, salary, department_id
FROM hr.employees;
⚠️
Wymagania dla REFRESH FAST
1. MV LOG musi istnieć na każdej tabeli źródłowej
2. MV LOG musi zawierać wszystkie kolumny używane w MV
3. Zapytanie MV musi spełniać ograniczenia (np. GROUP BY z COUNT(*), agregaty)
4. Przy agregacji: MV musi zawierać COUNT(*) i COUNT(kol) dla każdej agregowanej kolumny

Ręczne odświeżanie

sql
-- Odśwież konkretny widok:
EXEC DBMS_MVIEW.REFRESH('mv_dept_summary');

-- Odśwież z trybem:
EXEC DBMS_MVIEW.REFRESH('mv_dept_summary', 'C');  -- C = Complete
EXEC DBMS_MVIEW.REFRESH('mv_dept_summary', 'F');  -- F = Fast
EXEC DBMS_MVIEW.REFRESH('mv_dept_summary', '?');  -- ? = Force

-- Odśwież wiele widoków naraz:
EXEC DBMS_MVIEW.REFRESH_ALL_MVIEWS();

-- Odśwież listę widoków:
EXEC DBMS_MVIEW.REFRESH(LIST => 'mv1,mv2,mv3', METHOD => 'C');

Słowniki danych — przydatne widoki

sql
-- Lista widoków zmaterializowanych bieżącego użytkownika:
SELECT mview_name, refresh_mode, refresh_method, last_refresh_date, staleness
FROM user_mviews;
-- staleness: FRESH (aktualne), STALE (nieaktualne), UNKNOWN

-- Wszystkie MV w bazie (DBA):
SELECT owner, mview_name, refresh_mode, last_refresh_date
FROM dba_mviews;

-- Logi MV:
SELECT log_table, log_trigger, rowids, sequence, primary_key
FROM user_mview_logs;

-- Historia czasów odświeżania MV:
SELECT name, last_refresh
FROM user_mview_refresh_times;

-- Komentarze do MV:
SELECT mview_name, comments
FROM user_mview_comments;

ALTER MATERIALIZED VIEW

sql
-- Zmiana trybu odświeżania:
ALTER MATERIALIZED VIEW mv_dept_summary
REFRESH COMPLETE ON DEMAND;

-- Włączenie/wyłączenie QUERY REWRITE:
ALTER MATERIALIZED VIEW mv_dept_summary ENABLE QUERY REWRITE;
ALTER MATERIALIZED VIEW mv_dept_summary DISABLE QUERY REWRITE;

-- Zmiana na BUILD DEFERRED (kompilacja bez ładowania):
ALTER MATERIALIZED VIEW mv_dept_summary
REFRESH COMPLETE ON DEMAND;

-- Skompiluj MV po zmianie struktury bazowych tabel:
ALTER MATERIALIZED VIEW mv_dept_summary COMPILE;

COMMENT ON MATERIALIZED VIEW

sql
-- Dodaj komentarz do MV:
COMMENT ON MATERIALIZED VIEW mv_dept_summary IS 'Podsumowanie departamentów — odświeżaj raz dziennie';

-- Odczytaj komentarze (z ALL_MVIEW_COMMENTS lub USER_MVIEW_COMMENTS):
SELECT mview_name, comments
FROM user_mview_comments
WHERE mview_name = 'MV_DEPT_SUMMARY';

Przywileje wymagane do tworzenia MV

PrzywilejDo czego
CREATE MATERIALIZED VIEWTworzenie MV we własnym schemacie
CREATE ANY MATERIALIZED VIEWTworzenie MV w cudzym schemacie
CREATE TABLEWymagany — MV tworzy tabelę kontenera
QUERY REWRITEENABLE QUERY REWRITE we własnym schemacie
GLOBAL QUERY REWRITEENABLE QUERY REWRITE na tabelach innych schematów

Usuwanie MV i LOG

sql
-- Usuń widok zmaterializowany:
DROP MATERIALIZED VIEW mv_dept_summary;

-- Usuń log (najpierw sprawdź czy nie ma MV korzystającego z FAST):
DROP MATERIALIZED VIEW LOG ON hr.employees;

Pytania egzaminacyjne

🔤
Pytanie ABCD

Które połączenie parametrów jest NIEPOPRAWNE dla widoku zmaterializowanego?

🔤
Pytanie ABCD

Co jest wymagane do użycia REFRESH FAST?

🔤
Pytanie ABCD

Co robi ENABLE QUERY REWRITE?

🔤
Pytanie ABCD

Pole 'staleness' w USER_MVIEWS = 'STALE' oznacza...

🔍 Czy to się wykona?
CREATE MATERIALIZED VIEW mv_test
BUILD IMMEDIATE
REFRESH FAST ON COMMIT
AS SELECT employee_id, salary FROM hr.employees;
🔍 Czy to się wykona?
EXEC DBMS_MVIEW.REFRESH('MV_DEPT_SUMMARY', 'C');
🔍 Czy to się wykona?
DROP MATERIALIZED VIEW LOG ON hr.employees;
🔎 Znajdź błąd — wskaż niepoprawną linię
1CREATE MATERIALIZED VIEW mv_fast_test
2BUILD IMMEDIATE
3REFRESH FAST ON COMMIT
4AS
5SELECT department_id, SUM(salary) AS suma_pensji
6FROM hr.employees
7GROUP BY department_id;
🔎 Znajdź błąd — wskaż niepoprawną linię
1CREATE MATERIALIZED VIEW mv_test
2BUILD IMMEDIATE
3REFRESH COMPLETE ON COMMIT
4AS SELECT department_id, AVG(salary) FROM hr.employees
5GROUP BY department_id;

Ćwiczenia

💪

Ćwiczenie 1 — Podstawowy MV

Utwórz widok zmaterializowany mv_salary_stats, który dla każdego działu pokazuje: department_id, liczbę pracowników, średnią pensję, minimalną i maksymalną pensję. Użyj BUILD IMMEDIATE, REFRESH COMPLETE ON DEMAND.

💪

Ćwiczenie 2 — MV LOG i REFRESH FAST

Utwórz MV LOG na hr.employees dla kolumn employee_id, salary, department_id. Następnie stwórz widok zmaterializowany mv_emp_fast z REFRESH FAST ON COMMIT, który wybiera te kolumny.

💪

Ćwiczenie 3 — Słowniki i odświeżanie

Wyświetl wszystkie widoki zmaterializowane bieżącego użytkownika (nazwa, tryb odświeżenia, data ostatniego odświeżenia). Następnie ręcznie odśwież mv_salary_stats metodą COMPLETE.