📄

Ściągawka — Cała Składnia

Wszystkie konstrukcje na jednej stronie — szybkie przypomnienie

GROUP BY Extensions

ROLLUP(a,b,c)n+1 poziomów: (a,b,c), (a,b), (a), ()
CUBE(a,b)2ⁿ kombinacji: (a,b), (a), (b), ()
GROUPING SETS(...)tylko wybrane kombinacje
GROUPING(col)1 = NULL z powodu ROLLUP/CUBE, 0 = prawdziwa wartość
GROUPING_ID(a,b)wektor binarny GROUPING() jako liczba dziesiętna
sql
-- ROLLUP — hierarchia sum (dół do góry)
SELECT department_id, job_id, SUM(salary)
FROM hr.employees
GROUP BY ROLLUP(department_id, job_id);
-- wiersze: per (dept+job) + per dept + grand total

-- CUBE — wszystkie możliwe kombinacje
GROUP BY CUBE(department_id, job_id);
-- dodaje też: per job (bez dept)

-- GROUPING SETS — ręcznie wybierz kombinacje
GROUP BY GROUPING SETS(
  (department_id, job_id),
  (department_id),
  ()
);

-- Rozróżnij NULL-rollup od NULL-z-danych
SELECT
  CASE WHEN GROUPING(department_id) = 1
       THEN 'WSZYSTKIE' ELSE TO_CHAR(department_id) END AS dept,
  SUM(salary)
FROM hr.employees
GROUP BY ROLLUP(department_id);

Zapytania Hierarchiczne (CONNECT BY)

PRIOR col = col2PRIOR stoi przed kolumną RODZICA
START WITH condwarunek dla korzeni drzewa (opcjonalne)
LEVELgłębokość: korzeń=1, dzieci=2, wnuki=3…
CONNECT_BY_ISLEAF1 jeśli węzeł jest liściem (brak dzieci)
CONNECT_BY_ROOT colwartość tej kolumny w korzeniu
SYS_CONNECT_BY_PATH(col,'/')ścieżka od korzenia do bieżącego węzła
WHERE vs AND w CONNECT BYWHERE filtruje wynik; AND przycina gałęzie
sql
-- Drzewo od góry w dół: PRIOR na kolumnie rodzica
SELECT
  LPAD(' ', (LEVEL-1)*2) || last_name AS tree,
  LEVEL,
  SYS_CONNECT_BY_PATH(last_name, '/') AS path
FROM hr.employees
START WITH manager_id IS NULL          -- korzenie = bez menedżera
CONNECT BY PRIOR employee_id = manager_id;  -- PRIOR na rodzicu
-- Czytaj: "employee_id RODZICA = manager_id DZIECKA"

-- Filtrowanie:
WHERE  job_id = 'CLERK'          -- usuwa wiersze, dzieci zostają
AND    job_id = 'CLERK'          -- w CONNECT BY: przycina całą gałąź
HAVING LEVEL <= 3                -- głębokość max 3

Funkcje Analityczne (OVER)

funkcja() OVER ()cała tabela = jedna partycja, bez sortowania
PARTITION BY colosobne okno per wartość kolumny
ORDER BY colsortowanie w oknie; włącza domyślną ramkę!
ROWS BETWEEN ... AND ...ramka po numerze wiersza (dokładna)
RANGE BETWEEN ...ramka po wartości (domyślna gdy jest ORDER BY)
UNBOUNDED PRECEDINGod początku partycji
UNBOUNDED FOLLOWINGdo końca partycji
CURRENT ROWbieżący wiersz
sql
-- Ranking
RANK()        OVER (PARTITION BY dept ORDER BY sal DESC)  -- luki po remisie: 1,1,3
DENSE_RANK()  OVER (PARTITION BY dept ORDER BY sal DESC)  -- bez luk: 1,1,2
ROW_NUMBER()  OVER (PARTITION BY dept ORDER BY sal DESC)  -- unikalne: 1,2,3

-- TOP N per group:
SELECT * FROM (
  SELECT e.*, ROW_NUMBER() OVER (PARTITION BY department_id ORDER BY salary DESC) rn
  FROM hr.employees e
) WHERE rn = 1;   -- najlepiej zarabiający w każdym dziale

-- LAG / LEAD
LAG(salary, 1, 0)  OVER (ORDER BY hire_date)  -- poprzednia pensja (default=0)
LEAD(salary, 1)    OVER (ORDER BY hire_date)  -- następna pensja

-- Suma kumulacyjna
SUM(salary) OVER (PARTITION BY dept ORDER BY hire_date
                  ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW)

-- Grand total w każdym wierszu
SUM(salary) OVER ()

