📄
Ś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 kombinacjeGROUPING(col)1 = NULL z powodu ROLLUP/CUBE, 0 = prawdziwa wartośćGROUPING_ID(a,b)wektor binarny GROUPING() jako liczba dziesiętnasql
-- 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ą RODZICASTART 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 korzeniuSYS_CONNECT_BY_PATH(col,'/')ścieżka od korzenia do bieżącego węzłaWHERE vs AND w CONNECT BYWHERE filtruje wynik; AND przycina gałęziesql
-- 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 3Funkcje Analityczne (OVER)
funkcja() OVER ()cała tabela = jedna partycja, bez sortowaniaPARTITION BY colosobne okno per wartość kolumnyORDER 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 partycjiUNBOUNDED FOLLOWINGdo końca partycjiCURRENT ROWbieżący wierszsql
-- 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 WHENINSERT FIRSTkażdy wiersz do PIERWSZEGO pasującego WHENWHEN cond THEN INTO tblwarunkowe wstawianieCTASCREATE TABLE AS SELECT — kopiuje dane, NIE constraintysql
-- 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 łączeniaWHEN MATCHED THEN UPDATE/DELETEON = true → wiersz istnieje w obu tabelachWHEN NOT MATCHED THEN INSERTON = false → nowy wiersz z źródłasql
-- 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 LOGREFRESH COMPLETEpełny re-SELECT — zawsze możliweON COMMITodświeżanie przy każdym COMMIT na tabeli bazowejON DEMANDręczne: DBMS_MVIEW.REFRESH('mv_name')ENABLE QUERY REWRITEOracle automatycznie używa MV zamiast tabelsql
-- 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 OracleEXTERNAL TABLEplik pozostaje na dysku; tylko SELECTexpdp / impdpData Pump — eksport/import po stronie serweraDIRECTORY objectścieżka serwera — wymagana przez Data Pump i External TableSQL*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.logTabela 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-30657Data 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.EMPLOYEESHurtownie Danych — Schematy
Schemat gwiazdytabele wymiarów NIEZNORMALIZOWANE (płaskie) — szybsze JOINySchemat płatka śnieguwymiary ZNORMALIZOWANE (podzielone) — mniej redundancjiTabela faktów (fact)miary + klucze obce do wymiarówTabela wymiarów (dim)atrybuty opisowe (klient, produkt, czas)Grainco reprezentuje jeden wiersz w tabeli faktówSCD Typ 1nadpisz — brak historiiSCD Typ 2nowy wiersz + valid_from/valid_to — pełna historiaSCD Typ 3kolumna current + previous — jeden krok wsteczSchematy 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 HAVINGORA-01427Podzapytanie zwraca > 1 wiersz — użyj IN zamiast =ROWNUM > n = 0 wierszyROWNUM przypisywany przed filtrowaniem — opakuj w podzapytanieREFRESH FAST bez MV LOGTabela nie ma logu — użyj REFRESH COMPLETE lub utwórz MV LOGLAST_VALUE bez ramkiDomyślna ramka kończy się na CURRENT ROW — dodaj ROWS BETWEEN UNBOUNDED PRECEDING AND UNBOUNDED FOLLOWINGPRIOR po złej stronieCONNECT BY PRIOR child = parent → idziesz w górę (do rodziców). Chcesz w dół: PRIOR parent_col = child_colWHERE zamiast HAVINGAVG(sal) > 5000 w WHERE = ORA-00934; w HAVING działaINSERT FIRST vs ALLFIRST = pierwszy pasujący WHEN; ALL = każdy pasujący WHEN (wiersz może trafić do wielu tabel)