-- LAST_VALUE — UWAGA NA PUŁAPKĘ!
-- Domyślna ramka z ORDER BY: RANGE UNBOUNDED PRECEDING TO CURRENT ROW
-- "Ostatnia wartość" = bieżący wiersz! Naprawienie:
LAST_VALUE(salary) OVER (
  PARTITION BY dept ORDER BY salary
  ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING  -- ← wymagane!
)

Multi-table INSERT

INSERT ALLkażdy wiersz do WSZYSTKICH pasujących WHEN
INSERT FIRSTkażdy wiersz do PIERWSZEGO pasującego WHEN
WHEN cond THEN INTO tblwarunkowe wstawianie
CTASCREATE TABLE AS SELECT — kopiuje dane, NIE constrainty
sql
-- INSERT ALL — jeden wiersz może trafić do wielu tabel
INSERT ALL
  WHEN salary > 10000 THEN INTO high_sal (id, sal) VALUES (employee_id, salary)
  WHEN department_id = 50 THEN INTO dept50 (id)   VALUES (employee_id)
  ELSE INTO others (id) VALUES (employee_id)
SELECT employee_id, salary, department_id FROM hr.employees;

-- INSERT FIRST — zatrzymuje się po pierwszym dopasowaniu
INSERT FIRST
  WHEN salary > 10000 THEN INTO tier_a VALUES (employee_id, salary)
  WHEN salary > 5000  THEN INTO tier_b VALUES (employee_id, salary)
  ELSE                     INTO tier_c VALUES (employee_id, salary)
SELECT employee_id, salary FROM hr.employees;

-- CTAS — kopiuje strukturę + dane, NIE kopiuje PK/FK/CHECK
CREATE TABLE emp_copy AS
SELECT * FROM hr.employees WHERE department_id = 50;

-- Pusta kopia (tylko struktura):
CREATE TABLE emp_empty AS SELECT * FROM hr.employees WHERE 1=2;

MERGE (Upsert)

MERGE INTO targettabela docelowa (modyfikowana)
USING source ON condźródło (tabela/SELECT) i warunek łączenia
WHEN MATCHED THEN UPDATE/DELETEON = true → wiersz istnieje w obu tabelach
WHEN NOT MATCHED THEN INSERTON = false → nowy wiersz z źródła
sql
-- Pełny MERGE
MERGE INTO hr.employees t
USING (
  SELECT employee_id, salary, department_id FROM staging_updates
) s
ON (t.employee_id = s.employee_id)
WHEN MATCHED THEN
  UPDATE SET t.salary        = s.salary,
             t.department_id = s.department_id
  -- DELETE WHERE t.salary < 1000   ← opcjonalne: usuń przy spełnieniu warunku
WHEN NOT MATCHED THEN
  INSERT (employee_id, salary, department_id)
  VALUES (s.employee_id, s.salary, s.department_id);

-- SCD Typ 2 — zachowaj historię (nowy wiersz + zamknij stary)
MERGE INTO dim_employee t
USING staging s ON (t.employee_id = s.employee_id AND t.is_current = 'Y')
WHEN MATCHED THEN
  UPDATE SET t.valid_to = SYSDATE, t.is_current = 'N'
WHEN NOT MATCHED THEN
  INSERT (employee_id, name, salary, valid_from, valid_to, is_current)
  VALUES (s.employee_id, s.name, s.salary, SYSDATE, DATE '9999-12-31', 'Y');

Widoki Zmaterializowane (MV)

REFRESH FASTtylko zmienione wiersze — wymaga MV LOG
REFRESH COMPLETEpełny re-SELECT — zawsze możliwe
ON COMMITodświeżanie przy każdym COMMIT na tabeli bazowej
ON DEMANDręczne: DBMS_MVIEW.REFRESH('mv_name')
ENABLE QUERY REWRITEOracle automatycznie używa MV zamiast tabel
sql
-- Krok 1: MV LOG na tabeli bazowej (wymagany dla FAST)
CREATE MATERIALIZED VIEW LOG ON hr.employees
  WITH ROWID, PRIMARY KEY, SEQUENCE
  INCLUDING NEW VALUES;

-- Krok 2: Tworzenie MV
CREATE MATERIALIZED VIEW mv_dept_stats
  BUILD IMMEDIATE          -- od razu oblicz (DEFERRED = przy pierwszym REFRESH)
  REFRESH FAST ON COMMIT   -- FAST wymaga MV LOG
  ENABLE QUERY REWRITE     -- Oracle używa MV automatycznie
AS
  SELECT department_id, COUNT(*) cnt, SUM(salary) total_sal
  FROM hr.employees
  GROUP BY department_id;

-- Ręczne odświeżanie
EXEC DBMS_MVIEW.REFRESH('MV_DEPT_STATS', 'C');  -- C=COMPLETE, F=FAST

-- Słowniki danych
SELECT * FROM user_mviews;
SELECT * FROM user_mview_logs;

ETL — SQL*Loader, Tabele Zewnętrzne, Data Pump

SQL*Loader (sqlldr)ładuje plik zewnętrzny DO tabel Oracle
EXTERNAL TABLEplik pozostaje na dysku; tylko SELECT
expdp / impdpData Pump — eksport/import po stronie serwera
DIRECTORY objectścieżka serwera — wymagana przez Data Pump i External Table
SQL*Loader — plik sterujący (.ctl)
LOAD DATA
INFILE 'pracownicy.csv'
BADFILE  'pracownicy.bad'
DISCARDFILE 'pracownicy.dsc'
INTO TABLE hr.employees_load
APPEND                        -- INSERT | REPLACE | TRUNCATE | APPEND
FIELDS TERMINATED BY ',' OPTIONALLY ENCLOSED BY '"'
(
  employee_id,
  first_name,
  last_name,
  hire_date DATE "YYYY-MM-DD"
)

-- Wywołanie:
-- sqlldr userid=hr/hr control=load.ctl log=load.log
Tabela zewnętrzna
CREATE TABLE ext_employees (
  employee_id  NUMBER,
  first_name   VARCHAR2(50),
  last_name    VARCHAR2(50)
)
ORGANIZATION EXTERNAL (
  TYPE ORACLE_LOADER
  DEFAULT DIRECTORY ext_dir
  ACCESS PARAMETERS (
    RECORDS DELIMITED BY NEWLINE
    FIELDS TERMINATED BY ','
  )
  LOCATION ('pracownicy.csv')
)
REJECT LIMIT UNLIMITED;

-- Tylko SELECT — INSERT/UPDATE/DELETE → ORA-30657
Data Pump
-- Eksport schematu HR
expdp hr/hr DIRECTORY=dpump_dir DUMPFILE=hr_export.dmp SCHEMAS=HR LOGFILE=hr.log

-- Import do innego schematu
impdp system/sys DIRECTORY=dpump_dir DUMPFILE=hr_export.dmp
      REMAP_SCHEMA=HR:HR_TEST LOGFILE=import.log

-- Eksport tylko tabeli
expdp hr/hr DIRECTORY=dpump_dir DUMPFILE=emp.dmp TABLES=HR.EMPLOYEES

Hurtownie Danych — Schematy

Schemat gwiazdytabele wymiarów NIEZNORMALIZOWANE (płaskie) — szybsze JOINy
Schemat płatka śnieguwymiary ZNORMALIZOWANE (podzielone) — mniej redundancji
Tabela faktów (fact)miary + klucze obce do wymiarów
Tabela wymiarów (dim)atrybuty opisowe (klient, produkt, czas)
Grainco reprezentuje jeden wiersz w tabeli faktów
SCD Typ 1nadpisz — brak historii
SCD Typ 2nowy wiersz + valid_from/valid_to — pełna historia
SCD Typ 3kolumna current + previous — jeden krok wstecz

Schematy HR i SH — tabele i klucze

Schemat HR
hr.employees
employee_id, first_name, last_name, salary, department_id, manager_id, job_id, hire_date
hr.departments
department_id, department_name, manager_id, location_id
hr.jobs
job_id, job_title, min_salary, max_salary
hr.locations
location_id, city, country_id
hr.countries
country_id, country_name, region_id
hr.regions
region_id, region_name
Schemat SH (hurtownia)
sh.sales ⭐ FAKTY
prod_id, cust_id, time_id, channel_id, promo_id, amount_sold, quantity_sold, cost
sh.products
prod_id, prod_name, prod_category, prod_subcategory
sh.customers
cust_id, cust_last_name, cust_city, country_id
sh.times
time_id, calendar_year, calendar_quarter, calendar_month, day_name
sh.channels
channel_id, channel_desc (Direct/Internet/Partners)
sh.costs
prod_id, time_id, unit_cost, unit_price

Typowe Błędy na Egzaminie

ORA-00934Funkcja grupowa (AVG/SUM/COUNT) w WHERE — przenieś do HAVING
ORA-01427Podzapytanie zwraca > 1 wiersz — użyj IN zamiast =
ROWNUM > n = 0 wierszyROWNUM przypisywany przed filtrowaniem — opakuj w podzapytanie
REFRESH FAST bez MV LOGTabela nie ma logu — użyj REFRESH COMPLETE lub utwórz MV LOG
LAST_VALUE bez ramkiDomyślna ramka kończy się na CURRENT ROW — dodaj ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWING
PRIOR po złej stronieCONNECT BY PRIOR child = parent → idziesz w górę (do rodziców). Chcesz w dół: PRIOR parent_col = child_col
WHERE zamiast HAVINGAVG(sal) > 5000 w WHERE = ORA-00934; w HAVING działa
INSERT FIRST vs ALLFIRST = pierwszy pasujący WHEN; ALL = każdy pasujący WHEN (wiersz może trafić do wielu tabel)